Skip to main content

threecrate_gpu/
mesh.rs

1//! GPU-accelerated mesh rendering with PBR and flat shading
2
3use threecrate_core::{Result, Error};
4use threecrate_simplification::ProgressiveMesh;
5use crate::GpuContext;
6use nalgebra::{Matrix4, Vector3, Point3};
7use bytemuck::{Pod, Zeroable};
8use wgpu::util::DeviceExt;
9use winit::window::Window;
10
11/// Vertex data for mesh rendering with full PBR attributes
12#[repr(C)]
13#[derive(Copy, Clone, Debug, Pod, Zeroable)]
14pub struct MeshVertex {
15    pub position: [f32; 3],
16    pub normal: [f32; 3],
17    pub tangent: [f32; 3],
18    pub bitangent: [f32; 3],
19    pub uv: [f32; 2],
20    pub color: [f32; 3],
21    pub _padding: f32,
22}
23
24impl MeshVertex {
25    /// Create a new mesh vertex
26    pub fn new(
27        position: [f32; 3],
28        normal: [f32; 3],
29        uv: [f32; 2],
30        color: [f32; 3],
31    ) -> Self {
32        // Calculate tangent and bitangent vectors (simplified)
33        let tangent = if normal[0].abs() > 0.9 {
34            [0.0, 1.0, 0.0]
35        } else {
36            [1.0, 0.0, 0.0]
37        };
38        
39        let bitangent = [
40            normal[1] * tangent[2] - normal[2] * tangent[1],
41            normal[2] * tangent[0] - normal[0] * tangent[2],
42            normal[0] * tangent[1] - normal[1] * tangent[0],
43        ];
44        
45        Self {
46            position,
47            normal,
48            tangent,
49            bitangent,
50            uv,
51            color,
52            _padding: 0.0,
53        }
54    }
55    
56    /// Create vertex from position and normal with default color
57    pub fn from_pos_normal(position: [f32; 3], normal: [f32; 3]) -> Self {
58        Self::new(position, normal, [0.0, 0.0], [0.8, 0.8, 0.8])
59    }
60    
61    /// Vertex buffer layout descriptor
62    pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
63        wgpu::VertexBufferLayout {
64            array_stride: std::mem::size_of::<MeshVertex>() as wgpu::BufferAddress,
65            step_mode: wgpu::VertexStepMode::Vertex,
66            attributes: &[
67                // Position
68                wgpu::VertexAttribute {
69                    offset: 0,
70                    shader_location: 0,
71                    format: wgpu::VertexFormat::Float32x3,
72                },
73                // Normal
74                wgpu::VertexAttribute {
75                    offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
76                    shader_location: 1,
77                    format: wgpu::VertexFormat::Float32x3,
78                },
79                // Tangent
80                wgpu::VertexAttribute {
81                    offset: std::mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
82                    shader_location: 2,
83                    format: wgpu::VertexFormat::Float32x3,
84                },
85                // Bitangent
86                wgpu::VertexAttribute {
87                    offset: std::mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
88                    shader_location: 3,
89                    format: wgpu::VertexFormat::Float32x3,
90                },
91                // UV
92                wgpu::VertexAttribute {
93                    offset: std::mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
94                    shader_location: 4,
95                    format: wgpu::VertexFormat::Float32x2,
96                },
97                // Color
98                wgpu::VertexAttribute {
99                    offset: std::mem::size_of::<[f32; 14]>() as wgpu::BufferAddress,
100                    shader_location: 5,
101                    format: wgpu::VertexFormat::Float32x3,
102                },
103            ],
104        }
105    }
106}
107
108/// Camera uniform data for mesh rendering
109#[repr(C)]
110#[derive(Copy, Clone, Pod, Zeroable)]
111pub struct MeshCameraUniform {
112    pub view_proj: [[f32; 4]; 4],
113    pub view_pos: [f32; 3],
114    pub _padding: f32,
115}
116
117/// PBR material properties
118#[repr(C)]
119#[derive(Copy, Clone, Debug, Pod, Zeroable)]
120pub struct PbrMaterial {
121    pub albedo: [f32; 3],
122    pub metallic: f32,
123    pub roughness: f32,
124    pub ao: f32,
125    pub emission: [f32; 3],
126    pub _padding: f32,
127}
128
129impl Default for PbrMaterial {
130    fn default() -> Self {
131        Self {
132            albedo: [0.7, 0.7, 0.7],
133            metallic: 0.0,
134            roughness: 0.5,
135            ao: 1.0,
136            emission: [0.0, 0.0, 0.0],
137            _padding: 0.0,
138        }
139    }
140}
141
142/// Flat shading material properties
143#[repr(C)]
144#[derive(Copy, Clone, Debug, Pod, Zeroable)]
145pub struct FlatMaterial {
146    pub color: [f32; 3],
147    pub _padding: f32,
148}
149
150impl Default for FlatMaterial {
151    fn default() -> Self {
152        Self {
153            color: [0.8, 0.8, 0.8],
154            _padding: 0.0,
155        }
156    }
157}
158
159/// Lighting parameters for mesh rendering
160#[repr(C)]
161#[derive(Copy, Clone, Debug, Pod, Zeroable)]
162pub struct MeshLightingParams {
163    pub light_position: [f32; 3],
164    pub light_intensity: f32,
165    pub light_color: [f32; 3],
166    pub ambient_strength: f32,
167    pub gamma: f32,
168    pub exposure: f32,
169    pub _padding: [f32; 2],
170}
171
172impl Default for MeshLightingParams {
173    fn default() -> Self {
174        Self {
175            light_position: [10.0, 10.0, 10.0],
176            light_intensity: 1.0,
177            light_color: [1.0, 1.0, 1.0],
178            ambient_strength: 0.03,
179            gamma: 2.2,
180            exposure: 1.0,
181            _padding: [0.0, 0.0],
182        }
183    }
184}
185
186/// Mesh rendering configuration
187#[derive(Debug, Clone)]
188pub struct MeshRenderConfig {
189    pub lighting_params: MeshLightingParams,
190    pub background_color: [f64; 4],
191    pub enable_depth_test: bool,
192    pub enable_backface_culling: bool,
193    pub enable_multisampling: bool,
194    pub wireframe_mode: bool,
195}
196
197impl Default for MeshRenderConfig {
198    fn default() -> Self {
199        Self {
200            lighting_params: MeshLightingParams::default(),
201            background_color: [0.1, 0.1, 0.1, 1.0],
202            enable_depth_test: true,
203            enable_backface_culling: true,
204            enable_multisampling: true,
205            wireframe_mode: false,
206        }
207    }
208}
209
210/// Mesh data structure for GPU rendering
211#[derive(Debug, Clone)]
212pub struct GpuMesh {
213    pub vertices: Vec<MeshVertex>,
214    pub indices: Vec<u32>,
215    pub material: PbrMaterial,
216}
217
218impl GpuMesh {
219    /// Create a new GPU mesh
220    pub fn new(vertices: Vec<MeshVertex>, indices: Vec<u32>, material: PbrMaterial) -> Self {
221        Self {
222            vertices,
223            indices,
224            material,
225        }
226    }
227    
228    /// Create a simple triangle mesh for testing
229    pub fn triangle() -> Self {
230        let vertices = vec![
231            MeshVertex::new([-0.5, -0.5, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0], [1.0, 0.0, 0.0]),
232            MeshVertex::new([0.5, -0.5, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0], [0.0, 1.0, 0.0]),
233            MeshVertex::new([0.0, 0.5, 0.0], [0.0, 0.0, 1.0], [0.5, 1.0], [0.0, 0.0, 1.0]),
234        ];
235        
236        let indices = vec![0, 1, 2];
237        
238        Self::new(vertices, indices, PbrMaterial::default())
239    }
240    
241    /// Create a cube mesh for testing
242    pub fn cube() -> Self {
243        let vertices = vec![
244            // Front face
245            MeshVertex::new([-1.0, -1.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0], [0.8, 0.2, 0.2]),
246            MeshVertex::new([1.0, -1.0, 1.0], [0.0, 0.0, 1.0], [1.0, 0.0], [0.8, 0.2, 0.2]),
247            MeshVertex::new([1.0, 1.0, 1.0], [0.0, 0.0, 1.0], [1.0, 1.0], [0.8, 0.2, 0.2]),
248            MeshVertex::new([-1.0, 1.0, 1.0], [0.0, 0.0, 1.0], [0.0, 1.0], [0.8, 0.2, 0.2]),
249            
250            // Back face
251            MeshVertex::new([1.0, -1.0, -1.0], [0.0, 0.0, -1.0], [0.0, 0.0], [0.2, 0.8, 0.2]),
252            MeshVertex::new([-1.0, -1.0, -1.0], [0.0, 0.0, -1.0], [1.0, 0.0], [0.2, 0.8, 0.2]),
253            MeshVertex::new([-1.0, 1.0, -1.0], [0.0, 0.0, -1.0], [1.0, 1.0], [0.2, 0.8, 0.2]),
254            MeshVertex::new([1.0, 1.0, -1.0], [0.0, 0.0, -1.0], [0.0, 1.0], [0.2, 0.8, 0.2]),
255            
256            // Top face
257            MeshVertex::new([-1.0, 1.0, 1.0], [0.0, 1.0, 0.0], [0.0, 0.0], [0.2, 0.2, 0.8]),
258            MeshVertex::new([1.0, 1.0, 1.0], [0.0, 1.0, 0.0], [1.0, 0.0], [0.2, 0.2, 0.8]),
259            MeshVertex::new([1.0, 1.0, -1.0], [0.0, 1.0, 0.0], [1.0, 1.0], [0.2, 0.2, 0.8]),
260            MeshVertex::new([-1.0, 1.0, -1.0], [0.0, 1.0, 0.0], [0.0, 1.0], [0.2, 0.2, 0.8]),
261            
262            // Bottom face
263            MeshVertex::new([-1.0, -1.0, -1.0], [0.0, -1.0, 0.0], [0.0, 0.0], [0.8, 0.8, 0.2]),
264            MeshVertex::new([1.0, -1.0, -1.0], [0.0, -1.0, 0.0], [1.0, 0.0], [0.8, 0.8, 0.2]),
265            MeshVertex::new([1.0, -1.0, 1.0], [0.0, -1.0, 0.0], [1.0, 1.0], [0.8, 0.8, 0.2]),
266            MeshVertex::new([-1.0, -1.0, 1.0], [0.0, -1.0, 0.0], [0.0, 1.0], [0.8, 0.8, 0.2]),
267            
268            // Right face
269            MeshVertex::new([1.0, -1.0, 1.0], [1.0, 0.0, 0.0], [0.0, 0.0], [0.8, 0.2, 0.8]),
270            MeshVertex::new([1.0, -1.0, -1.0], [1.0, 0.0, 0.0], [1.0, 0.0], [0.8, 0.2, 0.8]),
271            MeshVertex::new([1.0, 1.0, -1.0], [1.0, 0.0, 0.0], [1.0, 1.0], [0.8, 0.2, 0.8]),
272            MeshVertex::new([1.0, 1.0, 1.0], [1.0, 0.0, 0.0], [0.0, 1.0], [0.8, 0.2, 0.8]),
273            
274            // Left face
275            MeshVertex::new([-1.0, -1.0, -1.0], [-1.0, 0.0, 0.0], [0.0, 0.0], [0.2, 0.8, 0.8]),
276            MeshVertex::new([-1.0, -1.0, 1.0], [-1.0, 0.0, 0.0], [1.0, 0.0], [0.2, 0.8, 0.8]),
277            MeshVertex::new([-1.0, 1.0, 1.0], [-1.0, 0.0, 0.0], [1.0, 1.0], [0.2, 0.8, 0.8]),
278            MeshVertex::new([-1.0, 1.0, -1.0], [-1.0, 0.0, 0.0], [0.0, 1.0], [0.2, 0.8, 0.8]),
279        ];
280        
281        let indices = vec![
282            // Front face
283            0, 1, 2, 2, 3, 0,
284            // Back face
285            4, 5, 6, 6, 7, 4,
286            // Top face
287            8, 9, 10, 10, 11, 8,
288            // Bottom face
289            12, 13, 14, 14, 15, 12,
290            // Right face
291            16, 17, 18, 18, 19, 16,
292            // Left face
293            20, 21, 22, 22, 23, 20,
294        ];
295        
296        Self::new(vertices, indices, PbrMaterial::default())
297    }
298    
299    /// Create a mesh from point cloud with estimated normals
300    pub fn from_point_cloud(points: &[Point3<f32>], color: [f32; 3]) -> Self {
301        let vertices: Vec<MeshVertex> = points.iter().map(|p| {
302            // Simple normal estimation (could use the GPU normal computation)
303            let normal = [0.0, 0.0, 1.0]; // Default normal
304            MeshVertex::new([p.x, p.y, p.z], normal, [0.0, 0.0], color)
305        }).collect();
306        
307        // Create indices for point rendering (each point is a degenerate triangle)
308        let indices: Vec<u32> = (0..vertices.len() as u32).collect();
309        
310        Self::new(vertices, indices, PbrMaterial::default())
311    }
312}
313
314/// Shading mode for mesh rendering
315#[derive(Debug, Clone, Copy, PartialEq)]
316pub enum ShadingMode {
317    Flat,
318    Pbr,
319}
320
321/// GPU-accelerated mesh renderer with PBR and flat shading
322pub struct MeshRenderer<'window> {
323    pub gpu_context: GpuContext,
324    pub surface: wgpu::Surface<'window>,
325    pub surface_config: wgpu::SurfaceConfiguration,
326    pub pbr_pipeline: wgpu::RenderPipeline,
327    pub flat_pipeline: wgpu::RenderPipeline,
328    /// Single-sample PBR pipeline used for screenshot capture (no MSAA)
329    pub screenshot_pbr_pipeline: wgpu::RenderPipeline,
330    /// Single-sample flat pipeline used for screenshot capture (no MSAA)
331    pub screenshot_flat_pipeline: wgpu::RenderPipeline,
332    pub camera_uniform: MeshCameraUniform,
333    pub camera_buffer: wgpu::Buffer,
334    pub lighting_params: MeshLightingParams,
335    pub lighting_buffer: wgpu::Buffer,
336    pub bind_group_layout: wgpu::BindGroupLayout,
337    pub config: MeshRenderConfig,
338    pub msaa_texture: Option<wgpu::Texture>,
339    pub msaa_view: Option<wgpu::TextureView>,
340}
341
342impl<'window> MeshRenderer<'window> {
343    /// Create new mesh renderer with PBR and flat shading support
344    pub async fn new(window: &'window Window, config: MeshRenderConfig) -> Result<Self> {
345        let gpu_context = GpuContext::new().await?;
346        
347        let surface = gpu_context.instance.create_surface(window)
348            .map_err(|e| Error::Gpu(format!("Failed to create surface: {:?}", e)))?;
349
350        let surface_caps = surface.get_capabilities(&gpu_context.adapter);
351        let surface_format = surface_caps.formats.iter()
352            .copied()
353            .find(|f| f.is_srgb())
354            .unwrap_or(surface_caps.formats[0]);
355
356        let size = window.inner_size();
357        let sample_count = if config.enable_multisampling { 4 } else { 1 };
358        
359        let surface_config = wgpu::SurfaceConfiguration {
360            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
361            format: surface_format,
362            width: size.width,
363            height: size.height,
364            present_mode: surface_caps.present_modes[0],
365            alpha_mode: surface_caps.alpha_modes[0],
366            view_formats: vec![],
367            desired_maximum_frame_latency: 2,
368        };
369        surface.configure(&gpu_context.device, &surface_config);
370
371        // Create MSAA texture if enabled
372        let (msaa_texture, msaa_view) = if config.enable_multisampling {
373            let msaa_texture = gpu_context.device.create_texture(&wgpu::TextureDescriptor {
374                label: Some("MSAA Texture"),
375                size: wgpu::Extent3d {
376                    width: size.width,
377                    height: size.height,
378                    depth_or_array_layers: 1,
379                },
380                mip_level_count: 1,
381                sample_count,
382                dimension: wgpu::TextureDimension::D2,
383                format: surface_format,
384                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
385                view_formats: &[],
386            });
387            let msaa_view = msaa_texture.create_view(&wgpu::TextureViewDescriptor::default());
388            (Some(msaa_texture), Some(msaa_view))
389        } else {
390            (None, None)
391        };
392
393        // Create camera uniform
394        let camera_uniform = MeshCameraUniform {
395            view_proj: Matrix4::identity().into(),
396            view_pos: [0.0, 0.0, 0.0],
397            _padding: 0.0,
398        };
399
400        let camera_buffer = gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
401            label: Some("Camera Buffer"),
402            contents: bytemuck::bytes_of(&camera_uniform),
403            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
404        });
405
406        // Create lighting parameters buffer
407        let lighting_params = config.lighting_params;
408        let lighting_buffer = gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
409            label: Some("Lighting Buffer"),
410            contents: bytemuck::bytes_of(&lighting_params),
411            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
412        });
413
414        // Create bind group layout
415        let bind_group_layout = gpu_context.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
416            entries: &[
417                wgpu::BindGroupLayoutEntry {
418                    binding: 0,
419                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
420                    ty: wgpu::BindingType::Buffer {
421                        ty: wgpu::BufferBindingType::Uniform,
422                        has_dynamic_offset: false,
423                        min_binding_size: None,
424                    },
425                    count: None,
426                },
427                wgpu::BindGroupLayoutEntry {
428                    binding: 1,
429                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
430                    ty: wgpu::BindingType::Buffer {
431                        ty: wgpu::BufferBindingType::Uniform,
432                        has_dynamic_offset: false,
433                        min_binding_size: None,
434                    },
435                    count: None,
436                },
437                wgpu::BindGroupLayoutEntry {
438                    binding: 2,
439                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
440                    ty: wgpu::BindingType::Buffer {
441                        ty: wgpu::BufferBindingType::Uniform,
442                        has_dynamic_offset: false,
443                        min_binding_size: None,
444                    },
445                    count: None,
446                },
447            ],
448            label: Some("mesh_bind_group_layout"),
449        });
450
451        // Create PBR pipeline
452        let pbr_shader = gpu_context.device.create_shader_module(wgpu::ShaderModuleDescriptor {
453            label: Some("PBR Mesh Shader"),
454            source: wgpu::ShaderSource::Wgsl(include_str!("shaders/mesh_pbr.wgsl").into()),
455        });
456
457        let pbr_pipeline = Self::create_render_pipeline(
458            &gpu_context.device,
459            &bind_group_layout,
460            &pbr_shader,
461            surface_format,
462            sample_count,
463            &config,
464            "PBR",
465        );
466
467        // Create flat pipeline
468        let flat_shader = gpu_context.device.create_shader_module(wgpu::ShaderModuleDescriptor {
469            label: Some("Flat Mesh Shader"),
470            source: wgpu::ShaderSource::Wgsl(include_str!("shaders/mesh_flat.wgsl").into()),
471        });
472
473        let flat_pipeline = Self::create_render_pipeline(
474            &gpu_context.device,
475            &bind_group_layout,
476            &flat_shader,
477            surface_format,
478            sample_count,
479            &config,
480            "Flat",
481        );
482
483        // Screenshot pipelines always use sample_count=1 (no MSAA)
484        let screenshot_pbr_pipeline = Self::create_render_pipeline(
485            &gpu_context.device,
486            &bind_group_layout,
487            &pbr_shader,
488            surface_format,
489            1,
490            &config,
491            "Screenshot PBR",
492        );
493
494        let screenshot_flat_pipeline = Self::create_render_pipeline(
495            &gpu_context.device,
496            &bind_group_layout,
497            &flat_shader,
498            surface_format,
499            1,
500            &config,
501            "Screenshot Flat",
502        );
503
504        Ok(Self {
505            gpu_context,
506            surface,
507            surface_config,
508            pbr_pipeline,
509            flat_pipeline,
510            screenshot_pbr_pipeline,
511            screenshot_flat_pipeline,
512            camera_uniform,
513            camera_buffer,
514            lighting_params,
515            lighting_buffer,
516            bind_group_layout,
517            config,
518            msaa_texture,
519            msaa_view,
520        })
521    }
522
523    /// Create a render pipeline for mesh rendering
524    fn create_render_pipeline(
525        device: &wgpu::Device,
526        bind_group_layout: &wgpu::BindGroupLayout,
527        shader: &wgpu::ShaderModule,
528        surface_format: wgpu::TextureFormat,
529        sample_count: u32,
530        config: &MeshRenderConfig,
531        label: &str,
532    ) -> wgpu::RenderPipeline {
533        let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
534            label: Some(&format!("{} Mesh Render Pipeline Layout", label)),
535            bind_group_layouts: &[Some(bind_group_layout)],
536            immediate_size: 0,
537        });
538
539        device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
540            label: Some(&format!("{} Mesh Render Pipeline", label)),
541            layout: Some(&render_pipeline_layout),
542            vertex: wgpu::VertexState {
543                module: shader,
544                entry_point: Some("vs_main"),
545                buffers: &[MeshVertex::desc()],
546                compilation_options: wgpu::PipelineCompilationOptions::default(),
547            },
548            fragment: Some(wgpu::FragmentState {
549                module: shader,
550                entry_point: Some("fs_main"),
551                targets: &[Some(wgpu::ColorTargetState {
552                    format: surface_format,
553                    blend: Some(wgpu::BlendState::REPLACE),
554                    write_mask: wgpu::ColorWrites::ALL,
555                })],
556                compilation_options: wgpu::PipelineCompilationOptions::default(),
557            }),
558            primitive: wgpu::PrimitiveState {
559                topology: if config.wireframe_mode {
560                    wgpu::PrimitiveTopology::LineList
561                } else {
562                    wgpu::PrimitiveTopology::TriangleList
563                },
564                strip_index_format: None,
565                front_face: wgpu::FrontFace::Ccw,
566                cull_mode: if config.enable_backface_culling {
567                    Some(wgpu::Face::Back)
568                } else {
569                    None
570                },
571                unclipped_depth: false,
572                polygon_mode: wgpu::PolygonMode::Fill,
573                conservative: false,
574            },
575            depth_stencil: if config.enable_depth_test {
576                Some(wgpu::DepthStencilState {
577                    format: wgpu::TextureFormat::Depth32Float,
578                    depth_write_enabled: Some(true),
579                    depth_compare: Some(wgpu::CompareFunction::Less),
580                    stencil: wgpu::StencilState::default(),
581                    bias: wgpu::DepthBiasState::default(),
582                })
583            } else {
584                None
585            },
586            multisample: wgpu::MultisampleState {
587                count: sample_count,
588                mask: !0,
589                alpha_to_coverage_enabled: false,
590            },
591            multiview_mask: None,
592            cache: None,
593        })
594    }
595
596    /// Update camera matrices and position
597    pub fn update_camera(&mut self, view_matrix: Matrix4<f32>, proj_matrix: Matrix4<f32>, camera_pos: Vector3<f32>) {
598        self.camera_uniform.view_proj = (proj_matrix * view_matrix).into();
599        self.camera_uniform.view_pos = camera_pos.into();
600        
601        self.gpu_context.queue.write_buffer(
602            &self.camera_buffer,
603            0,
604            bytemuck::bytes_of(&self.camera_uniform),
605        );
606    }
607
608    /// Update lighting parameters
609    pub fn update_lighting(&mut self, params: MeshLightingParams) {
610        self.lighting_params = params;
611        self.gpu_context.queue.write_buffer(
612            &self.lighting_buffer,
613            0,
614            bytemuck::bytes_of(&self.lighting_params),
615        );
616    }
617
618    /// Resize renderer
619    pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
620        if new_size.width > 0 && new_size.height > 0 {
621            self.surface_config.width = new_size.width;
622            self.surface_config.height = new_size.height;
623            self.surface.configure(&self.gpu_context.device, &self.surface_config);
624            
625            // Recreate MSAA texture if needed
626            if self.config.enable_multisampling {
627                let msaa_texture = self.gpu_context.device.create_texture(&wgpu::TextureDescriptor {
628                    label: Some("MSAA Texture"),
629                    size: wgpu::Extent3d {
630                        width: new_size.width,
631                        height: new_size.height,
632                        depth_or_array_layers: 1,
633                    },
634                    mip_level_count: 1,
635                    sample_count: 4,
636                    dimension: wgpu::TextureDimension::D2,
637                    format: self.surface_config.format,
638                    usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
639                    view_formats: &[],
640                });
641                let msaa_view = msaa_texture.create_view(&wgpu::TextureViewDescriptor::default());
642                self.msaa_texture = Some(msaa_texture);
643                self.msaa_view = Some(msaa_view);
644            }
645        }
646    }
647
648    /// Create vertex buffer
649    pub fn create_vertex_buffer(&self, vertices: &[MeshVertex]) -> wgpu::Buffer {
650        self.gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
651            label: Some("Mesh Vertex Buffer"),
652            contents: bytemuck::cast_slice(vertices),
653            usage: wgpu::BufferUsages::VERTEX,
654        })
655    }
656
657    /// Create index buffer
658    pub fn create_index_buffer(&self, indices: &[u32]) -> wgpu::Buffer {
659        self.gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
660            label: Some("Mesh Index Buffer"),
661            contents: bytemuck::cast_slice(indices),
662            usage: wgpu::BufferUsages::INDEX,
663        })
664    }
665
666    /// Create depth texture
667    pub fn create_depth_texture(&self) -> wgpu::Texture {
668        let sample_count = if self.config.enable_multisampling { 4 } else { 1 };
669        
670        self.gpu_context.device.create_texture(&wgpu::TextureDescriptor {
671            label: Some("Depth Texture"),
672            size: wgpu::Extent3d {
673                width: self.surface_config.width,
674                height: self.surface_config.height,
675                depth_or_array_layers: 1,
676            },
677            mip_level_count: 1,
678            sample_count,
679            dimension: wgpu::TextureDimension::D2,
680            format: wgpu::TextureFormat::Depth32Float,
681            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
682            view_formats: &[],
683        })
684    }
685
686    /// Render mesh with specified shading mode
687    pub fn render(&self, mesh: &GpuMesh, shading_mode: ShadingMode) -> Result<()> {
688        let vertex_buffer = self.create_vertex_buffer(&mesh.vertices);
689        let index_buffer = self.create_index_buffer(&mesh.indices);
690        let depth_texture = self.create_depth_texture();
691        let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
692
693        let output = match self.surface.get_current_texture() {
694            wgpu::CurrentSurfaceTexture::Success(frame) => frame,
695            wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,
696            wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => return Ok(()),
697            _ => return Err(Error::Gpu("Failed to get surface texture".to_string())),
698        };
699        
700        let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
701
702        // Create material buffer based on shading mode
703        let material_buffer = match shading_mode {
704            ShadingMode::Pbr => {
705                self.gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
706                    label: Some("PBR Material Buffer"),
707                    contents: bytemuck::bytes_of(&mesh.material),
708                    usage: wgpu::BufferUsages::UNIFORM,
709                })
710            }
711            ShadingMode::Flat => {
712                let flat_material = FlatMaterial {
713                    color: mesh.material.albedo,
714                    _padding: 0.0,
715                };
716                self.gpu_context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
717                    label: Some("Flat Material Buffer"),
718                    contents: bytemuck::bytes_of(&flat_material),
719                    usage: wgpu::BufferUsages::UNIFORM,
720                })
721            }
722        };
723
724        let bind_group = self.gpu_context.device.create_bind_group(&wgpu::BindGroupDescriptor {
725            layout: &self.bind_group_layout,
726            entries: &[
727                wgpu::BindGroupEntry {
728                    binding: 0,
729                    resource: self.camera_buffer.as_entire_binding(),
730                },
731                wgpu::BindGroupEntry {
732                    binding: 1,
733                    resource: material_buffer.as_entire_binding(),
734                },
735                wgpu::BindGroupEntry {
736                    binding: 2,
737                    resource: self.lighting_buffer.as_entire_binding(),
738                },
739            ],
740            label: Some("mesh_bind_group"),
741        });
742
743        let mut encoder = self.gpu_context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
744            label: Some("Mesh Render Encoder"),
745        });
746
747        // Determine render target
748        let (color_attachment, resolve_target) = if let Some(ref msaa_view) = self.msaa_view {
749            (msaa_view, Some(&view))
750        } else {
751            (&view, None)
752        };
753
754        {
755            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
756                label: Some("Mesh Render Pass"),
757                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
758                    view: color_attachment,
759                    resolve_target,
760                    ops: wgpu::Operations {
761                        load: wgpu::LoadOp::Clear(wgpu::Color {
762                            r: self.config.background_color[0],
763                            g: self.config.background_color[1],
764                            b: self.config.background_color[2],
765                            a: self.config.background_color[3],
766                        }),
767                        store: wgpu::StoreOp::Store,
768                    },
769                    depth_slice: None,
770                })],
771                depth_stencil_attachment: if self.config.enable_depth_test {
772                    Some(wgpu::RenderPassDepthStencilAttachment {
773                        view: &depth_view,
774                        depth_ops: Some(wgpu::Operations {
775                            load: wgpu::LoadOp::Clear(1.0),
776                            store: wgpu::StoreOp::Store,
777                        }),
778                        stencil_ops: None,
779                    })
780                } else {
781                    None
782                },
783                timestamp_writes: None,
784                occlusion_query_set: None,
785                multiview_mask: None,
786            });
787
788            let pipeline = match shading_mode {
789                ShadingMode::Pbr => &self.pbr_pipeline,
790                ShadingMode::Flat => &self.flat_pipeline,
791            };
792
793            render_pass.set_pipeline(pipeline);
794            render_pass.set_bind_group(0, &bind_group, &[]);
795            render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
796            render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
797            render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
798        }
799
800        self.gpu_context.queue.submit(std::iter::once(encoder.finish()));
801        output.present();
802
803        Ok(())
804    }
805
806    /// Render mesh to an offscreen texture and return raw RGBA pixel bytes.
807    ///
808    /// Returns `(pixels, format, width, height)`.  The caller is responsible
809    /// for interpreting the format (BGRA vs RGBA) when encoding the image.
810    pub fn render_to_texture(
811        &self,
812        mesh: &GpuMesh,
813        shading_mode: ShadingMode,
814    ) -> Result<(Vec<u8>, wgpu::TextureFormat, u32, u32)> {
815        let width = self.surface_config.width;
816        let height = self.surface_config.height;
817        let format = self.surface_config.format;
818
819        // Offscreen render target (no MSAA, COPY_SRC so we can read it back)
820        let render_texture = self.gpu_context.device.create_texture(&wgpu::TextureDescriptor {
821            label: Some("Screenshot Render Texture"),
822            size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
823            mip_level_count: 1,
824            sample_count: 1,
825            dimension: wgpu::TextureDimension::D2,
826            format,
827            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
828            view_formats: &[],
829        });
830        let render_view = render_texture.create_view(&wgpu::TextureViewDescriptor::default());
831
832        let depth_texture = self.gpu_context.device.create_texture(&wgpu::TextureDescriptor {
833            label: Some("Screenshot Depth Texture"),
834            size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
835            mip_level_count: 1,
836            sample_count: 1,
837            dimension: wgpu::TextureDimension::D2,
838            format: wgpu::TextureFormat::Depth32Float,
839            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
840            view_formats: &[],
841        });
842        let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
843
844        let vertex_buffer = self.create_vertex_buffer(&mesh.vertices);
845        let index_buffer = self.create_index_buffer(&mesh.indices);
846
847        let material_buffer = match shading_mode {
848            ShadingMode::Pbr => self.gpu_context.device.create_buffer_init(
849                &wgpu::util::BufferInitDescriptor {
850                    label: Some("Screenshot PBR Material Buffer"),
851                    contents: bytemuck::bytes_of(&mesh.material),
852                    usage: wgpu::BufferUsages::UNIFORM,
853                },
854            ),
855            ShadingMode::Flat => {
856                let flat_material = FlatMaterial { color: mesh.material.albedo, _padding: 0.0 };
857                self.gpu_context.device.create_buffer_init(
858                    &wgpu::util::BufferInitDescriptor {
859                        label: Some("Screenshot Flat Material Buffer"),
860                        contents: bytemuck::bytes_of(&flat_material),
861                        usage: wgpu::BufferUsages::UNIFORM,
862                    },
863                )
864            }
865        };
866
867        let bind_group = self.gpu_context.device.create_bind_group(&wgpu::BindGroupDescriptor {
868            layout: &self.bind_group_layout,
869            entries: &[
870                wgpu::BindGroupEntry { binding: 0, resource: self.camera_buffer.as_entire_binding() },
871                wgpu::BindGroupEntry { binding: 1, resource: material_buffer.as_entire_binding() },
872                wgpu::BindGroupEntry { binding: 2, resource: self.lighting_buffer.as_entire_binding() },
873            ],
874            label: Some("screenshot_bind_group"),
875        });
876
877        // wgpu requires bytes_per_row to be aligned to COPY_BYTES_PER_ROW_ALIGNMENT (256)
878        let bytes_per_pixel = 4u32;
879        let unpadded_bytes_per_row = width * bytes_per_pixel;
880        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
881        let padded_bytes_per_row = (unpadded_bytes_per_row + align - 1) / align * align;
882
883        let staging_buffer = self.gpu_context.device.create_buffer(&wgpu::BufferDescriptor {
884            label: Some("Screenshot Staging Buffer"),
885            size: (padded_bytes_per_row * height) as wgpu::BufferAddress,
886            usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
887            mapped_at_creation: false,
888        });
889
890        let mut encoder = self.gpu_context.device.create_command_encoder(
891            &wgpu::CommandEncoderDescriptor { label: Some("Screenshot Encoder") },
892        );
893
894        {
895            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
896                label: Some("Screenshot Render Pass"),
897                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
898                    view: &render_view,
899                    resolve_target: None,
900                    ops: wgpu::Operations {
901                        load: wgpu::LoadOp::Clear(wgpu::Color {
902                            r: self.config.background_color[0],
903                            g: self.config.background_color[1],
904                            b: self.config.background_color[2],
905                            a: self.config.background_color[3],
906                        }),
907                        store: wgpu::StoreOp::Store,
908                    },
909                    depth_slice: None,
910                })],
911                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
912                    view: &depth_view,
913                    depth_ops: Some(wgpu::Operations {
914                        load: wgpu::LoadOp::Clear(1.0),
915                        store: wgpu::StoreOp::Store,
916                    }),
917                    stencil_ops: None,
918                }),
919                timestamp_writes: None,
920                occlusion_query_set: None,
921                multiview_mask: None,
922            });
923
924            let pipeline = match shading_mode {
925                ShadingMode::Pbr => &self.screenshot_pbr_pipeline,
926                ShadingMode::Flat => &self.screenshot_flat_pipeline,
927            };
928
929            render_pass.set_pipeline(pipeline);
930            render_pass.set_bind_group(0, &bind_group, &[]);
931            render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
932            render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
933            render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
934        }
935
936        // Copy rendered texture into staging buffer for CPU readback
937        encoder.copy_texture_to_buffer(
938            wgpu::TexelCopyTextureInfo {
939                texture: &render_texture,
940                mip_level: 0,
941                origin: wgpu::Origin3d::ZERO,
942                aspect: wgpu::TextureAspect::All,
943            },
944            wgpu::TexelCopyBufferInfo {
945                buffer: &staging_buffer,
946                layout: wgpu::TexelCopyBufferLayout {
947                    offset: 0,
948                    bytes_per_row: Some(padded_bytes_per_row),
949                    rows_per_image: None,
950                },
951            },
952            wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
953        );
954
955        self.gpu_context.queue.submit(std::iter::once(encoder.finish()));
956
957        // Map the buffer and wait for the GPU to finish
958        let buffer_slice = staging_buffer.slice(..);
959        let (tx, rx) = std::sync::mpsc::channel::<std::result::Result<(), wgpu::BufferAsyncError>>();
960        buffer_slice.map_async(wgpu::MapMode::Read, move |v| { let _ = tx.send(v); });
961        self.gpu_context.device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None });
962        rx.recv()
963            .map_err(|_| Error::Gpu("Screenshot buffer map channel error".to_string()))?
964            .map_err(|e| Error::Gpu(format!("Screenshot buffer map error: {:?}", e)))?;
965
966        let data = buffer_slice.get_mapped_range();
967
968        // Strip row padding before returning
969        let mut pixels: Vec<u8> = Vec::with_capacity((unpadded_bytes_per_row * height) as usize);
970        for row in 0..height as usize {
971            let start = row * padded_bytes_per_row as usize;
972            let end = start + unpadded_bytes_per_row as usize;
973            pixels.extend_from_slice(&data[start..end]);
974        }
975        drop(data);
976        staging_buffer.unmap();
977
978        Ok((pixels, format, width, height))
979    }
980}
981
982/// Convert threecrate mesh to GPU mesh format
983pub fn mesh_to_gpu_mesh(
984    vertices: &[Point3<f32>],
985    indices: &[u32],
986    normals: Option<&[Vector3<f32>]>,
987    colors: Option<&[[f32; 3]]>,
988    material: Option<PbrMaterial>,
989) -> GpuMesh {
990    let gpu_vertices: Vec<MeshVertex> = vertices
991        .iter()
992        .enumerate()
993        .map(|(i, vertex)| {
994            let normal = normals
995                .and_then(|n| n.get(i))
996                .map(|n| [n.x, n.y, n.z])
997                .unwrap_or([0.0, 0.0, 1.0]);
998            
999            let color = colors
1000                .and_then(|c| c.get(i))
1001                .copied()
1002                .unwrap_or([0.8, 0.8, 0.8]);
1003            
1004            MeshVertex::new([vertex.x, vertex.y, vertex.z], normal, [0.0, 0.0], color)
1005        })
1006        .collect();
1007
1008    GpuMesh::new(
1009        gpu_vertices,
1010        indices.to_vec(),
1011        material.unwrap_or_default(),
1012    )
1013}
1014
1015/// Pre-computed LOD levels for a mesh, generated from a progressive mesh.
1016///
1017/// Levels are ordered from coarsest (index 0) to finest (last index).
1018/// Use `select_level` to pick the appropriate LOD for a given camera distance.
1019pub struct LodMesh {
1020    /// GPU meshes at each LOD level, index 0 = coarsest
1021    pub levels: Vec<GpuMesh>,
1022    /// Distance thresholds for switching between levels.
1023    /// `thresholds[i]` is the maximum distance at which `levels[i+1]` should be used.
1024    pub thresholds: Vec<f32>,
1025}
1026
1027impl LodMesh {
1028    /// Create LOD levels by sampling the progressive mesh at evenly-spaced detail ratios.
1029    ///
1030    /// `num_levels` is the total number of LOD levels to generate (minimum 2).
1031    pub fn from_progressive_mesh(pm: &ProgressiveMesh, num_levels: usize) -> Self {
1032        let num_levels = num_levels.max(2);
1033
1034        let levels: Vec<GpuMesh> = (0..num_levels)
1035            .map(|i| {
1036                let ratio = i as f32 / (num_levels - 1) as f32;
1037                let mesh = pm.reconstruct_at_ratio(ratio);
1038                triangle_mesh_to_gpu_mesh(&mesh)
1039            })
1040            .collect();
1041
1042        // Generate default distance thresholds (evenly spaced)
1043        let thresholds: Vec<f32> = (0..num_levels.saturating_sub(1))
1044            .map(|i| {
1045                let t = (num_levels - 1 - i) as f32 / (num_levels - 1) as f32;
1046                t * 100.0
1047            })
1048            .collect();
1049
1050        LodMesh { levels, thresholds }
1051    }
1052
1053    /// Select the appropriate LOD level for a given camera distance.
1054    ///
1055    /// Returns the coarsest mesh that still looks acceptable at the given distance.
1056    /// Closer distances get finer detail; farther distances get coarser meshes.
1057    pub fn select_level(&self, distance: f32) -> &GpuMesh {
1058        for (i, &threshold) in self.thresholds.iter().enumerate() {
1059            if distance > threshold {
1060                return &self.levels[i];
1061            }
1062        }
1063        // Closest distance: return finest level
1064        self.levels.last().unwrap_or(&self.levels[0])
1065    }
1066
1067    /// Number of LOD levels.
1068    pub fn num_levels(&self) -> usize {
1069        self.levels.len()
1070    }
1071}
1072
1073/// Convert a `TriangleMesh` to a `GpuMesh` with default material.
1074fn triangle_mesh_to_gpu_mesh(mesh: &threecrate_core::TriangleMesh) -> GpuMesh {
1075    let normals_slice = mesh.normals.as_deref();
1076    let colors_f32: Option<Vec<[f32; 3]>> = mesh.colors.as_ref().map(|colors| {
1077        colors
1078            .iter()
1079            .map(|c| [c[0] as f32 / 255.0, c[1] as f32 / 255.0, c[2] as f32 / 255.0])
1080            .collect()
1081    });
1082
1083    let indices: Vec<u32> = mesh
1084        .faces
1085        .iter()
1086        .flat_map(|f| [f[0] as u32, f[1] as u32, f[2] as u32])
1087        .collect();
1088
1089    mesh_to_gpu_mesh(
1090        &mesh.vertices,
1091        &indices,
1092        normals_slice,
1093        colors_f32.as_deref(),
1094        None,
1095    )
1096}