use crate::camera::Camera; use anyhow::Result; use bytemuck::{Pod, Zeroable}; /// Matches the WGSL `Uniforms` struct byte-for-byte. /// /// WGSL alignment: vec3 aligns to 16 bytes, vec2 to 8, vec4 to 16. /// Total: 80 bytes (multiple of 16). #[repr(C)] #[derive(Debug, Copy, Clone, Pod, Zeroable)] struct Uniforms { resolution: [f32; 2], // offset 0 viewport_offset: [f32; 2], // offset 8 camera_pos: [f32; 3], // offset 16 (vec3 align 16) time: f32, // offset 28 camera_target: [f32; 3], // offset 32 (vec3 align 16) fov: f32, // offset 44 render_flags: [f32; 4], // offset 48 (vec4 align 16) scene_scale: f32, // offset 64 _pad: [f32; 3], // offset 68 } pub struct RenderPipeline { pipeline: wgpu::RenderPipeline, uniform_buffer: wgpu::Buffer, bind_group: wgpu::BindGroup, scene_scale: f32, } impl RenderPipeline { pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat, wgsl_source: &str, scene_scale: f32) -> Result { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("scene"), source: wgpu::ShaderSource::Wgsl(wgsl_source.into()), }); let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("uniforms"), size: std::mem::size_of::() as u64, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("uniforms_layout"), entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: wgpu::BufferSize::new( std::mem::size_of::() as u64 ), }, count: None, }], }); let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("uniforms_bind"), layout: &bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: uniform_buffer.as_entire_binding(), }], }); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("render_layout"), bind_group_layouts: &[&bind_group_layout], push_constant_ranges: &[], }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("render"), layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: &shader, entry_point: Some("vs_main"), buffers: &[], compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format, blend: None, write_mask: wgpu::ColorWrites::ALL, })], compilation_options: Default::default(), }), primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() }, depth_stencil: None, multisample: wgpu::MultisampleState::default(), multiview: None, cache: None, }); Ok(Self { pipeline, uniform_buffer, bind_group, scene_scale }) } pub fn update_uniforms( &self, queue: &wgpu::Queue, width: u32, height: u32, time: f32, camera: &Camera, ) { let uniforms = Uniforms { resolution: [width as f32, height as f32], viewport_offset: [0.0, 0.0], camera_pos: camera.position(), time, camera_target: camera.target, fov: camera.fov, render_flags: [1.0, 1.0, 1.0, 0.0], scene_scale: self.scene_scale, _pad: [0.0; 3], }; queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniforms)); } pub fn draw<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>) { pass.set_pipeline(&self.pipeline); pass.set_bind_group(0, &self.bind_group, &[]); pass.draw(0..3, 0..1); } }