From 25255a8b9147d27aa40b28d6aadb62c0ab275d32 Mon Sep 17 00:00:00 2001 From: Jon Santmyer Date: Wed, 22 Apr 2026 15:40:32 -0400 Subject: simplify rendering pipeline. add gridlines to tacmap --- assets/shaders/tacmap/grid.wgsl | 87 +++++++++++++++++++++++++++++++++++++++++ src/tacmap.rs | 31 +++++++++++---- src/tacmap/camera.rs | 6 +-- src/tacmap/render.rs | 70 +++++++++++++++++++++++---------- src/wgpuctx/mod.rs | 71 +++++++++++++++++++++++++++------ src/wgpuctx/pipeline.rs | 17 ++++++-- src/window.rs | 16 ++++---- 7 files changed, 242 insertions(+), 56 deletions(-) create mode 100644 assets/shaders/tacmap/grid.wgsl diff --git a/assets/shaders/tacmap/grid.wgsl b/assets/shaders/tacmap/grid.wgsl new file mode 100644 index 0000000..625dc7f --- /dev/null +++ b/assets/shaders/tacmap/grid.wgsl @@ -0,0 +1,87 @@ +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) grid_position: vec2, + @location(1) grid_scale: f32, +}; + +struct CameraUniform { + view: mat4x4, + proj: mat4x4, + scale: f32 +}; + +@group(0) @binding(0) +var camera: CameraUniform; + +const QUAD_VERTICES = array,6>( + vec3(-1.0, 0.0, -1.0), + vec3(-1.0, 0.0, 1.0), + vec3(1.0, 0.0, 1.0), + vec3(1.0, 0.0, 1.0), + vec3(1.0, 0.0, -1.0), + vec3(-1.0, 0.0, -1.0), +); + +@vertex +fn vs_main( + @builtin(vertex_index) index: u32 +) -> VertexOutput { + var out: VertexOutput; + + let model_pos = QUAD_VERTICES[index] * 50.0; + let world_pos = camera.proj * camera.view * vec4(model_pos.xyz, 1.0); + + out.clip_position = world_pos; + out.grid_position = vec2(model_pos.x, model_pos.z); + out.grid_scale = camera.scale * 1e6; + return out; +} + +fn modulus(a: f32, b: f32) -> f32 +{ return a - b * floor(a / b); } + +fn mod_vec2(a: vec2, b: vec2) -> vec2 +{ return a - b * floor(a / b); } + +const cell_line_thickness: f32 = 0.001; +const subcell_line_thickness: f32 = 0.0001; + +const cell_line_color: vec4 = vec4(0.25, 0.25, 0.25, 0.5); +const subcell_line_color: vec4 = vec4(0.125, 0.125, 0.125, 0.5); + +@fragment +fn fs_main(in: VertexOutput +) -> @location(0) vec4 { + + let scale_mod = modulus(log(1.0 / in.grid_scale) * 10.0, 10.0) + 1.0; + + let cell_size = 1.0 / scale_mod; + let half_cell_size = cell_size * 0.5; + let subcell_size = 0.1 / scale_mod; + let half_subcell_size = subcell_size * 0.5; + + //Position relative to the center of the quad. + let frag_pos_rel = (in.grid_position / 2.0); + + let cell_pos = mod_vec2(frag_pos_rel, vec2(cell_size)); + let subcell_pos = mod_vec2(frag_pos_rel, vec2(subcell_size)); + + let cell_dist = abs(cell_pos); + let subcell_dist = abs(subcell_pos); + + let d = fwidth(in.grid_position); + let clt_real = 0.5 * (cell_line_thickness + d); + let sclt_real = 0.5 * (subcell_line_thickness + d); + + let center_distance = length(in.grid_position); + let falloff = smoothstep(1.0, 0.0, center_distance / 2.0); + let subcell_falloff = smoothstep(1.0, 0.5, scale_mod / 11.0); + + var color = vec4(0.0); + if subcell_dist.x < sclt_real.x || subcell_dist.y < sclt_real.y + { color = subcell_line_color * subcell_falloff; } + if cell_dist.x < clt_real.x || cell_dist.y < clt_real.y + { color = cell_line_color; } + + return color * falloff; +} diff --git a/src/tacmap.rs b/src/tacmap.rs index e4cb76a..abae50e 100644 --- a/src/tacmap.rs +++ b/src/tacmap.rs @@ -17,6 +17,8 @@ use crate::canvas::Canvas; use crate::solar_system::SolarSystem; use crate::solar_system::SystemId; use crate::ui; +use crate::wgpuctx::RenderPassBuilder; +use crate::wgpuctx::SceneCtx; use crate::wgpuctx::WgpuCtx; use render::*; use camera::*; @@ -28,7 +30,8 @@ pub struct TacticalMap pmatrix: Projection, camera_controller: CameraController, - renderstate: Option<(SystemId, BodyRenderer)> + body_renderer: Option<(SystemId, BodyRenderer)>, + grid_renderer: GridRenderer } impl TacticalMap @@ -66,7 +69,8 @@ impl TacticalMap camera: camera, pmatrix: projection, camera_controller: CameraController::new(), - renderstate: None, + body_renderer: None, + grid_renderer: GridRenderer::new(wgpuctx) } } @@ -121,7 +125,7 @@ impl TacticalMap self.camera.update_buffer(wgpuctx, &self.pmatrix); - let tacrender = match &mut self.renderstate { + let body_renderer = match &mut self.body_renderer { Some((id, render)) => { if *id != current_system_id { *id = current_system_id; @@ -131,14 +135,14 @@ impl TacticalMap }, None => { let tmp = render::BodyRenderer::new(wgpuctx); - self.renderstate = Some((current_system_id, tmp)); - &mut self.renderstate.as_mut().unwrap().1 + self.body_renderer = Some((current_system_id, tmp)); + &mut self.body_renderer.as_mut().unwrap().1 } }; //Update buffers for the current system and time. - tacrender.rebuild(wgpuctx, current_system); - match tacrender.update( + body_renderer.rebuild(wgpuctx, current_system); + match body_renderer.update( wgpuctx, current_system, &self.camera, game_state.timeman().seconds()) { @@ -146,8 +150,19 @@ impl TacticalMap Err(e) => println!("Tactical map render update error: {}", e) } - tacrender.render(wgpuctx, &self.canvas, &self.camera); + let mut scene = SceneCtx::from_view_default(wgpuctx, self.canvas.view(), Some("Tactical map scene")); + let pass_builder = RenderPassBuilder::new(Some("Tactical map render pass"), scene.view()) + .clear_color(wgpu::Color { r: 0.0, g: 0.0, b: 0.1, a: 1.0 }); + self.camera.stage_changes(scene.encoder_mut()); + { + let mut pass = pass_builder.build_from_scene(&mut scene); + + self.grid_renderer.render(wgpuctx, &mut pass, &self.camera); + body_renderer.render(wgpuctx, &mut pass, &self.camera); + } + + scene.submit(wgpuctx); Ok(()) } diff --git a/src/tacmap/camera.rs b/src/tacmap/camera.rs index c573cfb..895fa1e 100644 --- a/src/tacmap/camera.rs +++ b/src/tacmap/camera.rs @@ -253,7 +253,7 @@ impl CameraController &mut self, camera: &mut Camera, target: Vector3, - min_radius: f32, + target_radius: f32, dt: Duration) { camera.abs_position = target; @@ -278,7 +278,7 @@ impl CameraController let (az_sin, az_cos) = camera.yaw.0.sin_cos(); let (p_sin, p_cos) = camera.pitch.0.sin_cos(); - let radius = f32::max(min_radius * camera.scale, current_radius + dist_diff); + let radius = f32::max(target_radius * camera.scale, current_radius + dist_diff).max(0.1); camera.rel_position = Vector3::new( radius * p_cos * az_cos, radius * p_sin, @@ -333,7 +333,7 @@ impl CameraController self.update_orbit( camera, current_system.body_position(body).cast().unwrap(), - body.radius() * 2.0, + (body.radius() * 2.0).max(1.0), dt); } None => self.update_pan(camera, dt) diff --git a/src/tacmap/render.rs b/src/tacmap/render.rs index 26d6977..c1256ff 100644 --- a/src/tacmap/render.rs +++ b/src/tacmap/render.rs @@ -1,10 +1,12 @@ use std::{fmt::Display, num::NonZero}; use std::error::Error; +use wgpu::RenderPass; + use crate::canvas::Canvas; use crate::solar_system::Kilometers; use crate::tacmap::camera::Camera; -use crate::wgpuctx::RenderPassBuilder; +use crate::wgpuctx::{RenderPassBuilder, SceneCtx}; use crate::{solar_system::{SolarSystem, SystemId}, timeman::Second, vertex::{self, Vertex}, wgpuctx::{WgpuCtx, pipeline::RenderPipelineBuilder}}; struct BodyInstance @@ -161,7 +163,7 @@ impl BodyRenderer pub fn render( &self, wgpuctx: &WgpuCtx, - canvas: &Canvas, + pass: &mut RenderPass, camera: &Camera) { let (num_bodies, bodies_buffer) = match &self.body_instance_buffer { @@ -169,27 +171,13 @@ impl BodyRenderer None => return }; - let mut encoder = wgpuctx.create_default_encoder("Tactical map renderer encoder"); - let view = canvas.view(); - - { - camera.stage_changes(&mut encoder); - } - { - let mut pass = RenderPassBuilder::new("Tactiacal map render pass", view) - .clear_color(wgpu::Color { r: 0.0, g: 0.0, b: 0.1, a: 1.0 }) - .build(&mut encoder); - - pass.set_pipeline(&self.pipeline); - pass.set_bind_group(0, camera.bindgroup(), &[]); + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, camera.bindgroup(), &[]); - pass.set_vertex_buffer(0, self.body_vertex_buffer.slice(..)); - pass.set_vertex_buffer(1, bodies_buffer.slice(..)); + pass.set_vertex_buffer(0, self.body_vertex_buffer.slice(..)); + pass.set_vertex_buffer(1, bodies_buffer.slice(..)); - pass.draw(0..6, 0..num_bodies.clone() as _); - } - - wgpuctx.submit_encoder(encoder); + pass.draw(0..6, 0..num_bodies.clone() as _); } } // impl RenderState @@ -229,3 +217,43 @@ impl BodyInstanceRaw } } } + +pub struct GridRenderer +{ + pipeline: wgpu::RenderPipeline, +} + +impl GridRenderer +{ + pub fn new(wgpuctx: &WgpuCtx) + -> Self + { + let shader = wgpuctx.create_shader( + wgpu::include_wgsl!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/assets/shaders/tacmap/grid.wgsl") + )); + + let render_pipeline = RenderPipelineBuilder::new(&shader) + .add_bindgroup(&Camera::bindgroup_layout(wgpuctx)) + .cull_mode(None) + .build(Some("Tactical map grid pipeline"), wgpuctx); + + Self { + pipeline: render_pipeline + } + } + + pub fn render( + &self, + wgpuctx: &WgpuCtx, + pass: &mut RenderPass, + camera: &Camera + ) + { + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, camera.bindgroup(), &[]); + + pass.draw(0..6, 0..1); + } +} diff --git a/src/wgpuctx/mod.rs b/src/wgpuctx/mod.rs index f8aa3ec..5f26a99 100644 --- a/src/wgpuctx/mod.rs +++ b/src/wgpuctx/mod.rs @@ -19,11 +19,10 @@ pub struct WgpuCtx queue: wgpu::Queue, } -pub struct RenderPassBuilder<'encoder> +pub struct SceneCtx<'view> { - label: &'static str, - view: &'encoder wgpu::TextureView, - clear_color: wgpu::Color + view: &'view wgpu::TextureView, + encoder: wgpu::CommandEncoder } impl WgpuCtx @@ -127,11 +126,11 @@ impl WgpuCtx pub fn create_default_encoder( &self, - label: &'static str) + label: Option<&'static str>) -> wgpu::CommandEncoder { self.create_encoder( &wgpu::CommandEncoderDescriptor { - label: Some(label) + label: label } ) } @@ -198,16 +197,53 @@ impl WgpuCtx } //impl WgpuCtx +impl<'view> SceneCtx<'view> +{ + pub fn from_view_default( + wgpuctx: &WgpuCtx, + view: &'view wgpu::TextureView, + label: Option<&'static str>) + -> Self { + Self { + view: view, + encoder: wgpuctx.create_default_encoder(label) + } + } + + pub fn view(&self) -> &'view wgpu::TextureView + { self.view } + + pub fn encoder(&self) -> &wgpu::CommandEncoder + { &self.encoder } + + pub fn encoder_mut(&mut self) -> &mut wgpu::CommandEncoder + { &mut self.encoder } + + pub fn submit( + self, + wgpuctx: &WgpuCtx) + { + wgpuctx.submit_encoder(self.encoder); + } +} + +pub struct RenderPassBuilder<'encoder> +{ + label: Option<&'static str>, + view: &'encoder wgpu::TextureView, + clear_color: Option +} + impl<'encoder> RenderPassBuilder<'encoder> { pub fn new( - label: &'static str, + label: Option<&'static str>, view: &'encoder wgpu::TextureView) -> Self { Self { label: label, view: view, - clear_color: wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 } + clear_color: None } } @@ -215,24 +251,27 @@ impl<'encoder> RenderPassBuilder<'encoder> mut self, color: wgpu::Color) -> Self { - self.clear_color = color; + self.clear_color = Some(color); self } - pub fn build( + pub fn build_from_encoder( self, encoder: &'encoder mut wgpu::CommandEncoder) -> wgpu::RenderPass<'encoder> { encoder.begin_render_pass( &wgpu::RenderPassDescriptor { - label: Some(self.label), + label: self.label, color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: self.view, resolve_target: None, depth_slice: None, ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(self.clear_color), + load: match self.clear_color { + Some(color) => wgpu::LoadOp::Clear(color), + None => wgpu::LoadOp::Load + }, store: wgpu::StoreOp::Store } })], @@ -242,4 +281,10 @@ impl<'encoder> RenderPassBuilder<'encoder> } ) } -} + + pub fn build_from_scene( + self, + scene: &'encoder mut SceneCtx) + -> wgpu::RenderPass<'encoder> + { self.build_from_encoder(scene.encoder_mut()) } +}// impl RenderPassBuilder diff --git a/src/wgpuctx/pipeline.rs b/src/wgpuctx/pipeline.rs index 7606203..0811800 100644 --- a/src/wgpuctx/pipeline.rs +++ b/src/wgpuctx/pipeline.rs @@ -12,7 +12,9 @@ pub struct RenderPipelineBuilder<'a> vertex_comp_options: Option>, fragment_comp_options: Option>, - vertex_buffer_layouts: Vec> + vertex_buffer_layouts: Vec>, + + cull_mode: Option } impl<'a> RenderPipelineBuilder<'a> @@ -30,7 +32,9 @@ impl<'a> RenderPipelineBuilder<'a> vertex_comp_options: None, fragment_comp_options: None, - vertex_buffer_layouts: Vec::new() + vertex_buffer_layouts: Vec::new(), + + cull_mode: Some(wgpu::Face::Back) } } @@ -50,6 +54,13 @@ impl<'a> RenderPipelineBuilder<'a> self } + pub fn cull_mode( + mut self, + mode: Option + ) -> Self { + self.cull_mode = mode; self + } + pub fn build( self, label: Option<&'static str>, @@ -101,7 +112,7 @@ impl<'a> RenderPipelineBuilder<'a> topology: wgpu::PrimitiveTopology::TriangleList, strip_index_format: None, front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), + cull_mode: self.cull_mode, polygon_mode: wgpu::PolygonMode::Fill, unclipped_depth: false, conservative: false diff --git a/src/window.rs b/src/window.rs index 28c69f8..3b090ff 100644 --- a/src/window.rs +++ b/src/window.rs @@ -8,7 +8,7 @@ use winit::keyboard::KeyCode; use crate::tacmap::TacticalMap; use crate::{GameState, SystemicApp, ui}; use crate::solar_system::{SolarSystem, SystemId}; -use crate::wgpuctx::{RenderPassBuilder, WgpuCtx}; +use crate::wgpuctx::{RenderPassBuilder, SceneCtx, WgpuCtx}; use crate::eguictx::EguiCtx; pub struct GameWindow @@ -58,7 +58,7 @@ impl GameWindow eguictx: eguictx, tactical_map: tacmap, - ui_state: Default::default() + ui_state: ui_state }) } @@ -100,12 +100,12 @@ impl GameWindow game_state, self.ui_state.current_system)?; - let mut encoder = self.wgpuctx.create_default_encoder("Systemic window command encoder"); let view = self.wgpuctx.prepare_surface(&wgpu::TextureViewDescriptor::default())?; + let mut scene = SceneCtx::from_view_default(&self.wgpuctx, &view, Some("Systemic window scene")); { - let mut pass = RenderPassBuilder::new("Systemic window render pass", &view) - .clear_color(wgpu::Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }) - .build(&mut encoder); + let mut pass = RenderPassBuilder::new(Some("Systemic window render pass"), &view) + .clear_color(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }) + .build_from_scene(&mut scene); //Draw the tactical map canvas. self.tactical_map.present(&mut pass)?; @@ -117,11 +117,11 @@ impl GameWindow self.eguictx.present( &self.window, &self.wgpuctx, - &mut encoder, + scene.encoder_mut(), &view); } - self.wgpuctx.submit_encoder(encoder); + scene.submit(&self.wgpuctx); self.wgpuctx.present_surface(); self.window.request_redraw(); -- cgit v1.2.3