mod bind_group; mod debug_overlay; mod pipeline; mod shadow; mod types; pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS}; use crate::debug::DebugMode; use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer}; use crate::texture::{DitherTextures, FlowmapTexture}; use pipeline::{create_debug_lines_pipeline, create_main_pipeline, create_snow_clipmap_pipeline, create_wireframe_pipeline}; use std::cell::RefCell; pub struct Renderer { pub device: wgpu::Device, pub queue: wgpu::Queue, pub surface: wgpu::Surface<'static>, pub config: wgpu::SurfaceConfiguration, framebuffer: LowResFramebuffer, standard_pipeline: wgpu::RenderPipeline, snow_clipmap_pipeline: wgpu::RenderPipeline, wireframe_pipeline: Option, debug_lines_pipeline: Option, debug_overlay: Option, wireframe_supported: bool, uniform_buffer: wgpu::Buffer, bind_group_layout: wgpu::BindGroupLayout, bind_group: wgpu::BindGroup, quad_vb: wgpu::Buffer, quad_ib: wgpu::Buffer, quad_num_indices: u32, blit_pipeline: wgpu::RenderPipeline, blit_bind_group: wgpu::BindGroup, shadow_pipeline: Option, shadow_bind_group_layout: wgpu::BindGroupLayout, shadow_bind_group: Option, terrain_height_scale: f32, pub spotlights: Vec, pub shadow_bias: f32, shadow_map_texture: wgpu::Texture, shadow_map_view: wgpu::TextureView, shadow_map_sampler: wgpu::Sampler, dither_textures: Option, flowmap_texture: Option, blue_noise_view: wgpu::TextureView, blue_noise_sampler: wgpu::Sampler, dummy_snow_light_view: wgpu::TextureView, dummy_snow_light_sampler: wgpu::Sampler, snow_light_accumulation: Option, snow_light_bound: bool, } impl Renderer { pub async fn new( window: &sdl3::video::Window, render_scale: u32, ) -> Result> { let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends: wgpu::Backends::VULKAN, ..Default::default() }); let surface = unsafe { let target = wgpu::SurfaceTargetUnsafe::from_window(window)?; instance.create_surface_unsafe(target)? }; let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), force_fallback_adapter: false, }) .await .map_err(|_| "Failed to find adapter")?; let wireframe_supported = adapter.features().contains(wgpu::Features::POLYGON_MODE_LINE); let required_features = if wireframe_supported { wgpu::Features::POLYGON_MODE_LINE } else { wgpu::Features::empty() }; let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { required_features, ..Default::default() }) .await?; let size = window.size(); let config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: surface.get_capabilities(&adapter).formats[0], width: size.0, height: size.1, present_mode: wgpu::PresentMode::Fifo, alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![], desired_maximum_frame_latency: 2, }; surface.configure(&device, &config); let low_res_width = config.width / render_scale; let low_res_height = config.height / render_scale; let framebuffer = LowResFramebuffer::new(&device, low_res_width, low_res_height, config.format); let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Uniform Buffer"), size: std::mem::size_of::() as wgpu::BufferAddress, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); let dither_textures = match DitherTextures::load_octaves(&device, &queue) { Ok(textures) => { println!("Loaded dither textures successfully"); Some(textures) } Err(e) => { eprintln!( "Warning: Could not load dither textures: {}. Rendering may look incorrect.", e ); None } }; let flowmap_texture = match FlowmapTexture::load(&device, &queue, "textures/terrain_flowmap.exr") { Ok(texture) => { println!("Loaded terrain flowmap successfully"); Some(texture) } Err(e) => { eprintln!( "Warning: Could not load terrain flowmap: {}. Path lighting will not work.", e ); None } }; let blue_noise_data = image::open("textures/blue_noise.png") .expect("Failed to load blue noise texture") .to_luma8(); let blue_noise_size = blue_noise_data.dimensions(); let blue_noise_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("Blue Noise"), size: wgpu::Extent3d { width: blue_noise_size.0, height: blue_noise_size.1, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R8Unorm, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, view_formats: &[], }); queue.write_texture( wgpu::TexelCopyTextureInfo { texture: &blue_noise_texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, }, &blue_noise_data, wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(blue_noise_size.0), rows_per_image: Some(blue_noise_size.1), }, wgpu::Extent3d { width: blue_noise_size.0, height: blue_noise_size.1, depth_or_array_layers: 1, }, ); let blue_noise_view = blue_noise_texture.create_view(&wgpu::TextureViewDescriptor::default()); let blue_noise_sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("Blue Noise Sampler"), address_mode_u: wgpu::AddressMode::Repeat, address_mode_v: wgpu::AddressMode::Repeat, mag_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest, ..Default::default() }); let shadow_map_size = 4096; let shadow_map_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("Shadow Map"), size: wgpu::Extent3d { width: shadow_map_size, height: shadow_map_size, depth_or_array_layers: MAX_SPOTLIGHTS as u32, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); let shadow_map_view = shadow_map_texture.create_view(&wgpu::TextureViewDescriptor { dimension: Some(wgpu::TextureViewDimension::D2Array), ..Default::default() }); let shadow_map_sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("Shadow Map Sampler"), address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::MipmapFilterMode::Nearest, compare: Some(wgpu::CompareFunction::LessEqual), ..Default::default() }); let dummy_texture = device.create_texture(&wgpu::TextureDescriptor { label: Some("Dummy Snow Light"), size: wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1, }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R16Float, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, view_formats: &[], }); queue.write_texture( wgpu::TexelCopyTextureInfo { texture: &dummy_texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, }, &[0u8; 2], wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(2), rows_per_image: Some(1), }, wgpu::Extent3d { width: 1, height: 1, depth_or_array_layers: 1, }, ); let dummy_snow_light_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default()); let dummy_snow_light_sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("Snow Light Sampler"), address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, ..Default::default() }); let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Bind Group Layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Depth, view_dimension: wgpu::TextureViewDimension::D2Array, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison), count: None, }, wgpu::BindGroupLayoutEntry { binding: 3, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: false }, view_dimension: wgpu::TextureViewDimension::D2Array, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 4, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), count: None, }, wgpu::BindGroupLayoutEntry { binding: 5, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: false }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 6, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), count: None, }, wgpu::BindGroupLayoutEntry { binding: 7, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: false }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 8, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), count: None, }, wgpu::BindGroupLayoutEntry { binding: 9, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 10, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, ], }); let bind_group = Self::create_bind_group_from_parts( &device, &bind_group_layout, &uniform_buffer, &shadow_map_view, &shadow_map_sampler, dither_textures.as_ref().expect("Dither textures required"), flowmap_texture.as_ref().expect("Flowmap texture required"), &blue_noise_view, &blue_noise_sampler, &dummy_snow_light_view, &dummy_snow_light_sampler, ); let standard_pipeline = create_main_pipeline( &device, config.format, &bind_group_layout, wgpu::DepthBiasState::default(), "Standard Pipeline", ); let snow_clipmap_pipeline = create_snow_clipmap_pipeline(&device, config.format, &bind_group_layout); let (quad_vb, quad_ib, quad_num_indices) = create_fullscreen_quad(&device); let blit_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Blit Bind Group Layout"), entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, ], }); let blit_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("Blit Bind Group"), layout: &blit_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&framebuffer.view), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&framebuffer.sampler), }, ], }); let blit_pipeline = create_blit_pipeline(&device, config.format, &blit_bind_group_layout); let wireframe_pipeline = if wireframe_supported { Some(create_wireframe_pipeline(&device, config.format, &bind_group_layout)) } else { None }; let debug_lines_pipeline = Some(create_debug_lines_pipeline(&device, config.format, &bind_group_layout)); let debug_overlay = Some(debug_overlay::DebugOverlay::new(&device, config.format)); let shadow_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Shadow Bind Group Layout"), entries: &[wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, count: None, }], }); Ok(Self { device, queue, surface, config, framebuffer, standard_pipeline, snow_clipmap_pipeline, wireframe_pipeline, debug_lines_pipeline, debug_overlay, wireframe_supported, uniform_buffer, bind_group_layout, bind_group, quad_vb, quad_ib, quad_num_indices, blit_pipeline, blit_bind_group, shadow_pipeline: None, shadow_bind_group_layout, shadow_bind_group: None, terrain_height_scale: 10.0, spotlights: vec![Spotlight::new( glam::Vec3::new(0.0, 50.0, 0.0), glam::Vec3::new(-0.5, -1.0, 1.0).normalize(), 0.4, 1.0, 100.0, )], shadow_bias: 0.005, shadow_map_texture, shadow_map_view, shadow_map_sampler, dither_textures, flowmap_texture, blue_noise_view, blue_noise_sampler, dummy_snow_light_view, dummy_snow_light_sampler, snow_light_accumulation: None, snow_light_bound: false, }) } pub fn render( &mut self, view: &glam::Mat4, projection: &glam::Mat4, camera_position: glam::Vec3, player_position: glam::Vec3, draw_calls: &[DrawCall], time: f32, delta_time: f32, debug_mode: DebugMode, ) -> wgpu::SurfaceTexture { let light_view_projections = self.calculate_light_view_projections(); if let Some(ref mut snow_light_accum) = self.snow_light_accumulation { let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Snow Light Accumulation Encoder"), }); snow_light_accum.render( &mut encoder, &self.queue, &self.spotlights, delta_time, &light_view_projections, self.shadow_bias, self.terrain_height_scale, ); self.queue.submit(std::iter::once(encoder.finish())); } if self.snow_light_accumulation.is_some() && !self.snow_light_bound { self.rebuild_bind_group_with_snow_light(); self.snow_light_bound = true; } self.render_shadow_pass(draw_calls, &light_view_projections, player_position, time); let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder"), }); for (i, draw_call) in draw_calls.iter().enumerate() { let uniforms = Uniforms::new( draw_call.model, *view, *projection, &light_view_projections, camera_position, player_position, self.terrain_height_scale, time, self.shadow_bias, draw_call.tile_scale, draw_call.enable_dissolve, draw_call.enable_snow_light, &self.spotlights, debug_mode.as_u32(), ); self.queue .write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms])); { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("3D Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &self.framebuffer.view, resolve_target: None, ops: wgpu::Operations { load: if i == 0 { wgpu::LoadOp::Clear(wgpu::Color { r: 0.2, g: 0.2, b: 0.2, a: 1.0, }) } else { wgpu::LoadOp::Load }, store: wgpu::StoreOp::Store, }, depth_slice: None, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: &self.framebuffer.depth_view, depth_ops: Some(wgpu::Operations { load: if i == 0 { wgpu::LoadOp::Clear(1.0) } else { wgpu::LoadOp::Load }, store: wgpu::StoreOp::Store, }), stencil_ops: None, }), timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); let pipeline = match draw_call.pipeline { Pipeline::Standard => &self.standard_pipeline, Pipeline::SnowClipmap => &self.snow_clipmap_pipeline, Pipeline::DebugLines => self .debug_lines_pipeline .as_ref() .unwrap_or(&self.standard_pipeline), }; render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, &self.bind_group, &[]); if let Some(ref displacement_bind_group) = draw_call.displacement_bind_group { render_pass.set_bind_group(1, displacement_bind_group, &[]); } render_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..)); if let Some(ref instance_buffer) = draw_call.instance_buffer { render_pass.set_vertex_buffer(1, instance_buffer.slice(..)); } render_pass .set_index_buffer(draw_call.index_buffer.slice(..), wgpu::IndexFormat::Uint32); render_pass.draw_indexed(0..draw_call.num_indices, 0, 0..draw_call.num_instances); } } if debug_mode == DebugMode::Wireframe { if let Some(ref wireframe_pipeline) = self.wireframe_pipeline { for draw_call in draw_calls.iter() { if matches!(draw_call.pipeline, Pipeline::SnowClipmap | Pipeline::DebugLines) { continue; } let uniforms = Uniforms::new( draw_call.model, *view, *projection, &light_view_projections, camera_position, player_position, self.terrain_height_scale, time, self.shadow_bias, draw_call.tile_scale, draw_call.enable_dissolve, draw_call.enable_snow_light, &self.spotlights, 4, ); self.queue.write_buffer( &self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]), ); { let mut wire_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Wireframe Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &self.framebuffer.view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, depth_slice: None, })], depth_stencil_attachment: Some( wgpu::RenderPassDepthStencilAttachment { view: &self.framebuffer.depth_view, depth_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }), stencil_ops: None, }, ), timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); wire_pass.set_pipeline(wireframe_pipeline); wire_pass.set_bind_group(0, &self.bind_group, &[]); wire_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..)); if let Some(ref instance_buffer) = draw_call.instance_buffer { wire_pass.set_vertex_buffer(1, instance_buffer.slice(..)); } wire_pass.set_index_buffer( draw_call.index_buffer.slice(..), wgpu::IndexFormat::Uint32, ); wire_pass.draw_indexed( 0..draw_call.num_indices, 0, 0..draw_call.num_instances, ); } } } } if debug_mode == DebugMode::ShadowMap { if let Some(ref overlay) = self.debug_overlay { let shadow_view = &self.shadow_map_view; let framebuffer_view = &self.framebuffer.view; let quad_vb = &self.quad_vb; let quad_ib = &self.quad_ib; let quad_num = self.quad_num_indices; let device = &self.device; overlay.render_shadow_map( &mut encoder, framebuffer_view, shadow_view, quad_vb, quad_ib, quad_num, device, ); } } else if debug_mode == DebugMode::SnowLight { if let Some(ref overlay) = self.debug_overlay { let snow_light_view = self .snow_light_accumulation .as_ref() .map(|s| s.read_view()) .unwrap_or(&self.dummy_snow_light_view); let framebuffer_view = &self.framebuffer.view; let quad_vb = &self.quad_vb; let quad_ib = &self.quad_ib; let quad_num = self.quad_num_indices; let device = &self.device; let sampler = &self.dummy_snow_light_sampler; overlay.render_snow_light( &mut encoder, framebuffer_view, snow_light_view, sampler, quad_vb, quad_ib, quad_num, device, ); } } self.queue.submit(std::iter::once(encoder.finish())); let frame = match self.surface.get_current_texture() { Ok(frame) => frame, Err(_) => { self.surface.configure(&self.device, &self.config); self.surface .get_current_texture() .expect("Failed to acquire next surface texture") } }; let screen_view = frame .texture .create_view(&wgpu::TextureViewDescriptor::default()); let mut blit_encoder = self.device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Blit Encoder"), }); { let mut blit_pass = blit_encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Blit Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &screen_view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store, }, depth_slice: None, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); blit_pass.set_pipeline(&self.blit_pipeline); blit_pass.set_bind_group(0, &self.blit_bind_group, &[]); blit_pass.set_vertex_buffer(0, self.quad_vb.slice(..)); blit_pass.set_index_buffer(self.quad_ib.slice(..), wgpu::IndexFormat::Uint16); blit_pass.draw_indexed(0..self.quad_num_indices, 0, 0..1); } self.queue.submit(std::iter::once(blit_encoder.finish())); frame } pub fn render_scale(&self) -> (u32, u32) { ( self.config.width / self.framebuffer.width, self.config.height / self.framebuffer.height, ) } pub fn set_terrain_data(&mut self) { let shadow_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("Shadow Bind Group"), layout: &self.shadow_bind_group_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: self.uniform_buffer.as_entire_binding(), }], }); let shadow_pipeline = pipeline::create_shadow_pipeline(&self.device, &self.shadow_bind_group_layout); self.shadow_bind_group = Some(shadow_bind_group); self.shadow_pipeline = Some(shadow_pipeline); self.terrain_height_scale = 1.0; } pub fn init_snow_light_accumulation(&mut self, terrain_min: glam::Vec2, terrain_max: glam::Vec2) { let snow_light_accumulation = crate::snow_light::SnowLightAccumulation::new( &self.device, terrain_min, terrain_max, 512, ); self.snow_light_accumulation = Some(snow_light_accumulation); } pub fn set_snow_depth(&mut self, snow_depth_view: &wgpu::TextureView) { println!("set_snow_depth() called"); if let Some(ref mut snow_light_accum) = self.snow_light_accumulation { println!("Snow light accumulation exists, setting up bind groups"); let snow_depth_sampler = self.device.create_sampler(&wgpu::SamplerDescriptor { label: Some("Snow Depth Sampler"), address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, mag_filter: wgpu::FilterMode::Nearest, min_filter: wgpu::FilterMode::Nearest, ..Default::default() }); match crate::texture::HeightmapTexture::load( &self.device, &self.queue, "textures/terrain_heightmap.exr", ) { Ok(heightmap) => { snow_light_accum.set_heightmap( &self.device, &heightmap.view, &heightmap.sampler, &self.shadow_map_view, &self.shadow_map_sampler, snow_depth_view, &snow_depth_sampler, ); } Err(e) => { eprintln!("Failed to load heightmap for snow depth setup: {}", e); } } } } pub fn aspect_ratio(&self) -> f32 { self.config.width as f32 / self.config.height as f32 } } thread_local! { static GLOBAL_RENDERER: RefCell> = RefCell::new(None); } pub fn init(renderer: Renderer) { GLOBAL_RENDERER.with(|r| *r.borrow_mut() = Some(renderer)); } pub fn with_device(f: F) -> R where F: FnOnce(&wgpu::Device) -> R, { GLOBAL_RENDERER.with(|r| { let renderer = r.borrow(); let renderer = renderer.as_ref().expect("Renderer not set"); f(&renderer.device) }) } pub fn with_queue(f: F) -> R where F: FnOnce(&wgpu::Queue) -> R, { GLOBAL_RENDERER.with(|r| { let renderer = r.borrow(); let renderer = renderer.as_ref().expect("Renderer not set"); f(&renderer.queue) }) } pub fn set_terrain_data() { GLOBAL_RENDERER.with(|r| { let mut renderer = r.borrow_mut(); let renderer = renderer.as_mut().expect("Renderer not set"); renderer.set_terrain_data(); }); } pub fn init_snow_light_accumulation(terrain_min: glam::Vec2, terrain_max: glam::Vec2) { GLOBAL_RENDERER.with(|r| { let mut renderer = r.borrow_mut(); let renderer = renderer.as_mut().expect("Renderer not set"); renderer.init_snow_light_accumulation(terrain_min, terrain_max); }); } pub fn set_snow_depth(snow_depth_view: &wgpu::TextureView) { GLOBAL_RENDERER.with(|r| { let mut renderer = r.borrow_mut(); let renderer = renderer.as_mut().expect("Renderer not set"); renderer.set_snow_depth(snow_depth_view); }); } pub fn aspect_ratio() -> f32 { GLOBAL_RENDERER.with(|r| { let renderer = r.borrow(); let renderer = renderer.as_ref().expect("Renderer not set"); renderer.aspect_ratio() }) } pub fn render( view: &glam::Mat4, projection: &glam::Mat4, camera_position: glam::Vec3, player_position: glam::Vec3, draw_calls: &[DrawCall], time: f32, delta_time: f32, debug_mode: DebugMode, ) -> wgpu::SurfaceTexture { GLOBAL_RENDERER.with(|r| { let mut renderer = r.borrow_mut(); let renderer = renderer.as_mut().expect("Renderer not set"); renderer.render( view, projection, camera_position, player_position, draw_calls, time, delta_time, debug_mode, ) }) } pub fn with_surface_format(f: F) -> R where F: FnOnce(wgpu::TextureFormat) -> R, { GLOBAL_RENDERER.with(|r| { let renderer = r.borrow(); let renderer = renderer.as_ref().expect("Renderer not set"); f(renderer.config.format) }) } pub fn set_shadow_bias(bias: f32) { GLOBAL_RENDERER.with(|r| { let mut renderer = r.borrow_mut(); let renderer = renderer.as_mut().expect("Renderer not set"); renderer.shadow_bias = bias; }); } pub fn update_spotlights(spotlights: Vec) { GLOBAL_RENDERER.with(|r| { let mut renderer = r.borrow_mut(); let renderer = renderer.as_mut().expect("Renderer not set"); renderer.spotlights = spotlights; }); }