From 6712a7fccb989754333edc97d8d8ce6106edbc2b Mon Sep 17 00:00:00 2001 From: John Doty Date: Thu, 24 Aug 2023 22:46:35 -0700 Subject: [PATCH 1/4] [oden] Textures are optional with no bind group --- src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dc81230a..afe285da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -598,14 +598,14 @@ impl DrawCall { pub fn draw<'a>(&self, state: &'a State, pass: &mut wgpu::RenderPass<'a>) { if self.draw_end > self.draw_start { - let texture_id = match self.texture_id { - Some(id) => id, - None => return, + match self.texture_id { + Some(id) => { + let bind_group = state.sprite_textures.get(&id).unwrap(); + pass.set_bind_group(0, bind_group, &[]); + } + None => (), }; - let bind_group = state.sprite_textures.get(&texture_id).unwrap(); - pass.set_bind_group(0, bind_group, &[]); - let vb = state.get_sprite_instance_buffer(&self.vertex_buffer); pass.set_bind_group(1, &state.screen_uniform_bind_group, &[]); pass.set_vertex_buffer(0, state.sprite_vertex_buffer.slice(..)); From 0296db31066bd8432e70c34f666f760cc0ddcc41 Mon Sep 17 00:00:00 2001 From: John Doty Date: Fri, 25 Aug 2023 14:07:18 -0700 Subject: [PATCH 2/4] [oden] Initial ground work for circles --- src/circle_shader.wgsl | 110 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 113 +++++++++++++++++++++++++++++++++++++++-- src/sprite_shader.wgsl | 62 +++++++++++++--------- 3 files changed, 259 insertions(+), 26 deletions(-) create mode 100644 src/circle_shader.wgsl diff --git a/src/circle_shader.wgsl b/src/circle_shader.wgsl new file mode 100644 index 00000000..b267b4ac --- /dev/null +++ b/src/circle_shader.wgsl @@ -0,0 +1,110 @@ +// ---------------------------------------------------------------------------- +// Vertex shader +// ---------------------------------------------------------------------------- + +struct VertexInput { + @location(0) position : vec3, + @location(1) tex_coords : vec2, +}; + +struct InstanceInput { + @location(5) center: vec2, + @location(6) radius: f32, + @location(7) stroke_width: f32, + @location(8) stroke_color: vec4, + @location(9) fill_color: vec4, +}; + +struct VertexOutput { + @builtin(position) clip_position : vec4, + @location(0) tex_coords : vec2, + @location(1) inner_r2: f32, + @location(2) stroke_color: vec4, + @location(3) fill_color: vec4, +}; + +@vertex fn vs_main(vertex : VertexInput, instance : InstanceInput)->VertexOutput { + var out : VertexOutput; + out.stroke_color = instance.stroke_color; + out.fill_color = instance.fill_color; + + // The circle's coordinate system goes from (-1,-1) to (1,1) but by + // convention we provide ourselves texture coordinates that go from (0,0) + // to (1,1). + out.tex_coords = mix(vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vertex.tex_coords); + + // Compute the squared radius of the inner circle, so we don't do it + // per-pixel. + // + // The radius of the inner circle goes from 0 (no inner circle) to 1 (no + // stroke), because the radius of the outer circle is implicitly 1 (the + // circle in the square we're rendering. + // + // (Honestly I don't even need to do this per-vertex, this is per-instance, + // I can pre-calculate this if I need this to be faster somehow.) + let delta = instance.radius - instance.stroke_width; //, 0, instance.radius); + let inner_radius = delta / instance.radius; + out.inner_r2 = inner_radius * inner_radius; + + let radius = vec2f(instance.radius, instance.radius); + let in_pos = instance.center + mix(-radius, radius, vec2f(vertex.position.x, vertex.position.y)); + + let position = adjust_for_resolution(in_pos); + out.clip_position = vec4f(position.x, position.y, vertex.position.z, 1.0); + return out; +} + +// ---------------------------------------------------------------------------- +// Fragment shader +// ---------------------------------------------------------------------------- + +@fragment fn fs_main(in : VertexOutput)->@location(0) vec4 { + let tc2 = in.tex_coords * in.tex_coords; + if (tc2.x + tc2.y <= in.inner_r2) { + return in.fill_color; + } else if (tc2.x + tc2.y <= 1.0) { + return in.stroke_color; + } else { + return vec4(0.0, 0.0, 0.0, 1.0); + } +} + +// ---------------------------------------------------------------------------- +// Resolution Handling +// ---------------------------------------------------------------------------- + +struct ScreenUniform { + resolution : vec2f, +}; +@group(1) @binding(0) // 1. + var screen : ScreenUniform; + +const RES = vec2f(320.0, 240.0); // The logical resolution of the screen. + +fn adjust_for_resolution(in_pos: vec2) -> vec2 { + // Adjust in_pos for the "resolution" of the screen. + let RES_AR = RES.x / RES.y; // The aspect ratio of the logical screen. + + // the actual resolution of the screen. + let screen_ar = screen.resolution.x / screen.resolution.y; + + // Compute the difference in resolution ... correctly? + // + // nudge is the amount to add to the logical resolution so that the pixels + // stay the same size but we respect the aspect ratio of the screen. (So + // there's more of them in either the x or y direction.) + var nudge = vec2f(0.0); + if (screen_ar > RES_AR) { + nudge.x = (RES.y * screen_ar) - RES.x; + } else { + nudge.y = (RES.x / screen_ar) - RES.y; + } + var new_logical_resolution = RES + nudge; + + // Now we can convert the incoming position to clip space, in the new screen. + let centered = in_pos + (nudge / 2.0); + var position = (2.0 * centered / new_logical_resolution) - 1.0; + position.y = -position.y; + + return position; +} diff --git a/src/lib.rs b/src/lib.rs index afe285da..bdf3b59c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,60 @@ impl SpriteInstance { } } +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +struct CircleInstance { + center: [f32; 2], + radius: f32, + stroke_width: f32, + stroke_color: [f32; 4], + fill_color: [f32; 4], +} + +impl CircleInstance { + fn desc() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 5, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, + shader_location: 6, + format: wgpu::VertexFormat::Float32, + }, + wgpu::VertexAttribute { + offset: (std::mem::size_of::<[f32; 2]>() + std::mem::size_of::()) + as wgpu::BufferAddress, + shader_location: 7, + format: wgpu::VertexFormat::Float32, + }, + wgpu::VertexAttribute { + offset: (std::mem::size_of::<[f32; 2]>() + + std::mem::size_of::() + + std::mem::size_of::()) + as wgpu::BufferAddress, + shader_location: 8, + format: wgpu::VertexFormat::Float32x4, + }, + wgpu::VertexAttribute { + offset: (std::mem::size_of::<[f32; 2]>() + + std::mem::size_of::() + + std::mem::size_of::() + + std::mem::size_of::<[f32; 4]>()) + as wgpu::BufferAddress, + shader_location: 9, + format: wgpu::VertexFormat::Float32x4, + }, + ], + } + } +} + #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] struct ScreenUniforms { @@ -208,15 +262,20 @@ struct State { config: wgpu::SurfaceConfiguration, size: winit::dpi::PhysicalSize, window: Window, - render_pipeline: wgpu::RenderPipeline, sprite_vertex_buffer: wgpu::Buffer, + + sprite_pipeline: wgpu::RenderPipeline, sprite_instance_buffers: Vec>, next_free_sprite_instance_buffer: usize, sprite_bind_group_layout: wgpu::BindGroupLayout, sprite_textures: HashMap, + circle_pipeline: wgpu::RenderPipeline, + circle_instance_buffers: Vec>, + next_free_circle_instance_buffer: usize, + write_textures: HashMap, screen_uniform: ScreenUniforms, @@ -366,6 +425,49 @@ impl State { multiview: None, }); + let circle_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Circle Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("circle_shader.wgsl").into()), + }); + + let circle_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Circle Pipeline"), + layout: Some(&sprite_pipeline_layout), + vertex: wgpu::VertexState { + module: &circle_shader, + entry_point: "vs_main", + buffers: &[Vertex::desc(), CircleInstance::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &circle_shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }); + let sprite_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Sprite Vertex Buffer"), contents: bytemuck::cast_slice(SPRITE_VERTICES), @@ -379,7 +481,7 @@ impl State { queue: hardware.queue, config, size, - render_pipeline: sprite_pipeline, + sprite_pipeline, sprite_vertex_buffer, sprite_instance_buffers: Vec::new(), @@ -387,6 +489,11 @@ impl State { sprite_bind_group_layout, sprite_textures: HashMap::new(), + + circle_pipeline, + circle_instance_buffers: Vec::new(), + next_free_circle_instance_buffer: 0, + write_textures: HashMap::new(), screen_uniform, screen_uniform_buffer, @@ -809,7 +916,7 @@ impl<'a> FrameBuilder<'a> { depth_stencil_attachment: None, }); - pass.set_pipeline(&self.state.render_pipeline); + pass.set_pipeline(&self.state.sprite_pipeline); for call in &self.draw_calls { call.draw(&self.state, &mut pass); } diff --git a/src/sprite_shader.wgsl b/src/sprite_shader.wgsl index 267aeb3c..20387c57 100644 --- a/src/sprite_shader.wgsl +++ b/src/sprite_shader.wgsl @@ -1,10 +1,6 @@ +// ---------------------------------------------------------------------------- // Vertex shader - -struct ScreenUniform { - resolution : vec2f, -}; -@group(1) @binding(0) // 1. - var screen : ScreenUniform; +// ---------------------------------------------------------------------------- struct VertexInput { @location(0) position : vec3, @@ -23,12 +19,44 @@ struct VertexOutput { @location(0) tex_coords : vec2, }; -const RES = vec2f(320.0, 240.0); // The logical resolution of the screen. - @vertex fn vs_main(vertex : VertexInput, instance : InstanceInput)->VertexOutput { var out : VertexOutput; out.tex_coords = instance.src_top_left + (vertex.tex_coords * instance.src_dims); + let in_pos = instance.dest_top_left + (vec2f(vertex.position.x, vertex.position.y) * instance.dest_dims); + + let position = adjust_for_resolution(in_pos); + out.clip_position = vec4f(position.x, position.y, vertex.position.z, 1.0); + return out; +} + +// ---------------------------------------------------------------------------- +// Fragment shader +// ---------------------------------------------------------------------------- + +@group(0) @binding(0) var t_diffuse : texture_2d; +@group(0) @binding(1) var s_diffuse : sampler; + +@fragment fn fs_main(in : VertexOutput)->@location(0) vec4 { + let tc = vec2(u32(in.tex_coords.x), u32(in.tex_coords.y)); + return textureLoad(t_diffuse, tc, 0); +} + + +// ---------------------------------------------------------------------------- +// Resolution Handling +// ---------------------------------------------------------------------------- + +struct ScreenUniform { + resolution : vec2f, +}; +@group(1) @binding(0) // 1. + var screen : ScreenUniform; + +const RES = vec2f(320.0, 240.0); // The logical resolution of the screen. + +fn adjust_for_resolution(in_pos: vec2) -> vec2 { + // Adjust in_pos for the "resolution" of the screen. let RES_AR = RES.x / RES.y; // The aspect ratio of the logical screen. // the actual resolution of the screen. @@ -47,22 +75,10 @@ const RES = vec2f(320.0, 240.0); // The logical resolution of the screen. } var new_logical_resolution = RES + nudge; - // Now we can convert the incoming position to clip space, in the new screen. - let in_pos = instance.dest_top_left + (vec2f(vertex.position.x, vertex.position.y) * instance.dest_dims); + // Now we can convert the incoming position to clip space, in the new screen. let centered = in_pos + (nudge / 2.0); var position = (2.0 * centered / new_logical_resolution) - 1.0; position.y = -position.y; - - out.clip_position = vec4f(position.x, position.y, vertex.position.z, 1.0); - return out; -} - -// Fragment shader.... - -@group(0) @binding(0) var t_diffuse : texture_2d; -@group(0) @binding(1) var s_diffuse : sampler; - -@fragment fn fs_main(in : VertexOutput)->@location(0) vec4 { - let tc = vec2(u32(in.tex_coords.x), u32(in.tex_coords.y)); - return textureLoad(t_diffuse, tc, 0); + + return position; } From 26a39390120d165132c38f8f27e290d5e714683e Mon Sep 17 00:00:00 2001 From: John Doty Date: Fri, 25 Aug 2023 15:10:55 -0700 Subject: [PATCH 3/4] [oden] Massive refactor on state machine for circles --- src/lib.rs | 212 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 145 insertions(+), 67 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bdf3b59c..4c5f94ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -255,6 +255,65 @@ const SPRITE_VERTICES: &[Vertex] = &[ }, ]; +struct VertexBufferPool { + buffers: Vec>, + next_free_buffer: usize, +} + +impl VertexBufferPool { + fn new() -> Self { + VertexBufferPool { + buffers: Vec::new(), + next_free_buffer: 0, + } + } + + fn clear(&mut self) { + self.next_free_buffer = 0; + } + + fn new_buffer(&mut self, device: &wgpu::Device) -> VertexBufferHandle { + if self.next_free_buffer >= self.buffers.len() { + let max_sprites: usize = 4096; + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Vertex Buffer"), + size: (max_sprites * std::mem::size_of::()).try_into().unwrap(), + mapped_at_creation: false, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + }); + self.buffers.push(VertexBuffer { + buffer, + capacity: max_sprites, + vec: Vec::with_capacity(max_sprites), + }); + } + + let index = self.next_free_buffer; + self.next_free_buffer += 1; + let vb = &mut self.buffers[index]; + vb.vec.clear(); + VertexBufferHandle { + index, + capacity: vb.capacity, + } + } + + fn get(&self, handle: &VertexBufferHandle) -> &VertexBuffer { + &self.buffers[handle.index] + } + + fn get_mut(&mut self, handle: &VertexBufferHandle) -> &mut VertexBuffer { + &mut self.buffers[handle.index] + } + + fn copy_instance_buffers(&mut self, queue: &wgpu::Queue) { + for i in 0..self.next_free_buffer { + let vb = &self.buffers[i]; + queue.write_buffer(&vb.buffer, 0, bytemuck::cast_slice(&vb.vec)); + } + } +} + struct State { surface: wgpu::Surface, device: wgpu::Device, @@ -266,15 +325,13 @@ struct State { sprite_vertex_buffer: wgpu::Buffer, sprite_pipeline: wgpu::RenderPipeline, - sprite_instance_buffers: Vec>, - next_free_sprite_instance_buffer: usize, + sprite_instance_buffers: VertexBufferPool, sprite_bind_group_layout: wgpu::BindGroupLayout, sprite_textures: HashMap, circle_pipeline: wgpu::RenderPipeline, - circle_instance_buffers: Vec>, - next_free_circle_instance_buffer: usize, + circle_instance_buffers: VertexBufferPool, write_textures: HashMap, @@ -484,15 +541,13 @@ impl State { sprite_pipeline, sprite_vertex_buffer, - sprite_instance_buffers: Vec::new(), - next_free_sprite_instance_buffer: 0, + sprite_instance_buffers: VertexBufferPool::new(), sprite_bind_group_layout, sprite_textures: HashMap::new(), circle_pipeline, - circle_instance_buffers: Vec::new(), - next_free_circle_instance_buffer: 0, + circle_instance_buffers: VertexBufferPool::new(), write_textures: HashMap::new(), screen_uniform, @@ -529,8 +584,9 @@ impl State { fn render(&mut self, commands: Vec) -> Result<(), wgpu::SurfaceError> { let _span = span!("context render"); - // Reset vertex buffers. - self.next_free_sprite_instance_buffer = 0; + // Reset instance buffers. + self.sprite_instance_buffers.clear(); + self.circle_instance_buffers.clear(); let mut builder = FrameBuilder::new(self)?; for command in commands { @@ -610,59 +666,23 @@ impl State { self.write_textures.insert(id, texture); } - fn new_vertex_buffer(&mut self) -> VertexBufferHandle { - if self.next_free_sprite_instance_buffer >= self.sprite_instance_buffers.len() { - let max_sprites: usize = 4096; - let buffer = self.device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Sprite Instance Buffer"), - size: (max_sprites * std::mem::size_of::()) - .try_into() - .unwrap(), - mapped_at_creation: false, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - }); - self.sprite_instance_buffers.push(VertexBuffer { - buffer, - capacity: max_sprites, - vec: Vec::with_capacity(max_sprites), - }); - } - - let index = self.next_free_sprite_instance_buffer; - self.next_free_sprite_instance_buffer += 1; - let vb = &mut self.sprite_instance_buffers[index]; - vb.vec.clear(); - VertexBufferHandle { - index, - capacity: vb.capacity, - } - } - - fn get_sprite_instance_buffer( - &self, - handle: &VertexBufferHandle, - ) -> &VertexBuffer { - &self.sprite_instance_buffers[handle.index] - } - - fn get_sprite_instance_buffer_mut( - &mut self, - handle: &VertexBufferHandle, - ) -> &mut VertexBuffer { - &mut self.sprite_instance_buffers[handle.index] - } - fn copy_instance_buffers(&mut self) { - for i in 0..self.next_free_sprite_instance_buffer { - let vb = &self.sprite_instance_buffers[i]; - self.queue - .write_buffer(&vb.buffer, 0, bytemuck::cast_slice(&vb.vec)); - } + self.sprite_instance_buffers + .copy_instance_buffers(&self.queue); + self.circle_instance_buffers + .copy_instance_buffers(&self.queue); } } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum DrawMode { + Sprites, + Circles, +} + #[derive(Debug)] struct DrawCall { + mode: DrawMode, texture_id: Option, vertex_buffer: VertexBufferHandle, draw_start: u32, @@ -670,8 +690,9 @@ struct DrawCall { } impl DrawCall { - pub fn new(vertex_buffer: VertexBufferHandle, draw_start: u32) -> Self { + pub fn new(mode: DrawMode, vertex_buffer: VertexBufferHandle, draw_start: u32) -> Self { DrawCall { + mode, texture_id: None, vertex_buffer, draw_start, @@ -680,7 +701,7 @@ impl DrawCall { } pub fn new_at_buffer_tail(&self) -> Self { - DrawCall::new(self.vertex_buffer.clone(), self.draw_end) + DrawCall::new(self.mode, self.vertex_buffer.clone(), self.draw_end) } pub fn switch_textures(&self, id: u32) -> DrawCall { @@ -713,10 +734,23 @@ impl DrawCall { None => (), }; - let vb = state.get_sprite_instance_buffer(&self.vertex_buffer); + let vb = match self.mode { + DrawMode::Sprites => { + &state + .sprite_instance_buffers + .get(&self.vertex_buffer) + .buffer + } + DrawMode::Circles => { + &state + .circle_instance_buffers + .get(&self.vertex_buffer) + .buffer + } + }; pass.set_bind_group(1, &state.screen_uniform_bind_group, &[]); pass.set_vertex_buffer(0, state.sprite_vertex_buffer.slice(..)); - pass.set_vertex_buffer(1, vb.buffer.slice(..)); + pass.set_vertex_buffer(1, vb.slice(..)); pass.draw( 0..SPRITE_VERTICES.len() as u32, @@ -733,6 +767,7 @@ struct FrameBuilder<'a> { encoder: wgpu::CommandEncoder, output: wgpu::SurfaceTexture, + mode: DrawMode, target: Rc, color: Option<[f64; 4]>, draw_calls: Vec, @@ -762,6 +797,7 @@ impl<'a> FrameBuilder<'a> { encoder, output, + mode: DrawMode::Sprites, target: last_view, color: None, draw_calls: Vec::new(), @@ -836,6 +872,19 @@ impl<'a> FrameBuilder<'a> { self.target = target; } + fn new_instance_buffer(&mut self) -> VertexBufferHandle { + match self.mode { + DrawMode::Sprites => self + .state + .sprite_instance_buffers + .new_buffer(&self.state.device), + DrawMode::Circles => self + .state + .circle_instance_buffers + .new_buffer(&self.state.device), + } + } + fn use_texture(&mut self, texture_id: u32) { match self.draw_calls.last_mut() { Some(call) => match call.texture_id { @@ -855,26 +904,35 @@ impl<'a> FrameBuilder<'a> { } }, None => { - let mut call = DrawCall::new(self.state.new_vertex_buffer(), 0); + let mut call = DrawCall::new(self.mode, self.new_instance_buffer(), 0); call.texture_id = Some(texture_id); self.draw_calls.push(call); } } } + fn switch_mode(&mut self, mode: DrawMode) { + if self.mode != mode { + self.flush(); + self.draw_calls.clear(); + self.mode = mode; + } + } + fn get_sprite_instance_buffer(&mut self) -> &mut VertexBuffer { + self.switch_mode(DrawMode::Sprites); match self.draw_calls.last_mut() { Some(call) => match call.allocate_capacity(1) { - Some(vb) => return self.state.get_sprite_instance_buffer_mut(&vb), + Some(vb) => return self.state.sprite_instance_buffers.get_mut(&vb), None => {} }, None => {} }; - let mut call = DrawCall::new(self.state.new_vertex_buffer(), 0); + let mut call = DrawCall::new(self.mode, self.new_instance_buffer(), 0); let vb = call.allocate_capacity(1).unwrap(); self.draw_calls.push(call); - self.state.get_sprite_instance_buffer_mut(&vb) + self.state.sprite_instance_buffers.get_mut(&vb) } fn push_sprite(&mut self, sc: script::graphics::SpriteCommand) { @@ -887,10 +945,26 @@ impl<'a> FrameBuilder<'a> { }); } + fn get_circle_instance_buffer(&mut self) -> &mut VertexBuffer { + self.switch_mode(DrawMode::Circles); + match self.draw_calls.last_mut() { + Some(call) => match call.allocate_capacity(1) { + Some(vb) => return self.state.circle_instance_buffers.get_mut(&vb), + None => {} + }, + None => {} + }; + + let mut call = DrawCall::new(self.mode, self.new_instance_buffer(), 0); + let vb = call.allocate_capacity(1).unwrap(); + self.draw_calls.push(call); + self.state.circle_instance_buffers.get_mut(&vb) + } + fn flush(&mut self) { let first_call = match self.draw_calls.last() { Some(call) => call.new_at_buffer_tail(), - None => DrawCall::new(self.state.new_vertex_buffer(), 0), + None => DrawCall::new(self.mode, self.new_instance_buffer(), 0), }; if self.draw_calls.len() > 0 { @@ -916,7 +990,11 @@ impl<'a> FrameBuilder<'a> { depth_stencil_attachment: None, }); - pass.set_pipeline(&self.state.sprite_pipeline); + match self.mode { + DrawMode::Sprites => pass.set_pipeline(&self.state.sprite_pipeline), + DrawMode::Circles => pass.set_pipeline(&self.state.circle_pipeline), + } + for call in &self.draw_calls { call.draw(&self.state, &mut pass); } From 020bb8f124bc8e0e6d0c0e25733d61aa851d3b88 Mon Sep 17 00:00:00 2001 From: John Doty Date: Fri, 25 Aug 2023 15:32:35 -0700 Subject: [PATCH 4/4] [oden] Circles work --- game/actor.ts | 3 ++- src/circle_shader.wgsl | 6 ++--- src/graphics.ts | 12 +++++++++ src/lib.rs | 51 +++++++++++++++++++++---------------- src/script/graphics.rs | 54 +++++++++++++++++++++++----------------- src/sprite_shader.wgsl | 6 ++--- types/graphics-core.d.ts | 2 ++ 7 files changed, 83 insertions(+), 51 deletions(-) diff --git a/game/actor.ts b/game/actor.ts index b2a5ca8c..c320e0c9 100644 --- a/game/actor.ts +++ b/game/actor.ts @@ -1,7 +1,7 @@ import { load_texture } from "./assets"; import { btn, Button } from "./input"; import { Vec2, new_v2, vadd, vsub, vnorm, vmul } from "./vector"; -import { spr, use_texture, Texture } from "./graphics"; +import { spr, circle, use_texture, Texture } from "./graphics"; import { has_collision, Level } from "./level"; export interface ActorProps { @@ -191,6 +191,7 @@ export class Robo extends Actor { const frame = (anim.start + ((clock / anim.speed) % anim.length)) >> 0; spr(x, y, w, h, frame * w, 0, 32, 32); + // circle(this.props.position.x, this.props.position.y, 16, 1); } } } diff --git a/src/circle_shader.wgsl b/src/circle_shader.wgsl index b267b4ac..4b03cae3 100644 --- a/src/circle_shader.wgsl +++ b/src/circle_shader.wgsl @@ -31,7 +31,7 @@ struct VertexOutput { // The circle's coordinate system goes from (-1,-1) to (1,1) but by // convention we provide ourselves texture coordinates that go from (0,0) // to (1,1). - out.tex_coords = mix(vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vertex.tex_coords); + out.tex_coords = (vertex.tex_coords * vec2f(2.0,2.0)) - vec2f(1.0,1.0); // Compute the squared radius of the inner circle, so we don't do it // per-pixel. @@ -65,7 +65,7 @@ struct VertexOutput { } else if (tc2.x + tc2.y <= 1.0) { return in.stroke_color; } else { - return vec4(0.0, 0.0, 0.0, 1.0); + return vec4(0.0, 0.0, 0.0, 0.0); } } @@ -76,7 +76,7 @@ struct VertexOutput { struct ScreenUniform { resolution : vec2f, }; -@group(1) @binding(0) // 1. +@group(0) @binding(0) // 1. var screen : ScreenUniform; const RES = vec2f(320.0, 240.0); // The logical resolution of the screen. diff --git a/src/graphics.ts b/src/graphics.ts index 06ce66f3..a422e78c 100644 --- a/src/graphics.ts +++ b/src/graphics.ts @@ -52,6 +52,18 @@ export function spr( core.spr(x, y, w, h, sx, sy, sw, sh); } +/** + * Draw a circle. + * + * @param x - The x coordinate of the center of the circle. + * @param y - The y coordinate of the center of the circle. + * @param r - The radius of the circle. + * @param s - The stroke width of the circle. + */ +export function circle(x: number, y: number, r: number, s: number) { + core.circle(x, y, r, s); +} + export class Texture { #id: number; constructor(id: number) { diff --git a/src/lib.rs b/src/lib.rs index 4c5f94ef..ada883a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,7 @@ impl Vertex { #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -struct SpriteInstance { +pub struct SpriteInstance { src_top_left: [f32; 2], src_dims: [f32; 2], @@ -87,7 +87,7 @@ impl SpriteInstance { #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -struct CircleInstance { +pub struct CircleInstance { center: [f32; 2], radius: f32, stroke_width: f32, @@ -440,7 +440,7 @@ impl State { let sprite_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Sprite Pipeline Layout"), - bind_group_layouts: &[&sprite_bind_group_layout, &screen_uniform_bind_group_layout], + bind_group_layouts: &[&screen_uniform_bind_group_layout, &sprite_bind_group_layout], push_constant_ranges: &[], }); @@ -487,9 +487,16 @@ impl State { source: wgpu::ShaderSource::Wgsl(include_str!("circle_shader.wgsl").into()), }); + let circle_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Sprite Pipeline Layout"), + bind_group_layouts: &[&screen_uniform_bind_group_layout], + push_constant_ranges: &[], + }); + let circle_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Circle Pipeline"), - layout: Some(&sprite_pipeline_layout), + layout: Some(&circle_pipeline_layout), vertex: wgpu::VertexState { module: &circle_shader, entry_point: "vs_main", @@ -726,21 +733,22 @@ impl DrawCall { pub fn draw<'a>(&self, state: &'a State, pass: &mut wgpu::RenderPass<'a>) { if self.draw_end > self.draw_start { - match self.texture_id { - Some(id) => { - let bind_group = state.sprite_textures.get(&id).unwrap(); - pass.set_bind_group(0, bind_group, &[]); - } - None => (), - }; - let vb = match self.mode { DrawMode::Sprites => { + match self.texture_id { + Some(id) => { + let bind_group = state.sprite_textures.get(&id).unwrap(); + pass.set_bind_group(1, bind_group, &[]); + } + None => (), + }; + &state .sprite_instance_buffers .get(&self.vertex_buffer) .buffer } + DrawMode::Circles => { &state .circle_instance_buffers @@ -748,7 +756,7 @@ impl DrawCall { .buffer } }; - pass.set_bind_group(1, &state.screen_uniform_bind_group, &[]); + pass.set_bind_group(0, &state.screen_uniform_bind_group, &[]); pass.set_vertex_buffer(0, state.sprite_vertex_buffer.slice(..)); pass.set_vertex_buffer(1, vb.slice(..)); @@ -845,7 +853,8 @@ impl<'a> FrameBuilder<'a> { } } GraphicsCommand::Print(pc) => println!("{}", pc.text), - GraphicsCommand::Sprite(sc) => self.push_sprite(sc), + GraphicsCommand::Sprite(si) => self.push_sprite(si), + GraphicsCommand::Circle(ci) => self.push_circle(ci), GraphicsCommand::UseTexture(id) => self.use_texture(id), GraphicsCommand::EndFrame => self.flush(), @@ -935,14 +944,9 @@ impl<'a> FrameBuilder<'a> { self.state.sprite_instance_buffers.get_mut(&vb) } - fn push_sprite(&mut self, sc: script::graphics::SpriteCommand) { + fn push_sprite(&mut self, si: SpriteInstance) { let vertex_buffer = self.get_sprite_instance_buffer(); - vertex_buffer.vec.push(SpriteInstance { - src_top_left: [sc.u, sc.v], - src_dims: [sc.sw, sc.sh], - dest_top_left: [sc.x, sc.y], - dest_dims: [sc.w, sc.h], - }); + vertex_buffer.vec.push(si); } fn get_circle_instance_buffer(&mut self) -> &mut VertexBuffer { @@ -961,6 +965,11 @@ impl<'a> FrameBuilder<'a> { self.state.circle_instance_buffers.get_mut(&vb) } + fn push_circle(&mut self, ci: CircleInstance) { + let vertex_buffer = self.get_circle_instance_buffer(); + vertex_buffer.vec.push(ci); + } + fn flush(&mut self) { let first_call = match self.draw_calls.last() { Some(call) => call.new_at_buffer_tail(), diff --git a/src/script/graphics.rs b/src/script/graphics.rs index b55b5d93..7bbe137e 100644 --- a/src/script/graphics.rs +++ b/src/script/graphics.rs @@ -15,18 +15,6 @@ pub struct ClearCommand { pub color: [f64; 4], } -#[derive(Debug)] -pub struct SpriteCommand { - pub x: f32, - pub y: f32, - pub w: f32, - pub h: f32, - pub u: f32, - pub v: f32, - pub sw: f32, - pub sh: f32, -} - #[derive(Debug)] pub struct CreateTextureCommand { pub id: u32, @@ -38,7 +26,8 @@ pub struct CreateTextureCommand { pub enum GraphicsCommand { Clear(ClearCommand), Print(PrintCommand), - Sprite(SpriteCommand), + Circle(crate::CircleInstance), + Sprite(crate::SpriteInstance), CreateTexture(CreateTextureCommand), CreateWritableTexture { id: u32, @@ -78,16 +67,26 @@ impl GraphicsImpl { } fn spr(&self, x: f32, y: f32, w: f32, h: f32, u: f32, v: f32, sw: f32, sh: f32) { - let _ = self.sender.send(GraphicsCommand::Sprite(SpriteCommand { - x, - y, - w, - h, - u, - v, - sw, - sh, - })); + let _ = self + .sender + .send(GraphicsCommand::Sprite(crate::SpriteInstance { + src_top_left: [u, v], + src_dims: [sw, sh], + dest_top_left: [x, y], + dest_dims: [w, h], + })); + } + + fn circle(&self, x: f32, y: f32, r: f32, s: f32) { + let _ = self + .sender + .send(GraphicsCommand::Circle(crate::CircleInstance { + center: [x, y], + radius: r, + stroke_width: s, + stroke_color: [1.0, 0.0, 0.0, 1.0], + fill_color: [1.0, 1.0, 1.0, 1.0], + })); } fn create_texture( @@ -183,6 +182,15 @@ impl GraphicsAPI { )?, )?; } + { + let gfx = gfx.clone(); + builder.export( + "circle", + ctx.new_fn(move |_: &ContextRef, x: f32, y: f32, r: f32, s: f32| { + gfx.circle(x, y, r, s) + })?, + )?; + } { let gfx = gfx.clone(); builder.export( diff --git a/src/sprite_shader.wgsl b/src/sprite_shader.wgsl index 20387c57..cb2a9a21 100644 --- a/src/sprite_shader.wgsl +++ b/src/sprite_shader.wgsl @@ -34,8 +34,8 @@ struct VertexOutput { // Fragment shader // ---------------------------------------------------------------------------- -@group(0) @binding(0) var t_diffuse : texture_2d; -@group(0) @binding(1) var s_diffuse : sampler; +@group(1) @binding(0) var t_diffuse : texture_2d; +@group(1) @binding(1) var s_diffuse : sampler; @fragment fn fs_main(in : VertexOutput)->@location(0) vec4 { let tc = vec2(u32(in.tex_coords.x), u32(in.tex_coords.y)); @@ -50,7 +50,7 @@ struct VertexOutput { struct ScreenUniform { resolution : vec2f, }; -@group(1) @binding(0) // 1. +@group(0) @binding(0) // 1. var screen : ScreenUniform; const RES = vec2f(320.0, 240.0); // The logical resolution of the screen. diff --git a/types/graphics-core.d.ts b/types/graphics-core.d.ts index 45cf9b2c..3eaa5234 100644 --- a/types/graphics-core.d.ts +++ b/types/graphics-core.d.ts @@ -15,6 +15,8 @@ export function spr( sh: number ); +export function circle(x: number, y: number, r: number, s: number); + export function create_texture( buffer: ArrayBuffer, label: string | undefined