diff --git a/build.rs b/build.rs index d0fb369..d5ffc56 100644 --- a/build.rs +++ b/build.rs @@ -10,8 +10,8 @@ const VK_HEADER: &[&str] = &[ const FN_PREFIX: &str = "PFN_"; const SHADER: &[&str] = &[ - "src/overlay/shader/single_color.vert", - "src/overlay/shader/single_color.frag", + "src/overlay/elements/shader/single_color.vert", + "src/overlay/elements/shader/single_color.frag", ]; fn query_vulkan_function_typedefs() { diff --git a/src/overlay/elements/mod.rs b/src/overlay/elements/mod.rs index e69de29..038032f 100644 --- a/src/overlay/elements/mod.rs +++ b/src/overlay/elements/mod.rs @@ -0,0 +1,6 @@ +mod pedals; +mod pipeline; +mod radar; + +pub use pedals::*; +pub use radar::*; diff --git a/src/overlay/elements/pedals.rs b/src/overlay/elements/pedals.rs index e69de29..12f970a 100644 --- a/src/overlay/elements/pedals.rs +++ b/src/overlay/elements/pedals.rs @@ -0,0 +1,66 @@ +use std::sync::Arc; + +use anyhow::Result; +use rfactor_sm_reader::{rF2VehicleTelemetry, VehicleScoringInfoV01}; +use ui::prelude::*; + +use crate::overlay::{rfactor_data::DataReceiver, UiOverlay}; + +pub struct Pedals { + gui: Arc, + + brake: Arc, + throttle: Arc, +} + +impl Pedals { + pub fn new(gui_handler: &Arc) -> Result { + const DESC: &str = include_str!("ui_files/pedals.xml"); + + let gui = GuiBuilder::from_str(gui_handler, DESC)?; + + let brake = gui.element("brake")?; + let throttle = gui.element("throttle")?; + + Ok(Self { + gui, + brake, + throttle, + }) + } +} + +impl Drop for Pedals { + fn drop(&mut self) { + self.gui.disable().unwrap(); + } +} + +impl UiOverlay for Pedals { + fn enable_ui(&mut self) -> Result<()> { + self.gui.enable() + } +} + +impl DataReceiver for Pedals { + fn scoring_update(&mut self, _vehicle_scoring: &[VehicleScoringInfoV01]) -> Result<()> { + Ok(()) + } + + fn telemetry_update( + &mut self, + player_id: Option, + telemetries: &[rF2VehicleTelemetry], + ) -> Result<()> { + if let Some(id) = player_id { + if let Some(telemetry) = telemetries.iter().find(|telemetry| telemetry.id == id) { + self.brake + .set_progress(telemetry.unfiltered_throttle as f32)?; + self.throttle + .set_progress(telemetry.unfiltered_throttle as f32)?; + } + } + + Ok(()) + } +} diff --git a/src/overlay/pipeline.rs b/src/overlay/elements/pipeline.rs similarity index 78% rename from src/overlay/pipeline.rs rename to src/overlay/elements/pipeline.rs index fca50bf..ee729a2 100644 --- a/src/overlay/pipeline.rs +++ b/src/overlay/elements/pipeline.rs @@ -3,7 +3,7 @@ use vulkan_rs::prelude::*; use std::{mem, sync::Arc}; -use super::rendering::PositionOnlyVertex; +use super::radar::PositionOnlyVertex; pub struct SingleColorPipeline { pipeline: Arc, @@ -11,7 +11,12 @@ pub struct SingleColorPipeline { } impl SingleColorPipeline { - pub fn new(device: Arc, renderpass: &Arc) -> Result { + pub fn new( + device: Arc, + renderpass: &Arc, + width: u32, + height: u32, + ) -> Result { let vertex_shader = ShaderModule::from_slice( device.clone(), include_bytes!("shader/single_color.vert.spv"), @@ -36,6 +41,23 @@ impl SingleColorPipeline { .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(), @@ -60,6 +82,8 @@ impl SingleColorPipeline { .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 { diff --git a/src/overlay/elements/radar.rs b/src/overlay/elements/radar.rs index e69de29..e9216f4 100644 --- a/src/overlay/elements/radar.rs +++ b/src/overlay/elements/radar.rs @@ -0,0 +1,495 @@ +use anyhow::Result; +use cgmath::{ortho, vec2, vec3, vec4, Deg, InnerSpace, Matrix4, Rad, Vector2, Vector3, Vector4}; +use rfactor_sm_reader::*; +use serde::{Deserialize, Serialize}; +use vulkan_rs::prelude::*; + +use std::sync::{Arc, Mutex}; + +use super::pipeline::SingleColorPipeline; + +use crate::{ + overlay::{rendering::Rendering, rfactor_data::DataReceiver, UiOverlay}, + 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), + }, + ] + } +} + +fn convert_vec(v: rF2Vec3) -> Vector3 { + vec3(v.x as f32, v.y as f32, v.z as f32) +} + +#[derive(Deserialize, Serialize, Clone, Copy, Debug)] +pub struct RadarConfig { + pub radar_scale: f32, + pub radar_center_factor: f32, + pub radar_transparency: f32, + pub height_scale: f32, + pub width_scale: f32, + pub radar_car_distance: f32, + pub safe_color: Vector3, + pub danger_color: Vector3, +} + +impl RadarConfig { + pub const fn new() -> Self { + Self { + radar_scale: 1.0, + radar_center_factor: 0.25, + radar_transparency: 0.5, + height_scale: 0.15, + width_scale: 0.4, + radar_car_distance: 20.0, + safe_color: vec3(0.0, 0.75, 0.0), + danger_color: vec3(0.75, 0.0, 0.0), + } + } +} + +pub struct Radar { + // config + config: RadarConfig, + + // radar objects + background: Option, + player_car: RadarObject, + cars: Vec, + + // buffer car objects, to prevent recreating them every update + car_handles: Vec, + + // game info + player_id: Option, + + // math objects + radar_center: Vector2, + ortho: Matrix4, + _window_width: u32, + _window_height: u32, + radar_extent: f32, + car_width: f32, + car_height: f32, + + device: Arc, + queue: Arc>, + + pipeline: SingleColorPipeline, + render_target: RenderTarget, + + enabled: bool, +} + +impl Radar { + pub fn new( + config: RadarConfig, + device: Arc, + queue: Arc>, + rendering: &Rendering, + ) -> Result { + write_log!(" =================== create RFactorData ==================="); + + let radar_extent = rendering.swapchain().width() as f32 * 0.075 * config.radar_scale; + let car_height = radar_extent * config.height_scale; + let car_width = car_height * config.width_scale; + let radar_center = vec2( + rendering.swapchain().width() as f32 / 2.0, + rendering.swapchain().height() as f32 / 2.0 + - rendering.swapchain().height() as f32 * config.radar_center_factor, + ); + + let flip_y = matrix4_from_diagonal(vec3(1.0, -1.0, 1.0)); + let ortho = flip_y + * ortho( + 0.0, + rendering.swapchain().width() as f32, + 0.0, + rendering.swapchain().height() as f32, + -1.0, + 1.0, + ); + + let render_target = RenderTarget::builder() + .add_sub_pass( + SubPass::builder( + rendering.swapchain().width(), + rendering.swapchain().height(), + ) + .set_prepared_targets(&rendering.images(), 0, [0.0, 0.0, 0.0, 1.0], false) + .build(&device, &queue)?, + ) + .build(&device)?; + + let pipeline = SingleColorPipeline::new( + device.clone(), + render_target.render_pass(), + rendering.swapchain().width(), + rendering.swapchain().height(), + )?; + + Ok(Self { + config, + + background: if config.radar_transparency == 0.0 { + None + } else { + Some(RadarObject::new( + device.clone(), + pipeline.descriptor_layout(), + PositionOnlyVertex::from_2d_corners( + ortho * Matrix4::from_translation(radar_center.extend(0.0)), + [ + vec2(-radar_extent, -radar_extent), + vec2(-radar_extent, radar_extent), + vec2(radar_extent, radar_extent), + vec2(radar_extent, -radar_extent), + ], + ), + [0.5, 0.5, 0.5, config.radar_transparency], + )?) + }, + player_car: RadarObject::new( + device.clone(), + pipeline.descriptor_layout(), + PositionOnlyVertex::from_2d_corners( + ortho * Matrix4::from_translation(radar_center.extend(0.0)), + [ + vec2(-car_width, -car_height), + vec2(-car_width, car_height), + vec2(car_width, car_height), + vec2(car_width, -car_height), + ], + ), + [0.0, 0.9, 0.0, 0.9], + )?, + cars: Vec::new(), + car_handles: Vec::new(), + + player_id: None, + + radar_center, + ortho, + _window_width: rendering.swapchain().width(), + _window_height: rendering.swapchain().height(), + radar_extent, + car_width, + car_height, + + device, + queue, + + render_target, + pipeline, + + enabled: false, + }) + } + + fn create_car_object(&self, offset: Vector2, color: [f32; 4]) -> Result { + write_log!(" =================== create car object ==================="); + + RadarObject::new( + self.device.clone(), + &self.pipeline.descriptor_layout(), + Self::create_car_vertices( + self.ortho + * Matrix4::from_translation(self.radar_center.extend(0.0)) + * Matrix4::from_translation(offset.extend(0.0)), + self.car_width, + self.car_height, + ), + color, + ) + } + + fn create_car_vertices( + mvp: Matrix4, + car_width: f32, + car_height: f32, + ) -> [PositionOnlyVertex; 6] { + PositionOnlyVertex::from_2d_corners( + mvp, + [ + vec2(-car_width, -car_height), + vec2(-car_width, car_height), + vec2(car_width, car_height), + vec2(car_width, -car_height), + ], + ) + } + + pub fn render(&self, image_index: u32) -> 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, image_index as usize); + + recorder.bind_pipeline(self.pipeline.pipeline())?; + + for object in self.objects() { + let buffer = &object.position_buffer; + + recorder.bind_descriptor_sets_minimal(&[&object.descriptor_set]); + recorder.bind_vertex_buffer(buffer); + recorder.draw_complete_single_instance(buffer.size() as u32); + } + + self.render_target.end(&recorder); + } + + Ok(command_buffer) + } + + fn objects(&self) -> Vec<&RadarObject> { + write_log!(" =================== get objects of radar ==================="); + + let mut objects = Vec::new(); + + // only draw radar when player is loaded into a map + if let Some(_player_id) = &self.player_id { + // only draw radar when any car is near enough + if !self.cars.is_empty() { + if let Some(background) = &self.background { + objects.push(background); + } + + for other_player_cars in &self.cars { + objects.push(other_player_cars); + } + + objects.push(&self.player_car); + } + } + + objects + } +} + +impl UiOverlay for Radar { + fn enable_ui(&mut self) -> Result<()> { + self.enabled = true; + + Ok(()) + } +} + +impl DataReceiver for Radar { + fn scoring_update(&mut self, _vehicle_scoring: &[VehicleScoringInfoV01]) -> Result<()> { + Ok(()) + } + + fn telemetry_update( + &mut self, + player_id: Option, + telemetries: &[rF2VehicleTelemetry], + ) -> Result<()> { + self.cars.clear(); + + if !self.enabled { + return Ok(()); + } + + if let Some(player_id) = player_id { + // make sure there are enough cars in buffer + if self.car_handles.len() < telemetries.len() { + let size_diff = telemetries.len() - self.car_handles.len(); + + for _ in 0..size_diff { + self.car_handles + .push(self.create_car_object(vec2(0.0, 0.0), [0.0, 0.0, 0.0, 0.0])?); + } + } + + let mut player_position = CarPosition::default(); + let mut other_positions = Vec::new(); + + for telemetry in telemetries { + let car = CarPosition::new( + convert_vec(telemetry.position), + [ + convert_vec(telemetry.orientation[0]), + convert_vec(telemetry.orientation[1]), + convert_vec(telemetry.orientation[2]), + ], + ); + + if telemetry.id == player_id { + player_position = car + } else { + other_positions.push(car); + } + } + + // update radar objects + let mut buffer_car_index = 0; + + for other_position in other_positions { + let diff = player_position.position - other_position.position; + let distance = diff.magnitude(); + + // check if car is close enough to the players car + if distance < self.config.radar_car_distance { + let offset = diff.xz() * (self.radar_extent / self.config.radar_car_distance); + + let buffered_car = self.car_handles[buffer_car_index].clone(); + buffer_car_index += 1; + buffered_car.update( + self.ortho, + offset, + player_position.rotation, + other_position.rotation, + self.radar_center, + self.car_width, + self.car_height, + [0.9, 0.9, 0.0, 0.9], + )?; + + self.cars.push(buffered_car); + } + } + } + + Ok(()) + } +} + +#[derive(Clone)] +struct RadarObject { + descriptor_set: Arc, + + // uniform buffer + color_buffer: Arc>, + + // vertex buffer + position_buffer: Arc>, +} + +impl RadarObject { + fn new( + device: Arc, + descriptor_layout: &Arc, + positions: [PositionOnlyVertex; 6], + color: [f32; 4], + ) -> Result { + let color_buffer = Buffer::builder() + .set_usage(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) + .set_memory_usage(MemoryUsage::CpuOnly) + .set_data(&color) + .build(device.clone())?; + + let position_buffer = Buffer::builder() + .set_usage(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) + .set_memory_usage(MemoryUsage::CpuOnly) + .set_data(&positions) + .build(device.clone())?; + + let descriptor_pool = DescriptorPool::builder() + .set_layout(descriptor_layout.clone()) + .build(device.clone())?; + + let descriptor_set = descriptor_pool.prepare_set().allocate()?; + descriptor_set.update(&[DescriptorWrite::uniform_buffers(0, &[&color_buffer])])?; + + Ok(Self { + descriptor_set, + color_buffer, + position_buffer, + }) + } + + pub fn update( + &self, + + ortho: Matrix4, + offset: Vector2, + player_rotation: impl Into>, + rotation: impl Into>, + radar_center: Vector2, + car_width: f32, + car_height: f32, + color: [f32; 4], + ) -> Result<()> { + self.position_buffer.fill(&Radar::create_car_vertices( + ortho + * Matrix4::from_translation(radar_center.extend(0.0)) + * Matrix4::from_angle_z(-player_rotation.into()) + * Matrix4::from_translation(offset.extend(0.0)) + * Matrix4::from_angle_z(rotation.into()), + car_width, + car_height, + ))?; + self.color_buffer.fill(&color) + } +} + +struct CarPosition { + pub position: Vector3, + pub rotation: Rad, +} + +impl CarPosition { + fn new(position: Vector3, orientation: [Vector3; 3]) -> Self { + Self { + position, + rotation: Rad(orientation[2].x.atan2(orientation[2].z)), + } + } +} + +impl Default for CarPosition { + fn default() -> Self { + Self { + position: vec3(0.0, 0.0, 0.0), + rotation: Rad(0.0), + } + } +} + +const fn matrix4_from_diagonal(diagonal: Vector3) -> Matrix4 { + Matrix4::from_cols( + vec4(diagonal.x, 0.0, 0.0, 0.0), + vec4(0.0, diagonal.y, 0.0, 0.0), + vec4(0.0, 0.0, diagonal.z, 0.0), + vec4(0.0, 0.0, 0.0, 1.0), + ) +} diff --git a/src/overlay/shader/single_color.frag b/src/overlay/elements/shader/single_color.frag similarity index 100% rename from src/overlay/shader/single_color.frag rename to src/overlay/elements/shader/single_color.frag diff --git a/src/overlay/shader/single_color.vert b/src/overlay/elements/shader/single_color.vert similarity index 100% rename from src/overlay/shader/single_color.vert rename to src/overlay/elements/shader/single_color.vert diff --git a/src/overlay/elements/ui_files/gui.xsd b/src/overlay/elements/ui_files/gui.xsd new file mode 100644 index 0000000..7e0042c --- /dev/null +++ b/src/overlay/elements/ui_files/gui.xsd @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/overlay/elements/ui_files/pedals.xml b/src/overlay/elements/ui_files/pedals.xml new file mode 100644 index 0000000..b3d093b --- /dev/null +++ b/src/overlay/elements/ui_files/pedals.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/src/overlay/mod.rs b/src/overlay/mod.rs index 48460ba..aa90311 100644 --- a/src/overlay/mod.rs +++ b/src/overlay/mod.rs @@ -2,21 +2,31 @@ use crate::write_log; use self::{ rendering::Rendering, - rfactor_data::{RFactorData, RadarConfig}, + rfactor_data::{DataReceiver, RFactorData}, }; -mod pipeline; +mod elements; mod rendering; mod rfactor_data; use anyhow::Result; use assetpath::AssetPath; -use std::sync::{Arc, Mutex}; +use std::{ + cell::RefCell, + rc::Rc, + sync::{Arc, Mutex}, +}; use ui::prelude::*; use vulkan_rs::prelude::*; +use elements::*; + use serde::{Deserialize, Serialize}; +pub trait UiOverlay: DataReceiver { + fn enable_ui(&mut self) -> Result<()>; +} + #[derive(Deserialize, Serialize)] pub struct OverlayConfig { pub radar_config: RadarConfig, @@ -41,6 +51,8 @@ pub struct Overlay { rendering: Option, gui_handler: Option>, + ui_elements: Vec>>, + rfactor_data: Option, } @@ -54,6 +66,7 @@ impl Overlay { queue: None, rendering: None, gui_handler: None, + ui_elements: Vec::new(), rfactor_data: None, } @@ -104,13 +117,16 @@ impl Overlay { write_log!("-> create rendering: old cleared"); - let rendering = Rendering::new(self.device(), self.queue(), swapchain.clone())?; + let mut rendering = Rendering::new(self.queue(), swapchain.clone())?; + + write_log!("-> create rendering: new created"); // only font is used let mut create_info = GuiHandlerCreateInfo::default(); create_info.font_path = AssetPath::from(self.config.font_path.clone()); create_info.font_path.assume_prefix_free(); + // provide trait required by GuiHandler let ctx = Arc::new(ContextImpl::new( self.device(), self.queue(), @@ -118,38 +134,75 @@ impl Overlay { rendering.images().clone(), )); - self.gui_handler = Some(GuiHandler::new( - create_info, - &(ctx as Arc), - )?); + // create GuiHandler + let gui_handler = GuiHandler::new(create_info, &(ctx as Arc))?; - self.rendering = Some(rendering); + // create ui elements - write_log!("-> create rendering: new created"); + // create radar + let radar = Rc::new(RefCell::new(Radar::new( + self.config.radar_config, + self.device(), + self.queue(), + &rendering, + )?)); + + // create pedals + let pedals = Rc::new(RefCell::new(Pedals::new(&gui_handler)?)); + + // add rendering callbacks + rendering.add_render_callback({ + let radar = radar.clone(); + + move |index| radar.borrow().render(index) + }); + + rendering.add_render_callback({ + let gui_handler = gui_handler.clone(); + let device = self.device(); + let queue = self.queue(); + + move |index| { + let command_buffer = + CommandBuffer::new_primary().build(device.clone(), 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, + ))?; + + gui_handler.process(&mut recorder, &TargetMode::Mono(index as usize))?; + } + + Ok(command_buffer) + } + }); + + // add ui elements to local list + self.ui_elements.push(radar); + self.ui_elements.push(pedals); write_log!("-> create rendering: end"); + self.rendering = Some(rendering); + self.gui_handler = Some(gui_handler); + Ok(()) } pub fn render(&mut self) -> Result<()> { - let swapchain = self.rendering.as_ref().unwrap().swapchain().clone(); - if self.rfactor_data.is_none() { - self.rfactor_data = RFactorData::new( - self.config.radar_config, - self.device(), - self.rendering - .as_mut() - .unwrap() - .single_color_pipeline() - .descriptor_layout(), - swapchain.width(), - swapchain.height(), - ) - .ok(); + self.rfactor_data = RFactorData::new().ok(); - write_log!("created RFactorData"); + if let Some(data) = &mut self.rfactor_data { + write_log!("created RFactorData"); + + for receiver in self.ui_elements.iter() { + receiver.borrow_mut().enable_ui()?; + data.add_receiver(receiver.clone()); + } + } } // check twice for rfactor data, because of borrowing rules @@ -157,12 +210,7 @@ impl Overlay { rfactor.update()?; } - let objects = match &self.rfactor_data { - Some(rfactor) => rfactor.objects(), - None => Vec::new(), - }; - - self.rendering.as_mut().unwrap().render(swapchain, &objects) + self.rendering.as_ref().unwrap().render() } } diff --git a/src/overlay/rendering.rs b/src/overlay/rendering.rs index 1637652..ba16122 100644 --- a/src/overlay/rendering.rs +++ b/src/overlay/rendering.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use cgmath::{Matrix4, Vector2, Vector4}; use vulkan_rs::prelude::*; use std::{ @@ -7,61 +6,19 @@ use std::{ time::Duration, }; -use super::{pipeline::SingleColorPipeline, rfactor_data::RenderObject}; 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), - }, - ] - } -} - pub struct Rendering { swapchain: Arc, - pipeline: SingleColorPipeline, - render_target: RenderTarget, - command_buffer: Arc, images: Vec>, queue: Arc>, + + render_callbacks: Vec Result>>>, } impl Rendering { - pub fn new( - device: Arc, - queue: Arc>, - swapchain: Arc, - ) -> Result { + pub fn new(queue: Arc>, swapchain: Arc) -> Result { crate::write_log!("-> Rendering ctor: begin"); let vk_images = swapchain.vk_images()?; write_log!(format!( @@ -77,14 +34,6 @@ impl Rendering { }; write_log!("-> Rendering ctor: wrapped images"); - let render_target = RenderTarget::builder() - .add_sub_pass( - SubPass::builder(swapchain.width(), swapchain.height()) - .set_prepared_targets(&images, 0, [0.0, 0.0, 0.0, 1.0], false) - .build(&device, &queue)?, - ) - .build(&device)?; - write_log!("-> Rendering ctor: created render_target"); write_log!(format!( @@ -95,12 +44,11 @@ impl Rendering { Ok(Self { swapchain, - pipeline: SingleColorPipeline::new(device.clone(), render_target.render_pass())?, - render_target, - command_buffer: CommandBuffer::new_primary().build(device.clone(), queue.clone())?, images, queue, + + render_callbacks: Vec::new(), }) } @@ -108,62 +56,24 @@ impl Rendering { &self.swapchain } - pub fn single_color_pipeline(&self) -> &SingleColorPipeline { - &self.pipeline + pub fn add_render_callback(&mut self, f: F) + where + F: Fn(u32) -> Result> + 'static, + { + self.render_callbacks.push(Box::new(f)); } - pub fn render( - &mut self, - swapchain: Arc, - objects: &[&dyn RenderObject], - ) -> Result<()> { + pub fn render(&self) -> Result<()> { let image_index = self.swapchain.current_index(); - let viewport = [VkViewport { - x: 0.0, - y: 0.0, - width: swapchain.width() as f32, - height: swapchain.height() as f32, - minDepth: 0.0, - maxDepth: 1.0, - }]; - - let scissor = [VkRect2D { - offset: VkOffset2D { x: 0, y: 0 }, - extent: VkExtent2D { - width: swapchain.width(), - height: swapchain.height(), - }, - }]; - - { - let mut recorder = self.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, image_index as usize); - - recorder.bind_pipeline(self.pipeline.pipeline())?; - recorder.set_scissor(&scissor); - recorder.set_viewport(&viewport); - - write_log!(format!("-> Rendering {} objects", objects.len())); - - for object in objects { - let buffer = object.buffer(); - - recorder.bind_descriptor_sets_minimal(&[object.descriptor()]); - recorder.bind_vertex_buffer(buffer); - recorder.draw_complete_single_instance(buffer.size() as u32); - } - - self.render_target.end(&recorder); - } + let command_buffers: Vec> = self + .render_callbacks + .iter() + .map(|c| c(image_index)) + .collect::>>>()?; let queue = self.queue.lock().unwrap(); - queue.minimal_submit(Duration::from_secs(10), &[self.command_buffer.clone()])?; + queue.minimal_submit(Duration::from_secs(10), &command_buffers)?; Ok(()) } diff --git a/src/overlay/rfactor_data.rs b/src/overlay/rfactor_data.rs index 05835e7..dbb974d 100644 --- a/src/overlay/rfactor_data.rs +++ b/src/overlay/rfactor_data.rs @@ -1,196 +1,52 @@ use anyhow::Result; -use cgmath::{ortho, vec2, vec3, vec4, Deg, InnerSpace, Matrix4, Rad, Vector2, Vector3}; use rfactor_sm_reader::*; -use vulkan_rs::prelude::*; -use serde::{Deserialize, Serialize}; +use std::{cell::RefCell, rc::Rc, time::Instant}; -use std::{sync::Arc, time::Instant}; - -use super::rendering::PositionOnlyVertex; use crate::write_log; -fn convert_vec(v: rF2Vec3) -> Vector3 { - vec3(v.x as f32, v.y as f32, v.z as f32) -} +use super::UiOverlay; -pub trait RenderObject { - fn descriptor(&self) -> &Arc; - fn buffer(&self) -> &Arc>; -} +pub trait DataReceiver { + fn scoring_update(&mut self, vehicle_scoring: &[VehicleScoringInfoV01]) -> Result<()>; -#[derive(Deserialize, Serialize, Clone, Copy, Debug)] -pub struct RadarConfig { - pub radar_scale: f32, - pub radar_center_factor: f32, - pub radar_transparency: f32, - pub height_scale: f32, - pub width_scale: f32, - pub radar_car_distance: f32, - pub safe_color: Vector3, - pub danger_color: Vector3, -} - -impl RadarConfig { - pub const fn new() -> Self { - Self { - radar_scale: 1.0, - radar_center_factor: 0.25, - radar_transparency: 0.5, - height_scale: 0.15, - width_scale: 0.4, - radar_car_distance: 20.0, - safe_color: vec3(0.0, 0.75, 0.0), - danger_color: vec3(0.75, 0.0, 0.0), - } - } + fn telemetry_update( + &mut self, + player_id: Option, + telemetries: &[rF2VehicleTelemetry], + ) -> Result<()>; } pub struct RFactorData { - // config - config: RadarConfig, - // rf2 memory mapped data telemetry_reader: TelemetryReader, scoring_reader: ScoringReader, - // radar objects - background: Option, - player_car: RadarObject, - cars: Vec, - - // buffer car objects, to prevent recreating them every update - car_handles: Vec, - - // game info + start_time: Instant, player_id: Option, - // math objects - radar_center: Vector2, - ortho: Matrix4, - _window_width: u32, - _window_height: u32, - radar_extent: f32, - car_width: f32, - car_height: f32, - - start_time: Instant, - - device: Arc, - descriptor_layout: Arc, + receivers: Vec>>, } impl RFactorData { - pub fn new( - config: RadarConfig, - device: Arc, - descriptor_layout: &Arc, - width: u32, - height: u32, - ) -> Result { + pub fn new() -> Result { write_log!(" =================== create RFactorData ==================="); - let radar_extent = width as f32 * 0.075 * config.radar_scale; - let car_height = radar_extent * config.height_scale; - let car_width = car_height * config.width_scale; - let radar_center = vec2( - width as f32 / 2.0, - height as f32 / 2.0 - height as f32 * config.radar_center_factor, - ); - - let flip_y = matrix4_from_diagonal(vec3(1.0, -1.0, 1.0)); - let ortho = flip_y * ortho(0.0, width as f32, 0.0, height as f32, -1.0, 1.0); let start_time = Instant::now(); Ok(Self { - config, - telemetry_reader: TelemetryReader::new(start_time.elapsed().as_secs_f32())?, scoring_reader: ScoringReader::new(start_time.elapsed().as_secs_f32())?, - background: if config.radar_transparency == 0.0 { - None - } else { - Some(RadarObject::new( - device.clone(), - descriptor_layout, - PositionOnlyVertex::from_2d_corners( - ortho * Matrix4::from_translation(radar_center.extend(0.0)), - [ - vec2(-radar_extent, -radar_extent), - vec2(-radar_extent, radar_extent), - vec2(radar_extent, radar_extent), - vec2(radar_extent, -radar_extent), - ], - ), - [0.5, 0.5, 0.5, config.radar_transparency], - )?) - }, - player_car: RadarObject::new( - device.clone(), - descriptor_layout, - PositionOnlyVertex::from_2d_corners( - ortho * Matrix4::from_translation(radar_center.extend(0.0)), - [ - vec2(-car_width, -car_height), - vec2(-car_width, car_height), - vec2(car_width, car_height), - vec2(car_width, -car_height), - ], - ), - [0.0, 0.9, 0.0, 0.9], - )?, - cars: Vec::new(), - car_handles: Vec::new(), - + start_time, player_id: None, - radar_center, - ortho, - _window_width: width, - _window_height: height, - radar_extent, - car_width, - car_height, - - start_time, - - device, - descriptor_layout: descriptor_layout.clone(), + receivers: Vec::new(), }) } - fn create_car_object(&self, offset: Vector2, color: [f32; 4]) -> Result { - write_log!(" =================== create car object ==================="); - - RadarObject::new( - self.device.clone(), - &self.descriptor_layout, - Self::create_car_vertices( - self.ortho - * Matrix4::from_translation(self.radar_center.extend(0.0)) - * Matrix4::from_translation(offset.extend(0.0)), - self.car_width, - self.car_height, - ), - color, - ) - } - - fn create_car_vertices( - mvp: Matrix4, - car_width: f32, - car_height: f32, - ) -> [PositionOnlyVertex; 6] { - PositionOnlyVertex::from_2d_corners( - mvp, - [ - vec2(-car_width, -car_height), - vec2(-car_width, car_height), - vec2(car_width, car_height), - vec2(car_width, -car_height), - ], - ) + pub fn add_receiver(&mut self, receiver: Rc>) { + self.receivers.push(receiver); } fn now(&self) -> f32 { @@ -200,8 +56,6 @@ impl RFactorData { pub fn update(&mut self) -> Result<()> { write_log!(" =================== update RFactorData ==================="); - let mut should_render = false; - // get scoring info if let Some((scoring_info, vehicle_scorings)) = self.scoring_reader.vehicle_scoring(self.now()) @@ -224,223 +78,23 @@ impl RFactorData { } } - if let Some(id) = &self.player_id { - if let Some(vehicle_scoring) = - vehicle_scorings.iter().find(|scoring| scoring.mID == *id) - { - should_render = vehicle_scoring.mInPits != 0; - } + for receiver in self.receivers.iter() { + receiver.borrow_mut().scoring_update(&vehicle_scorings)?; } } - // if player id is set (a map is loaded), check telemetry data - if let Some(player_id) = &self.player_id { - write_log!("before telemetry update"); - if let Some(telemetries) = self.telemetry_reader.query_telemetry(self.now()) { - write_log!("new telemetry update"); + // check telemetry data + write_log!("before telemetry update"); + if let Some(telemetries) = self.telemetry_reader.query_telemetry(self.now()) { + write_log!("new telemetry update"); - self.cars.clear(); - - if should_render { - // make sure there are enough cars in buffer - if self.car_handles.len() < telemetries.len() { - let size_diff = telemetries.len() - self.car_handles.len(); - - for _ in 0..size_diff { - self.car_handles.push( - self.create_car_object(vec2(0.0, 0.0), [0.0, 0.0, 0.0, 0.0])?, - ); - } - } - - let mut player_position = CarPosition::default(); - let mut other_positions = Vec::new(); - - for telemetry in telemetries { - let car = CarPosition::new( - convert_vec(telemetry.position), - [ - convert_vec(telemetry.orientation[0]), - convert_vec(telemetry.orientation[1]), - convert_vec(telemetry.orientation[2]), - ], - ); - - if telemetry.id == *player_id { - player_position = car - } else { - other_positions.push(car); - } - } - - // update radar objects - let mut buffer_car_index = 0; - - for other_position in other_positions { - let diff = player_position.position - other_position.position; - let distance = diff.magnitude(); - - // check if car is close enough to the players car - if distance < self.config.radar_car_distance { - let offset = - diff.xz() * (self.radar_extent / self.config.radar_car_distance); - - let buffered_car = self.car_handles[buffer_car_index].clone(); - buffer_car_index += 1; - buffered_car.update( - self.ortho, - offset, - player_position.rotation, - other_position.rotation, - self.radar_center, - self.car_width, - self.car_height, - [0.9, 0.9, 0.0, 0.9], - )?; - - self.cars.push(buffered_car); - } - } - } + for receiver in self.receivers.iter() { + receiver + .borrow_mut() + .telemetry_update(self.player_id, &telemetries)?; } } Ok(()) } - - pub fn objects(&self) -> Vec<&dyn RenderObject> { - write_log!(" =================== get objects of RFactorData ==================="); - - let mut objects: Vec<&dyn RenderObject> = Vec::new(); - - // only draw radar when player is loaded into a map - if let Some(_player_id) = &self.player_id { - // only draw radar when any car is near enough - if !self.cars.is_empty() { - if let Some(background) = &self.background { - objects.push(background); - } - - for other_player_cars in &self.cars { - objects.push(other_player_cars); - } - - objects.push(&self.player_car); - } - } - - objects - } -} - -#[derive(Clone)] -struct RadarObject { - descriptor_set: Arc, - - // uniform buffer - color_buffer: Arc>, - - // vertex buffer - position_buffer: Arc>, -} - -impl RadarObject { - fn new( - device: Arc, - descriptor_layout: &Arc, - positions: [PositionOnlyVertex; 6], - color: [f32; 4], - ) -> Result { - let color_buffer = Buffer::builder() - .set_usage(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) - .set_memory_usage(MemoryUsage::CpuOnly) - .set_data(&color) - .build(device.clone())?; - - let position_buffer = Buffer::builder() - .set_usage(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) - .set_memory_usage(MemoryUsage::CpuOnly) - .set_data(&positions) - .build(device.clone())?; - - let descriptor_pool = DescriptorPool::builder() - .set_layout(descriptor_layout.clone()) - .build(device.clone())?; - - let descriptor_set = descriptor_pool.prepare_set().allocate()?; - descriptor_set.update(&[DescriptorWrite::uniform_buffers(0, &[&color_buffer])])?; - - Ok(Self { - descriptor_set, - color_buffer, - position_buffer, - }) - } - - pub fn update( - &self, - - ortho: Matrix4, - offset: Vector2, - player_rotation: impl Into>, - rotation: impl Into>, - radar_center: Vector2, - car_width: f32, - car_height: f32, - color: [f32; 4], - ) -> Result<()> { - self.position_buffer - .fill(&RFactorData::create_car_vertices( - ortho - * Matrix4::from_translation(radar_center.extend(0.0)) - * Matrix4::from_angle_z(-player_rotation.into()) - * Matrix4::from_translation(offset.extend(0.0)) - * Matrix4::from_angle_z(rotation.into()), - car_width, - car_height, - ))?; - self.color_buffer.fill(&color) - } -} - -impl RenderObject for RadarObject { - fn descriptor(&self) -> &Arc { - &self.descriptor_set - } - - fn buffer(&self) -> &Arc> { - &self.position_buffer - } -} - -struct CarPosition { - pub position: Vector3, - pub rotation: Rad, -} - -impl CarPosition { - fn new(position: Vector3, orientation: [Vector3; 3]) -> Self { - Self { - position, - rotation: Rad(orientation[2].x.atan2(orientation[2].z)), - } - } -} - -impl Default for CarPosition { - fn default() -> Self { - Self { - position: vec3(0.0, 0.0, 0.0), - rotation: Rad(0.0), - } - } -} - -const fn matrix4_from_diagonal(diagonal: Vector3) -> Matrix4 { - Matrix4::from_cols( - vec4(diagonal.x, 0.0, 0.0, 0.0), - vec4(0.0, diagonal.y, 0.0, 0.0), - vec4(0.0, 0.0, diagonal.z, 0.0), - vec4(0.0, 0.0, 0.0, 1.0), - ) }