wormhole/
renderer.rs

1use wgpu::*;
2use wgpu::util::DeviceExt;
3use winit::window::Window;
4use cgmath::*;
5use bytemuck::{Pod, Zeroable};
6use crate::camera::Camera;
7use std::path::PathBuf;
8
9fn find_asset_path(relative_path: &str) -> PathBuf {
10    // First, try relative to executable (for packaged games)
11    if let Ok(exe_path) = std::env::current_exe() {
12        if let Some(exe_dir) = exe_path.parent() {
13            let asset_path = exe_dir.join(relative_path);
14            if asset_path.exists() {
15                return asset_path;
16            }
17        }
18    }
19    
20    // Fallback: try current directory (for development)
21    PathBuf::from(relative_path)
22}
23
24#[repr(C)]
25#[derive(Copy, Clone, Debug, Pod, Zeroable)]
26struct Uniforms {
27    model: [[f32; 4]; 4],
28    view: [[f32; 4]; 4],
29    proj: [[f32; 4]; 4],
30}
31
32#[repr(C)]
33#[derive(Copy, Clone, Debug, Pod, Zeroable)]
34struct Vertex {
35    position: [f32; 3],
36    uv: [f32; 2],
37}
38
39pub struct Renderer {
40    window_id: winit::window::WindowId,
41    surface: wgpu::Surface<'static>,
42    device: Device,
43    queue: Queue,
44    config: SurfaceConfiguration,
45    pipeline: RenderPipeline,
46    vertex_buffer: Buffer,
47    index_buffer: Buffer,
48    uniform_buffer: Buffer,
49    uniform_bind_group: BindGroup,
50    texture_bind_group: BindGroup,
51    rotation: f32,
52    pub camera: Camera,  // Made public for FFI access
53}
54
55impl Renderer {
56    pub async fn new(window: &Window) -> Self {
57        let window_id = window.id();
58        let size = window.inner_size();
59        
60        let instance = Instance::new(InstanceDescriptor {
61            backends: Backends::PRIMARY,
62            ..Default::default()
63        });
64
65        let surface_raw = instance.create_surface(window).unwrap();
66        // Safety: Surface doesn't actually need the window after creation in wgpu
67        // The surface is independent once created
68        let surface: wgpu::Surface<'static> = unsafe { std::mem::transmute(surface_raw) };
69
70        let adapter = instance
71            .request_adapter(&RequestAdapterOptions {
72                power_preference: PowerPreference::default(),
73                compatible_surface: Some(&surface),
74                force_fallback_adapter: false,
75            })
76            .await
77            .expect("Failed to find an appropriate adapter");
78
79        let (device, queue) = adapter
80            .request_device(
81                &DeviceDescriptor {
82                    required_features: Features::empty(),
83                    required_limits: Limits::default(),
84                    label: None,
85                },
86                None,
87            )
88            .await
89            .expect("Failed to create device");
90
91        let surface_caps = surface.get_capabilities(&adapter);
92        let surface_format = surface_caps
93            .formats
94            .iter()
95            .copied()
96            .find(|f| f.is_srgb())
97            .unwrap_or(surface_caps.formats[0]);
98
99        let config = SurfaceConfiguration {
100            usage: TextureUsages::RENDER_ATTACHMENT,
101            format: surface_format,
102            width: size.width,
103            height: size.height,
104            present_mode: surface_caps.present_modes[0],
105            alpha_mode: surface_caps.alpha_modes[0],
106            view_formats: vec![],
107            desired_maximum_frame_latency: 2,
108        };
109
110        surface.configure(&device, &config);
111
112        let shader = device.create_shader_module(ShaderModuleDescriptor {
113            label: Some("Cube Shader"),
114            source: ShaderSource::Wgsl(include_str!("../shaders/cube.vert.wgsl").into()),
115        });
116
117        let fragment_shader = device.create_shader_module(ShaderModuleDescriptor {
118            label: Some("Cube Fragment Shader"),
119            source: ShaderSource::Wgsl(include_str!("../shaders/cube.frag.wgsl").into()),
120        });
121
122        let uniform_buffer = device.create_buffer(&BufferDescriptor {
123            label: Some("Uniform Buffer"),
124            size: std::mem::size_of::<Uniforms>() as u64,
125            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
126            mapped_at_creation: false,
127        });
128
129        let uniform_bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
130            entries: &[BindGroupLayoutEntry {
131                binding: 0,
132                visibility: ShaderStages::VERTEX,
133                ty: BindingType::Buffer {
134                    ty: BufferBindingType::Uniform,
135                    has_dynamic_offset: false,
136                    min_binding_size: None,
137                },
138                count: None,
139            }],
140            label: Some("Uniform Bind Group Layout"),
141        });
142
143        let uniform_bind_group = device.create_bind_group(&BindGroupDescriptor {
144            layout: &uniform_bind_group_layout,
145            entries: &[BindGroupEntry {
146                binding: 0,
147                resource: uniform_buffer.as_entire_binding(),
148            }],
149            label: Some("Uniform Bind Group"),
150        });
151
152        // Create texture bind group layout
153        let texture_bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
154            entries: &[
155                BindGroupLayoutEntry {
156                    binding: 0,
157                    visibility: ShaderStages::FRAGMENT,
158                    ty: BindingType::Texture {
159                        multisampled: false,
160                        view_dimension: TextureViewDimension::D2,
161                        sample_type: TextureSampleType::Float { filterable: true },
162                    },
163                    count: None,
164                },
165                BindGroupLayoutEntry {
166                    binding: 1,
167                    visibility: ShaderStages::FRAGMENT,
168                    ty: BindingType::Sampler(SamplerBindingType::Filtering),
169                    count: None,
170                },
171            ],
172            label: Some("Texture Bind Group Layout"),
173        });
174
175        // Load texture - look for assets relative to executable or current directory
176        use crate::texture::Texture;
177        let texture_path = find_asset_path("assets/textures/monkey.jxl");
178        let texture = Texture::from_jxl(&device, &queue, &texture_path)
179            .unwrap_or_else(|_| {
180                // Fallback: create a simple test texture
181                let width = 256;
182                let height = 256;
183                let mut rgba8_data = vec![0u8; (width * height * 4) as usize];
184                // Create a simple checkerboard pattern
185                for y in 0..height {
186                    for x in 0..width {
187                        let idx = ((y * width + x) * 4) as usize;
188                        let checker = ((x / 32) + (y / 32)) % 2;
189                        rgba8_data[idx] = if checker == 0 { 255 } else { 128 };
190                        rgba8_data[idx + 1] = if checker == 0 { 255 } else { 128 };
191                        rgba8_data[idx + 2] = if checker == 0 { 255 } else { 128 };
192                        rgba8_data[idx + 3] = 255;
193                    }
194                }
195                
196                let texture_size = Extent3d {
197                    width,
198                    height,
199                    depth_or_array_layers: 1,
200                };
201                
202                let texture = device.create_texture(&TextureDescriptor {
203                    label: Some("Fallback Texture"),
204                    size: texture_size,
205                    mip_level_count: 1,
206                    sample_count: 1,
207                    dimension: TextureDimension::D2,
208                    format: TextureFormat::Rgba8UnormSrgb,
209                    usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
210                    view_formats: &[],
211                });
212                
213                queue.write_texture(
214                    ImageCopyTexture {
215                        texture: &texture,
216                        mip_level: 0,
217                        origin: Origin3d::ZERO,
218                        aspect: TextureAspect::All,
219                    },
220                    &rgba8_data,
221                    ImageDataLayout {
222                        offset: 0,
223                        bytes_per_row: Some(4 * width),
224                        rows_per_image: Some(height),
225                    },
226                    texture_size,
227                );
228                
229                let view = texture.create_view(&TextureViewDescriptor::default());
230                let sampler = device.create_sampler(&SamplerDescriptor {
231                    label: Some("Texture Sampler"),
232                    address_mode_u: AddressMode::ClampToEdge,
233                    address_mode_v: AddressMode::ClampToEdge,
234                    address_mode_w: AddressMode::ClampToEdge,
235                    mag_filter: FilterMode::Linear,
236                    min_filter: FilterMode::Linear,
237                    mipmap_filter: FilterMode::Nearest,
238                    ..Default::default()
239                });
240                
241                Texture {
242                    texture,
243                    view,
244                    sampler,
245                    width,
246                    height,
247                }
248            });
249
250        let texture_bind_group = device.create_bind_group(&BindGroupDescriptor {
251            layout: &texture_bind_group_layout,
252            entries: &[
253                BindGroupEntry {
254                    binding: 0,
255                    resource: BindingResource::TextureView(&texture.view),
256                },
257                BindGroupEntry {
258                    binding: 1,
259                    resource: BindingResource::Sampler(&texture.sampler),
260                },
261            ],
262            label: Some("Texture Bind Group"),
263        });
264
265        let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
266            label: Some("Render Pipeline Layout"),
267            bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout],
268            push_constant_ranges: &[],
269        });
270
271        let vertex_buffer = Self::create_cube_vertex_buffer(&device);
272        let index_buffer = Self::create_cube_index_buffer(&device);
273
274        let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
275            label: Some("Render Pipeline"),
276            layout: Some(&pipeline_layout),
277            vertex: VertexState {
278                module: &shader,
279                entry_point: "vs_main",
280                buffers: &[VertexBufferLayout {
281                    array_stride: std::mem::size_of::<Vertex>() as u64,
282                    step_mode: VertexStepMode::Vertex,
283                    attributes: &[
284                        VertexAttribute {
285                            offset: 0,
286                            shader_location: 0,
287                            format: VertexFormat::Float32x3,
288                        },
289                        VertexAttribute {
290                            offset: std::mem::size_of::<[f32; 3]>() as u64,
291                            shader_location: 1,
292                            format: VertexFormat::Float32x2,
293                        },
294                    ],
295                }],
296                compilation_options: PipelineCompilationOptions::default(),
297            },
298            fragment: Some(FragmentState {
299                module: &fragment_shader,
300                entry_point: "fs_main",
301                targets: &[Some(ColorTargetState {
302                    format: config.format,
303                    blend: Some(BlendState::REPLACE),
304                    write_mask: ColorWrites::ALL,
305                })],
306                compilation_options: PipelineCompilationOptions::default(),
307            }),
308            primitive: PrimitiveState {
309                topology: PrimitiveTopology::TriangleList,
310                strip_index_format: None,
311                front_face: FrontFace::Ccw,
312                cull_mode: Some(Face::Back),
313                polygon_mode: PolygonMode::Fill,
314                unclipped_depth: false,
315                conservative: false,
316            },
317            depth_stencil: Some(DepthStencilState {
318                format: TextureFormat::Depth32Float,
319                depth_write_enabled: true,
320                depth_compare: CompareFunction::Less,
321                stencil: StencilState::default(),
322                bias: DepthBiasState::default(),
323            }),
324            multisample: MultisampleState {
325                count: 1,
326                mask: !0,
327                alpha_to_coverage_enabled: false,
328            },
329            multiview: None,
330        });
331
332        Self {
333            window_id,
334            surface,
335            device,
336            queue,
337            config,
338            pipeline,
339            vertex_buffer,
340            index_buffer,
341            uniform_buffer,
342            uniform_bind_group,
343            texture_bind_group,
344            rotation: 0.0,
345            camera: Camera::new(size.width, size.height),
346        }
347    }
348
349    fn create_cube_vertex_buffer(device: &Device) -> Buffer {
350        // Cube vertices with UV coordinates
351        // Each face has UV coordinates from (0,0) to (1,1)
352        let vertices: [Vertex; 24] = [
353            // Front face (z = -1)
354            Vertex { position: [-1.0, -1.0, -1.0], uv: [0.0, 1.0] },
355            Vertex { position: [1.0, -1.0, -1.0], uv: [1.0, 1.0] },
356            Vertex { position: [1.0, 1.0, -1.0], uv: [1.0, 0.0] },
357            Vertex { position: [-1.0, 1.0, -1.0], uv: [0.0, 0.0] },
358            // Back face (z = 1)
359            Vertex { position: [1.0, -1.0, 1.0], uv: [0.0, 1.0] },
360            Vertex { position: [-1.0, -1.0, 1.0], uv: [1.0, 1.0] },
361            Vertex { position: [-1.0, 1.0, 1.0], uv: [1.0, 0.0] },
362            Vertex { position: [1.0, 1.0, 1.0], uv: [0.0, 0.0] },
363            // Left face (x = -1)
364            Vertex { position: [-1.0, -1.0, 1.0], uv: [0.0, 1.0] },
365            Vertex { position: [-1.0, -1.0, -1.0], uv: [1.0, 1.0] },
366            Vertex { position: [-1.0, 1.0, -1.0], uv: [1.0, 0.0] },
367            Vertex { position: [-1.0, 1.0, 1.0], uv: [0.0, 0.0] },
368            // Right face (x = 1)
369            Vertex { position: [1.0, -1.0, -1.0], uv: [0.0, 1.0] },
370            Vertex { position: [1.0, -1.0, 1.0], uv: [1.0, 1.0] },
371            Vertex { position: [1.0, 1.0, 1.0], uv: [1.0, 0.0] },
372            Vertex { position: [1.0, 1.0, -1.0], uv: [0.0, 0.0] },
373            // Bottom face (y = -1)
374            Vertex { position: [-1.0, -1.0, 1.0], uv: [0.0, 1.0] },
375            Vertex { position: [1.0, -1.0, 1.0], uv: [1.0, 1.0] },
376            Vertex { position: [1.0, -1.0, -1.0], uv: [1.0, 0.0] },
377            Vertex { position: [-1.0, -1.0, -1.0], uv: [0.0, 0.0] },
378            // Top face (y = 1)
379            Vertex { position: [-1.0, 1.0, -1.0], uv: [0.0, 1.0] },
380            Vertex { position: [1.0, 1.0, -1.0], uv: [1.0, 1.0] },
381            Vertex { position: [1.0, 1.0, 1.0], uv: [1.0, 0.0] },
382            Vertex { position: [-1.0, 1.0, 1.0], uv: [0.0, 0.0] },
383        ];
384
385        device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
386            label: Some("Cube Vertex Buffer"),
387            contents: bytemuck::cast_slice(&vertices),
388            usage: BufferUsages::VERTEX,
389        })
390    }
391
392    fn create_cube_index_buffer(device: &Device) -> Buffer {
393        // Updated indices for the new vertex layout (24 vertices, 4 per face)
394        let indices: [u16; 36] = [
395            // Front face (z = -1)
396            0, 1, 2, 2, 3, 0,
397            // Back face (z = 1)
398            4, 5, 6, 6, 7, 4,
399            // Left face (x = -1)
400            8, 9, 10, 10, 11, 8,
401            // Right face (x = 1)
402            12, 13, 14, 14, 15, 12,
403            // Bottom face (y = -1)
404            16, 17, 18, 18, 19, 16,
405            // Top face (y = 1)
406            20, 21, 22, 22, 23, 20,
407        ];
408
409        device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
410            label: Some("Cube Index Buffer"),
411            contents: bytemuck::cast_slice(&indices),
412            usage: BufferUsages::INDEX,
413        })
414    }
415
416    pub fn resize(&mut self, width: u32, height: u32) {
417        if width > 0 && height > 0 {
418            self.config.width = width;
419            self.config.height = height;
420            self.surface.configure(&self.device, &self.config);
421        }
422    }
423
424    pub fn update_rotation(&mut self, delta: f32) {
425        self.rotation += delta;
426    }
427
428    pub fn set_rotation(&mut self, rotation: f32) {
429        self.rotation = rotation;
430    }
431
432    pub fn window_id(&self) -> winit::window::WindowId {
433        self.window_id
434    }
435
436
437    pub fn render(&mut self) -> Result<(), SurfaceError> {
438        let output = self.surface.get_current_texture()?;
439        let texture_view = output.texture.create_view(&TextureViewDescriptor::default());
440
441        let depth_texture = self.device.create_texture(&TextureDescriptor {
442            size: Extent3d {
443                width: self.config.width,
444                height: self.config.height,
445                depth_or_array_layers: 1,
446            },
447            mip_level_count: 1,
448            sample_count: 1,
449            dimension: TextureDimension::D2,
450            format: TextureFormat::Depth32Float,
451            usage: TextureUsages::RENDER_ATTACHMENT,
452            view_formats: &[],
453            label: Some("Depth Texture"),
454        });
455
456        let depth_view = depth_texture.create_view(&TextureViewDescriptor::default());
457
458        let model = Matrix4::from_angle_y(Rad(self.rotation));
459        let view_matrix = self.camera.view_matrix();
460        let proj = self.camera.projection_matrix();
461
462        let uniforms = Uniforms {
463            model: model.into(),
464            view: view_matrix.into(),
465            proj: proj.into(),
466        };
467
468        self.queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
469
470        let mut encoder = self
471            .device
472            .create_command_encoder(&CommandEncoderDescriptor {
473                label: Some("Render Encoder"),
474            });
475
476        {
477            let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
478                label: Some("Render Pass"),
479                color_attachments: &[Some(RenderPassColorAttachment {
480                    view: &texture_view,
481                    resolve_target: None,
482                    ops: Operations {
483                        load: LoadOp::Clear(Color {
484                            r: 0.1,
485                            g: 0.1,
486                            b: 0.1,
487                            a: 1.0,
488                        }),
489                        store: StoreOp::Store,
490                    },
491                })],
492                depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
493                    view: &depth_view,
494                    depth_ops: Some(Operations {
495                        load: LoadOp::Clear(1.0),
496                        store: StoreOp::Store,
497                    }),
498                    stencil_ops: None,
499                }),
500                occlusion_query_set: None,
501                timestamp_writes: None,
502            });
503
504            render_pass.set_pipeline(&self.pipeline);
505            render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
506            render_pass.set_bind_group(1, &self.texture_bind_group, &[]);
507            render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
508            render_pass.set_index_buffer(self.index_buffer.slice(..), IndexFormat::Uint16);
509            render_pass.draw_indexed(0..36, 0, 0..1);
510        }
511
512        self.queue.submit(std::iter::once(encoder.finish()));
513        output.present();
514
515        Ok(())
516    }
517}