From 04bd9374a8102fbf1edeef378cb7e00544f2a0e5 Mon Sep 17 00:00:00 2001 From: hodasemi Date: Wed, 18 Jan 2023 11:51:16 +0100 Subject: [PATCH] Add telemetry history graph --- Cargo.toml | 1 + build.rs | 6 +- .../elements/leaderboard/leaderboard_grid.xml | 2 +- src/overlay/elements/mod.rs | 39 +++ src/overlay/elements/pedals/history.frag | 12 + src/overlay/elements/pedals/history.vert | 8 + src/overlay/elements/pedals/mod.rs | 238 +++++++++++++++++- src/overlay/elements/pedals/pedals.xml | 4 +- src/overlay/elements/pedals/pipeline.rs | 102 ++++++++ src/overlay/elements/radar/mod.rs | 38 +-- src/overlay/elements/watermark/mod.rs | 13 +- src/overlay/mod.rs | 13 +- src/overlay/rfactor_data.rs | 2 + 13 files changed, 416 insertions(+), 62 deletions(-) create mode 100644 src/overlay/elements/pedals/history.frag create mode 100644 src/overlay/elements/pedals/history.vert create mode 100644 src/overlay/elements/pedals/pipeline.rs diff --git a/Cargo.toml b/Cargo.toml index 1b3f910..0f90ea2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ cgmath = { version = "0.18.0", features = ["swizzle", "serde"] } paste = "1.0.11" serde = "1.0.152" serde_json = "1.0.91" +ringbuf = "0.3.2" diff --git a/build.rs b/build.rs index 1fadb6f..5586f94 100644 --- a/build.rs +++ b/build.rs @@ -10,8 +10,10 @@ const VK_HEADER: &[&str] = &[ const FN_PREFIX: &str = "PFN_"; const SHADER: &[&str] = &[ - "src/overlay/elements/pedals/single_color.vert", - "src/overlay/elements/pedals/single_color.frag", + "src/overlay/elements/radar/single_color.vert", + "src/overlay/elements/radar/single_color.frag", + "src/overlay/elements/pedals/history.vert", + "src/overlay/elements/pedals/history.frag", ]; fn query_vulkan_function_typedefs() { diff --git a/src/overlay/elements/leaderboard/leaderboard_grid.xml b/src/overlay/elements/leaderboard/leaderboard_grid.xml index 65e6394..0ad24dc 100644 --- a/src/overlay/elements/leaderboard/leaderboard_grid.xml +++ b/src/overlay/elements/leaderboard/leaderboard_grid.xml @@ -1,5 +1,5 @@ - \ No newline at end of file diff --git a/src/overlay/elements/mod.rs b/src/overlay/elements/mod.rs index bc1376d..df2b82b 100644 --- a/src/overlay/elements/mod.rs +++ b/src/overlay/elements/mod.rs @@ -7,3 +7,42 @@ pub use leaderboard::*; pub use pedals::*; pub use radar::*; pub use watermark::*; + +#[derive(Clone)] +pub struct PositionOnlyVertex { + pub position: cgmath::Vector4, +} + +impl PositionOnlyVertex { + /// + /// corners[0] - bottom left + /// corners[1] - top left + /// corners[2] - top right + /// corners[3] - bottom right + /// + pub fn from_2d_corners( + ortho: cgmath::Matrix4, + corners: [cgmath::Vector2; 4], + ) -> [Self; 6] { + [ + Self { + position: ortho * corners[0].extend(0.0).extend(1.0), + }, + Self { + position: ortho * corners[1].extend(0.0).extend(1.0), + }, + Self { + position: ortho * corners[2].extend(0.0).extend(1.0), + }, + Self { + position: ortho * corners[2].extend(0.0).extend(1.0), + }, + Self { + position: ortho * corners[3].extend(0.0).extend(1.0), + }, + Self { + position: ortho * corners[0].extend(0.0).extend(1.0), + }, + ] + } +} diff --git a/src/overlay/elements/pedals/history.frag b/src/overlay/elements/pedals/history.frag new file mode 100644 index 0000000..07bd8fa --- /dev/null +++ b/src/overlay/elements/pedals/history.frag @@ -0,0 +1,12 @@ +#version 450 + +layout (set = 0, binding = 0) uniform Color { + vec4 val; +} color; + +layout (location = 0) out vec4 out_color; + +void main() +{ + out_color = color.val; +} diff --git a/src/overlay/elements/pedals/history.vert b/src/overlay/elements/pedals/history.vert new file mode 100644 index 0000000..126ef86 --- /dev/null +++ b/src/overlay/elements/pedals/history.vert @@ -0,0 +1,8 @@ +#version 450 + +layout (location = 0) in vec4 position; + +void main() +{ + gl_Position = position; +} \ No newline at end of file diff --git a/src/overlay/elements/pedals/mod.rs b/src/overlay/elements/pedals/mod.rs index 51ccbb6..c76a07f 100644 --- a/src/overlay/elements/pedals/mod.rs +++ b/src/overlay/elements/pedals/mod.rs @@ -1,45 +1,230 @@ -use std::sync::Arc; +mod pipeline; + +use std::sync::{Arc, Mutex}; use anyhow::Result; +use cgmath::{ortho, vec4, Matrix4}; use rfactor_sm_reader::{rF2VehicleTelemetry, VehicleScoringInfoV01}; +use ringbuf::{HeapRb, Rb}; use ui::prelude::*; +use vulkan_rs::prelude::*; use crate::overlay::{ rfactor_data::{DataReceiver, GamePhase}, UiOverlay, }; +use crate::write_log; + +use self::pipeline::HistoryPipeline; + +use super::PositionOnlyVertex; pub struct Pedals { gui: Arc, brake: Arc, throttle: Arc, - history: Arc, + _history: Arc, - throttle_samples: Vec, - brake_samples: Vec, + throttle_samples: HeapRb, + brake_samples: HeapRb, + + ortho: Matrix4, + + device: Arc, + queue: Arc>, + + render_target: RenderTarget, + pipeline: HistoryPipeline, + + brake_descriptor: Arc, + brake_vertex_buffer: Arc>, + throttle_descriptor: Arc, + throttle_vertex_buffer: Arc>, } impl Pedals { - pub fn new(gui_handler: &Arc) -> Result { + pub fn new( + gui_handler: &Arc, + device: Arc, + queue: Arc>, + ) -> Result { const DESC: &str = include_str!("pedals.xml"); let gui = GuiBuilder::from_str(gui_handler, DESC)?; let brake = gui.element("brake")?; let throttle = gui.element("throttle")?; - let history = gui.element("history")?; + let history: Arc = gui.element("history")?; - Ok(Self { + let (icon_width, icon_height) = history.extent(); + let history_image = Image::empty( + icon_width as u32, + icon_height as u32, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_SAMPLE_COUNT_1_BIT, + ) + .format(VK_FORMAT_R8G8B8A8_UNORM) + .attach_sampler(Sampler::nearest_sampler().build(&device)?) + .build(&device, &queue)?; + + history_image.convert_layout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)?; + + history.set_icon(&history_image)?; + + let render_target = RenderTarget::builder() + .add_sub_pass( + SubPass::builder(history_image.width(), history_image.height()) + .set_prepared_targets(&[history_image.clone()], 0, [0.3, 0.3, 0.3, 1.0], true) + .build(&device, &queue)?, + ) + .build(&device)?; + + let pipeline = HistoryPipeline::new( + device.clone(), + render_target.render_pass(), + history_image.width(), + history_image.height(), + )?; + + let ortho = ortho(0.0, history_image.width() as f32, 0.0, 1.0, -1.0, 1.0); + + let descriptor_pool = DescriptorPool::builder() + .set_layout(pipeline.descriptor_layout().clone()) + .set_descriptor_set_count(2) + .build(device.clone())?; + + let brake_color_buffer: Arc> = Buffer::builder() + .set_usage(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) + .set_memory_usage(MemoryUsage::CpuOnly) + .set_data(&[0.9, 0.0, 0.0, 1.0]) + .build(device.clone())?; + + write_log!("allocate brake descriptor"); + + let brake_descriptor = descriptor_pool.prepare_set().allocate()?; + brake_descriptor.update(&[DescriptorWrite::uniform_buffers(0, &[&brake_color_buffer])])?; + + let throttle_color_buffer: Arc> = Buffer::builder() + .set_usage(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) + .set_memory_usage(MemoryUsage::CpuOnly) + .set_data(&[0.0, 0.9, 0.0, 1.0]) + .build(device.clone())?; + + write_log!("allocate throttle descriptor"); + + let throttle_descriptor = descriptor_pool.prepare_set().allocate()?; + throttle_descriptor.update(&[DescriptorWrite::uniform_buffers( + 0, + &[&throttle_color_buffer], + )])?; + + let mut throttle_samples = HeapRb::new(icon_width as usize); + let mut brake_samples = HeapRb::new(icon_width as usize); + + for _ in 0..icon_width { + throttle_samples.push_overwrite(0.0); + brake_samples.push_overwrite(0.0); + } + + let brake_vertex_buffer = Self::create_vertex_buffer(&device, icon_width as VkDeviceSize)?; + let throttle_vertex_buffer = + Self::create_vertex_buffer(&device, icon_width as VkDeviceSize)?; + + let me = Self { gui, brake, throttle, - history, + _history: history, - throttle_samples: Vec::new(), - brake_samples: Vec::new(), - }) + throttle_samples, + brake_samples, + + ortho, + + device, + queue, + + render_target, + pipeline, + + brake_descriptor, + brake_vertex_buffer, + throttle_descriptor, + throttle_vertex_buffer, + }; + + me.update_vertex_buffers()?; + + Ok(me) + } + + fn create_vertex_buffer( + device: &Arc, + size: VkDeviceSize, + ) -> Result>> { + Buffer::builder() + .set_usage(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) + .set_memory_usage(MemoryUsage::CpuOnly) + .set_size(size) + .build(device.clone()) + } + + fn update_vertex_buffers(&self) -> Result<()> { + self.update_vertex_buffer( + &self.throttle_vertex_buffer, + self.throttle_samples.as_slices(), + )?; + self.update_vertex_buffer(&self.brake_vertex_buffer, self.brake_samples.as_slices())?; + + Ok(()) + } + + fn update_vertex_buffer( + &self, + buffer: &Arc>, + (data1, data2): (&[f32], &[f32]), + ) -> Result<()> { + let points = data1 + .iter() + .chain(data2.iter()) + .enumerate() + .map(|(x, &date)| PositionOnlyVertex { + position: self.ortho * vec4(x as f32, date, 0.0, 1.0), + }) + .collect::>(); + + buffer.fill(&points) + } + + pub fn render(&self) -> Result> { + let command_buffer = + CommandBuffer::new_primary().build(self.device.clone(), self.queue.clone())?; + + { + let mut recorder = command_buffer.begin(VkCommandBufferBeginInfo::new( + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + | VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, + ))?; + + self.render_target + .begin(&recorder, VK_SUBPASS_CONTENTS_INLINE, 0); + + recorder.bind_pipeline(self.pipeline.pipeline())?; + + recorder.bind_descriptor_sets_minimal(&[&self.throttle_descriptor]); + recorder.bind_vertex_buffer(&self.throttle_vertex_buffer); + recorder.draw_complete_single_instance(self.throttle_vertex_buffer.size() as u32); + + recorder.bind_descriptor_sets_minimal(&[&self.brake_descriptor]); + recorder.bind_vertex_buffer(&self.brake_vertex_buffer); + recorder.draw_complete_single_instance(self.brake_vertex_buffer.size() as u32); + + self.render_target.end(&recorder); + } + + Ok(command_buffer) } } @@ -70,8 +255,10 @@ impl DataReceiver for Pedals { self.throttle.set_progress(throttle)?; self.brake.set_progress(brake)?; - self.throttle_samples.push(throttle); - self.brake_samples.push(brake); + self.throttle_samples.push_overwrite(throttle); + self.brake_samples.push_overwrite(brake); + + self.update_vertex_buffers()?; } } None => { @@ -82,3 +269,28 @@ impl DataReceiver for Pedals { Ok(()) } } + +#[cfg(test)] +mod test { + use ringbuf::{HeapRb, Rb}; + + #[test] + fn rb_test() { + const CAP: usize = 10; + + let mut buf = HeapRb::new(CAP); + + for _ in 0..CAP { + buf.push_overwrite(20); + } + + println!("{:?}", buf.as_slices()); + + buf.push_overwrite(40); + buf.push_overwrite(40); + buf.push_overwrite(40); + buf.push_overwrite(40); + + println!("{:?}", buf.as_slices()); + } +} diff --git a/src/overlay/elements/pedals/pedals.xml b/src/overlay/elements/pedals/pedals.xml index a05d5f7..feaf73c 100644 --- a/src/overlay/elements/pedals/pedals.xml +++ b/src/overlay/elements/pedals/pedals.xml @@ -1,6 +1,6 @@ - @@ -8,7 +8,7 @@ x_slot="1" y_slot="0" y_size="2" background="#494949" direction="bottom_to_top" foreground="#00b900"> - \ No newline at end of file diff --git a/src/overlay/elements/pedals/pipeline.rs b/src/overlay/elements/pedals/pipeline.rs new file mode 100644 index 0000000..49bfe86 --- /dev/null +++ b/src/overlay/elements/pedals/pipeline.rs @@ -0,0 +1,102 @@ +use anyhow::Result; +use vulkan_rs::prelude::*; + +use std::{mem, sync::Arc}; + +use super::super::PositionOnlyVertex; + +pub struct HistoryPipeline { + pipeline: Arc, + descriptor_layout: Arc, +} + +impl HistoryPipeline { + pub fn new( + device: Arc, + renderpass: &Arc, + width: u32, + height: u32, + ) -> Result { + let vertex_shader = ShaderModule::from_slice( + device.clone(), + include_bytes!("history.vert.spv"), + ShaderType::Vertex, + )?; + let fragment_shader = ShaderModule::from_slice( + device.clone(), + include_bytes!("history.frag.spv"), + ShaderType::Fragment, + )?; + + let descriptor_layout = DescriptorSetLayout::builder() + .add_layout_binding( + 0, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + VK_SHADER_STAGE_FRAGMENT_BIT, + 0, + ) + .build(device.clone())?; + + let pipeline_layout = PipelineLayout::builder() + .add_descriptor_set_layout(&descriptor_layout) + .build(device.clone())?; + + let viewport = VkViewport { + x: 0.0, + y: 0.0, + width: width as f32, + height: height as f32, + minDepth: 0.0, + maxDepth: 1.0, + }; + + let scissor = VkRect2D { + offset: VkOffset2D { x: 0, y: 0 }, + extent: VkExtent2D { + width: width, + height: height, + }, + }; + + let pipeline = Pipeline::new_graphics() + .set_vertex_shader( + vertex_shader.clone(), + vec![VkVertexInputBindingDescription { + binding: 0, + stride: mem::size_of::() as u32, + inputRate: VK_VERTEX_INPUT_RATE_VERTEX, + }], + vec![ + // position + VkVertexInputAttributeDescription { + location: 0, + binding: 0, + format: VK_FORMAT_R32G32B32A32_SFLOAT, + offset: 0, + }, + ], + ) + .set_fragment_shader(fragment_shader.clone()) + .input_assembly(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, false) + .default_depth_stencil(false, false) + .default_color_blend(vec![VkPipelineColorBlendAttachmentState::default()]) + .default_rasterization(VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE) + .default_multisample(VK_SAMPLE_COUNT_1_BIT) + .add_viewport(viewport) + .add_scissor(scissor) + .build(device, &pipeline_layout, &renderpass, 0)?; + + Ok(Self { + descriptor_layout, + pipeline, + }) + } + + pub fn pipeline(&self) -> &Arc { + &self.pipeline + } + + pub fn descriptor_layout(&self) -> &Arc { + &self.descriptor_layout + } +} diff --git a/src/overlay/elements/radar/mod.rs b/src/overlay/elements/radar/mod.rs index 7b2b0f2..fb94c36 100644 --- a/src/overlay/elements/radar/mod.rs +++ b/src/overlay/elements/radar/mod.rs @@ -1,7 +1,7 @@ mod pipeline; use anyhow::Result; -use cgmath::{ortho, vec2, vec3, vec4, Deg, InnerSpace, Matrix4, Rad, Vector2, Vector3, Vector4}; +use cgmath::{ortho, vec2, vec3, vec4, Deg, InnerSpace, Matrix4, Rad, Vector2, Vector3}; use rfactor_sm_reader::*; use serde::{Deserialize, Serialize}; use vulkan_rs::prelude::*; @@ -19,41 +19,7 @@ use crate::{ write_log, }; -#[derive(Clone)] -pub struct PositionOnlyVertex { - pub position: Vector4, -} - -impl PositionOnlyVertex { - /// - /// corners[0] - bottom left - /// corners[1] - top left - /// corners[2] - top right - /// corners[3] - bottom right - /// - pub fn from_2d_corners(ortho: Matrix4, corners: [Vector2; 4]) -> [Self; 6] { - [ - Self { - position: ortho * corners[0].extend(0.0).extend(1.0), - }, - Self { - position: ortho * corners[1].extend(0.0).extend(1.0), - }, - Self { - position: ortho * corners[2].extend(0.0).extend(1.0), - }, - Self { - position: ortho * corners[2].extend(0.0).extend(1.0), - }, - Self { - position: ortho * corners[3].extend(0.0).extend(1.0), - }, - Self { - position: ortho * corners[0].extend(0.0).extend(1.0), - }, - ] - } -} +use super::PositionOnlyVertex; fn convert_vec(v: rF2Vec3) -> Vector3 { vec3(v.x as f32, v.y as f32, v.z as f32) diff --git a/src/overlay/elements/watermark/mod.rs b/src/overlay/elements/watermark/mod.rs index 7eaec0b..093b89f 100644 --- a/src/overlay/elements/watermark/mod.rs +++ b/src/overlay/elements/watermark/mod.rs @@ -10,7 +10,7 @@ use crate::overlay::{ }; pub struct Watermark { - _gui: Arc, + gui: Arc, } impl Watermark { @@ -19,9 +19,7 @@ impl Watermark { let gui = GuiBuilder::from_str(gui_handler, DESC)?; - gui.enable()?; - - Ok(Self { _gui: gui }) + Ok(Self { gui }) } } @@ -30,9 +28,14 @@ impl UiOverlay for Watermark {} impl DataReceiver for Watermark { fn scoring_update( &mut self, - _phase: GamePhase, + phase: GamePhase, _vehicle_scoring: &[VehicleScoringInfoV01], ) -> Result<()> { + match phase { + GamePhase::TestDay => self.gui.enable()?, + _ => self.gui.disable()?, + } + Ok(()) } diff --git a/src/overlay/mod.rs b/src/overlay/mod.rs index 82e55ab..09a26ce 100644 --- a/src/overlay/mod.rs +++ b/src/overlay/mod.rs @@ -142,7 +142,7 @@ impl Overlay { // only font is used let mut create_info = GuiHandlerCreateInfo::default(); - create_info.font_path = AssetPath::from("/usr/share/vulkan_rf2_layer/font.png"); + create_info.font_path = AssetPath::from("/opt/sata_ssd/Workspace/vk_layer_rs/font.png"); create_info.font_path.assume_prefix_free(); // required to not crash @@ -196,8 +196,15 @@ impl Overlay { // create pedals if self.config.ui_config.enable_pedals { - let pedals = Rc::new(RefCell::new(Pedals::new(&gui_handler)?)); - self.ui_elements.push(pedals); + let pedals = Rc::new(RefCell::new(Pedals::new( + &gui_handler, + self.device(), + self.queue(), + )?)); + + self.ui_elements.push(pedals.clone()); + + rendering.add_render_callback(move |_| pedals.borrow().render()); write_log!("Pedals successfully created"); } diff --git a/src/overlay/rfactor_data.rs b/src/overlay/rfactor_data.rs index d65e62a..32fc85d 100644 --- a/src/overlay/rfactor_data.rs +++ b/src/overlay/rfactor_data.rs @@ -109,6 +109,8 @@ impl RFactorData { let phase = GamePhase::try_from(scoring_info.mSession)?; + write_log!(format!("GamePhase: {:?}", phase)); + for receiver in self.receivers.iter() { receiver .borrow_mut()