Skip to main content

threecrate_gpu/
mesh.rs

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