1use std::sync::Arc;
2use wgpu::{util::DeviceExt, Device, Queue, Surface, SurfaceConfiguration};
3use winit::window::Window;
4
5pub use crate::gpu_types::{
6 InstanceRaw, LightData, PostProcessUniforms, SceneUniforms, ShadowVsUniform, Vertex,
7};
8pub use crate::pipeline::SceneState;
9pub use crate::post_process::PostProcessState;
10
11pub struct RenderContext<'a> {
25 pub(crate) encoder: &'a mut wgpu::CommandEncoder,
26 pub(crate) view: &'a wgpu::TextureView,
27 pub(crate) renderer: &'a mut Renderer,
28 pub(crate) light_time: f32,
29}
30
31impl<'a> RenderContext<'a> {
32 pub fn new(
34 encoder: &'a mut wgpu::CommandEncoder,
35 view: &'a wgpu::TextureView,
36 renderer: &'a mut Renderer,
37 light_time: f32,
38 ) -> Self {
39 Self {
40 encoder,
41 view,
42 renderer,
43 light_time,
44 }
45 }
46
47 pub fn disable_gpu_compute(&mut self) {
50 self.renderer.gpu_fluid = None;
51 self.renderer.gpu_particles = None;
52 self.renderer.gpu_physics = None;
53 }
54
55 pub fn light_time(&self) -> f32 {
57 self.light_time
58 }
59
60 pub fn renderer(&self) -> &Renderer {
62 self.renderer
63 }
64
65 pub fn renderer_mut(&mut self) -> &mut Renderer {
67 self.renderer
68 }
69
70 pub fn encoder(&mut self) -> &mut wgpu::CommandEncoder {
72 self.encoder
73 }
74
75 pub fn output_view(&self) -> &wgpu::TextureView {
77 self.view
78 }
79
80 pub fn parts_mut(&mut self) -> (&mut wgpu::CommandEncoder, &wgpu::TextureView, &mut Renderer) {
83 (self.encoder, self.view, self.renderer)
84 }
85}
86
87pub struct Renderer {
88 pub surface: Surface<'static>,
90 pub device: Device,
91 pub queue: Queue,
92 pub config: SurfaceConfiguration,
93 pub size: winit::dpi::PhysicalSize<u32>,
94 pub depth_texture_view: wgpu::TextureView,
95
96 pub scene: SceneState,
98
99 pub post: PostProcessState,
101
102 pub gpu_particles: Option<crate::gpu_particles::GpuParticleSystem>,
104
105 pub gpu_physics: Option<crate::gpu_physics::GpuPhysicsSystem>,
106
107 pub gpu_fluid: Option<crate::gpu_fluid::GpuFluidSystem>,
109
110 pub deferred: Option<crate::deferred::DeferredState>,
112
113 pub gpu_cull: Option<crate::gpu_cull::GpuCullState>,
115
116 pub ssao: Option<crate::ssao::SsaoState>,
118
119 pub ssr: Option<crate::ssr::SsrState>,
121
122 pub ssgi: Option<crate::ssgi::SsgiState>,
124
125 pub volumetric: Option<crate::volumetric::VolumetricState>,
127
128 pub decal: Option<crate::decal::DecalState>,
130
131 pub taa: Option<crate::taa::TaaState>,
133
134 pub fxaa: Option<crate::fxaa::FxaaState>,
136
137 pub debug_renderer: Option<crate::debug_renderer::GizmoRendererSystem>,
139
140 pub asset_manager: std::sync::RwLock<crate::asset::AssetManager>,
142
143 pub web_profile: crate::web_profile::WebProfile,
145
146 pub shading_mode: u32,
148 pub environment_preset: u32,
149 pub environment_preset_2: u32,
150 pub environment_blend_t: f32,
151 pub bloom_intensity: f32,
152 pub bloom_threshold: f32,
153 pub exposure: f32,
154 pub dof_enabled: bool,
155 pub dof_focus_dist: f32,
156 pub dof_focus_range: f32,
157 pub dof_blur_size: f32,
158 pub chromatic_aberration: f32,
159 pub film_grain_intensity: f32,
160 pub point_shadows_enabled: bool,
161}
162
163impl Renderer {
164 pub fn load_shader(
165 device: &wgpu::Device,
166 file_path: &str,
167 fallback_src: &str,
168 label: &str,
169 ) -> wgpu::ShaderModule {
170 let source =
171 std::fs::read_to_string(file_path).unwrap_or_else(|_| fallback_src.to_string());
172 device.create_shader_module(wgpu::ShaderModuleDescriptor {
173 label: Some(label),
174 source: wgpu::ShaderSource::Wgsl(source.into()),
175 })
176 }
177
178 pub async fn new(window: Arc<Window>) -> Self {
179 let mut size = window.inner_size();
180 if size.width == 0 || size.height == 0 {
182 size = winit::dpi::PhysicalSize::new(1280, 720);
183 }
184
185 #[cfg(target_arch = "wasm32")]
186 {
187 if size.width > 640 || size.height > 360 {
190 let aspect = size.width as f32 / size.height as f32;
191 if aspect > 1.0 {
192 size.width = 640;
193 size.height = (640.0 / aspect) as u32;
194 } else {
195 size.height = 360;
196 size.width = (360.0 * aspect) as u32;
197 }
198 }
199 }
200
201 #[cfg(target_arch = "wasm32")]
202 let backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
203 #[cfg(not(target_arch = "wasm32"))]
204 let backends = wgpu::Backends::all();
205
206 log::info!("[Renderer] Window size: {}x{}", size.width, size.height);
207 log::info!("[Renderer] Backends: {:?}", backends);
208
209 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
210 backends,
211 ..Default::default()
212 });
213
214 #[cfg(not(target_arch = "wasm32"))]
216 {
217 let adapters = instance.enumerate_adapters(backends);
218 log::info!("[Renderer] {} adapter bulundu", adapters.len());
219 for (i, a) in adapters.iter().enumerate() {
220 let info = a.get_info();
221 log::info!(
222 "[Renderer] Adapter {}: {} ({:?}, {:?})",
223 i,
224 info.name,
225 info.backend,
226 info.device_type
227 );
228 }
229 }
230
231 let surface = instance
232 .create_surface(window.clone())
233 .expect("Surface oluşturulamadı!");
234
235 log::info!("[Renderer] Surface oluşturuldu, adapter aranıyor...");
236
237 let adapter = instance
238 .request_adapter(&wgpu::RequestAdapterOptions {
239 power_preference: wgpu::PowerPreference::default(),
240 compatible_surface: Some(&surface),
241 force_fallback_adapter: false,
242 })
243 .await;
244
245 let adapter = match adapter {
246 Some(a) => {
247 let info = a.get_info();
248 log::info!(
249 "[Renderer] Adapter bulundu: {} ({:?})",
250 info.name,
251 info.backend
252 );
253 a
254 }
255 None => {
256 log::warn!(
257 "[Renderer] Surface uyumlu adapter bulunamadı, surface'siz deneniyor..."
258 );
259 match instance
261 .request_adapter(&wgpu::RequestAdapterOptions {
262 power_preference: wgpu::PowerPreference::default(),
263 compatible_surface: None,
264 force_fallback_adapter: false,
265 })
266 .await
267 {
268 Some(a) => {
269 let info = a.get_info();
270 log::info!(
271 "[Renderer] Surface'siz adapter bulundu: {} ({:?})",
272 info.name,
273 info.backend
274 );
275 a
276 }
277 None => {
278 log::error!(
279 "[Renderer] Hiçbir adapter bulunamadı! Backends: {:?}",
280 backends
281 );
282 panic!(
283 "GPU adapter bulunamadı! Backends: {:?}, Window size: {}x{}",
284 backends, size.width, size.height
285 );
286 }
287 }
288 }
289 };
290
291 #[cfg(not(target_arch = "wasm32"))]
292 let (device, queue) = adapter
293 .request_device(
294 &wgpu::DeviceDescriptor {
295 required_features: wgpu::Features::POLYGON_MODE_LINE,
296 required_limits: wgpu::Limits {
297 max_bind_groups: 6,
298 max_storage_buffers_per_shader_stage: 8,
299 max_storage_buffer_binding_size: 256 << 20, ..wgpu::Limits::default()
301 },
302 label: None,
303 },
304 None,
305 )
306 .await
307 .unwrap();
308
309 #[cfg(target_arch = "wasm32")]
310 let (device, queue) = adapter
311 .request_device(
312 &wgpu::DeviceDescriptor {
313 required_features: wgpu::Features::empty(),
314 required_limits: wgpu::Limits {
315 max_bind_groups: 4,
316 max_storage_buffers_per_shader_stage: 8,
317 max_storage_buffer_binding_size: 128 << 20, ..wgpu::Limits::downlevel_webgl2_defaults()
319 .using_resolution(adapter.limits())
320 },
321 label: None,
322 },
323 None,
324 )
325 .await
326 .unwrap();
327
328 let surface_caps = surface.get_capabilities(&adapter);
329 let surface_format = surface_caps
330 .formats
331 .iter()
332 .copied()
333 .find(|f| f.is_srgb())
334 .unwrap_or(surface_caps.formats[0]);
335
336 let config = wgpu::SurfaceConfiguration {
337 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
338 format: surface_format,
339 width: size.width,
340 height: size.height,
341 present_mode: wgpu::PresentMode::AutoNoVsync,
343 alpha_mode: surface_caps.alpha_modes[0],
344 view_formats: vec![],
345 desired_maximum_frame_latency: 2,
346 };
347 surface.configure(&device, &config);
348
349 let depth_texture_view = Self::create_depth_texture(&device, config.width, config.height);
350
351 let scene = crate::pipeline::build_scene_pipelines(&device);
352 let post_res = crate::post_process::build_post_process_resources(
353 &device,
354 surface_format,
355 config.width,
356 config.height,
357 &depth_texture_view,
358 );
359
360 #[cfg(not(target_arch = "wasm32"))]
362 let gpu_particles = {
363 let max_particles: u32 = 100_000;
364 Some(crate::gpu_particles::GpuParticleSystem::new(
365 &device,
366 max_particles,
367 &scene.global_bind_group_layout,
368 wgpu::TextureFormat::Rgba16Float,
369 ))
370 };
371 #[cfg(target_arch = "wasm32")]
372 let gpu_particles: Option<crate::gpu_particles::GpuParticleSystem> = None;
373
374 #[cfg(not(target_arch = "wasm32"))]
375 let gpu_physics = {
376 let max_physics_spheres: u32 = 50_000;
377 let mut physics = crate::gpu_physics::GpuPhysicsSystem::new(
378 &device,
379 max_physics_spheres,
380 &scene.global_bind_group_layout,
381 wgpu::TextureFormat::Rgba16Float,
382 wgpu::TextureFormat::Depth32Float,
383 );
384 physics.enable_debug(&device, 0);
385 Some(physics)
386 };
387 #[cfg(target_arch = "wasm32")]
388 let gpu_physics: Option<crate::gpu_physics::GpuPhysicsSystem> = None;
389
390 #[cfg(not(target_arch = "wasm32"))]
391 let gpu_fluid = Some(crate::gpu_fluid::GpuFluidSystem::new(
392 &device,
393 &queue,
394 100_000,
395 &scene.global_bind_group_layout,
396 post_res.hdr_texture.format(),
397 config.width,
398 config.height,
399 ));
400 #[cfg(target_arch = "wasm32")]
401 let gpu_fluid: Option<crate::gpu_fluid::GpuFluidSystem> = None;
402 let debug_renderer = Some(crate::debug_renderer::GizmoRendererSystem::new(
403 &device,
404 &scene.global_bind_group_layout,
405 wgpu::TextureFormat::Rgba16Float,
406 wgpu::TextureFormat::Depth32Float,
407 ));
408
409 let scene_state = SceneState {
410 render_pipeline: scene.render_pipeline,
411 render_double_sided_pipeline: scene.render_double_sided_pipeline,
412 wireframe_pipeline: scene.wireframe_pipeline,
413 unlit_pipeline: scene.unlit_pipeline,
414 sky_pipeline: scene.sky_pipeline,
415 water_pipeline: scene.water_pipeline,
416 shadow_pipeline: scene.shadow_pipeline,
417 transparent_pipeline: scene.transparent_pipeline,
418 grid_pipeline: scene.grid_pipeline,
419 shadow_texture_view: scene.shadow_texture_view,
420 shadow_cascade_layer_views: scene.shadow_cascade_layer_views,
421 shadow_depth_texture: scene.shadow_depth_texture,
422 point_shadow_depth_texture: scene.point_shadow_depth_texture,
423 point_shadow_cube_view: scene.point_shadow_cube_view,
424 point_shadow_face_views: scene.point_shadow_face_views,
425 shadow_pass_bind_group_layout: scene.shadow_pass_bind_group_layout,
426 shadow_cascade_uniform_buffers: scene.shadow_cascade_uniform_buffers,
427 shadow_pass_bind_groups: scene.shadow_pass_bind_groups,
428 point_shadow_uniform_buffers: scene.point_shadow_uniform_buffers,
429 point_shadow_pass_bind_groups: scene.point_shadow_pass_bind_groups,
430 global_uniform_buffer: scene.global_uniform_buffer,
431 global_bind_group_layout: scene.global_bind_group_layout,
432 global_bind_group: scene.global_bind_group,
433 shadow_bind_group_layout: scene.shadow_bind_group_layout,
434 shadow_bind_group: scene.shadow_bind_group,
435 texture_bind_group_layout: scene.texture_bind_group_layout,
436 skeleton_bind_group_layout: scene.skeleton_bind_group_layout,
437 dummy_skeleton_bind_group: scene.dummy_skeleton_bind_group,
438 instance_bind_group_layout: scene.instance_bind_group_layout,
439 instance_buffer: scene.instance_buffer,
440 instance_bind_group: scene.instance_bind_group,
441 instance_capacity: scene.instance_capacity,
442 };
443
444 #[cfg(not(target_arch = "wasm32"))]
445 let deferred = Some(crate::deferred::DeferredState::new(
446 &device,
447 &scene_state,
448 size.width,
449 size.height,
450 ));
451 #[cfg(target_arch = "wasm32")]
452 let deferred: Option<crate::deferred::DeferredState> = None;
453
454 #[cfg(not(target_arch = "wasm32"))]
455 let gpu_cull = Some(crate::gpu_cull::GpuCullState::new(
456 &device,
457 &scene_state,
458 scene_state.instance_capacity as u32,
459 ));
460 #[cfg(target_arch = "wasm32")]
461 let gpu_cull: Option<crate::gpu_cull::GpuCullState> = None;
462
463 let ssao = deferred.as_ref().map(|def| {
464 crate::ssao::SsaoState::new(&device, &queue, &scene_state, def, size.width, size.height)
465 });
466
467 let ssr = deferred.as_ref().map(|def| {
468 crate::ssr::SsrState::new(
469 &device,
470 &scene_state,
471 def,
472 &post_res.hdr_texture_view,
473 size.width,
474 size.height,
475 )
476 });
477
478 let ssgi = deferred.as_ref().map(|def| {
479 crate::ssgi::SsgiState::new(
480 &device,
481 &scene_state,
482 def,
483 &post_res.hdr_texture_view,
484 size.width,
485 size.height,
486 )
487 });
488
489 let volumetric = deferred.as_ref().map(|def| {
490 crate::volumetric::VolumetricState::new(
491 &device,
492 &scene_state,
493 def,
494 size.width,
495 size.height,
496 )
497 });
498
499 let decal = deferred
500 .as_ref()
501 .map(|def| crate::decal::DecalState::new(&device, &scene_state, def));
502
503 let taa = if let Some(ref def) = deferred {
504 Some(crate::taa::TaaState::new(
505 &device,
506 &post_res.hdr_texture_view,
507 &def.world_position_view,
508 size.width,
509 size.height,
510 ))
511 } else {
512 None
513 };
514
515 let post_state = PostProcessState {
516 hdr_texture: post_res.hdr_texture,
517 hdr_texture_view: post_res.hdr_texture_view,
518 hdr_bind_group: post_res.hdr_bind_group,
519 bloom_extract_texture_view: post_res.bloom_extract_texture_view,
520 bloom_extract_bind_group: post_res.bloom_extract_bind_group,
521 bloom_blur_texture_view: post_res.bloom_blur_texture_view,
522 bloom_blur_bind_group: post_res.bloom_blur_bind_group,
523 post_bind_group_layout: post_res.post_bind_group_layout,
524 bloom_extract_pipeline: post_res.bloom_extract_pipeline,
525 bloom_blur_pipeline: post_res.bloom_blur_pipeline,
526 composite_pipeline: post_res.composite_pipeline,
527 blur_params_buffer: post_res.blur_params_buffer,
528 blur_params_bind_group_layout: post_res.blur_params_bind_group_layout,
529 blur_h_bind_group: post_res.blur_h_bind_group,
530 blur_v_bind_group: post_res.blur_v_bind_group,
531 composite_bloom_bind_group_layout: post_res.composite_bloom_bind_group_layout,
532 composite_bloom_bind_group: post_res.composite_bloom_bind_group,
533 post_params_buffer: post_res.post_params_buffer,
534 post_params_bind_group_layout: post_res.post_params_bind_group_layout,
535 post_params_bind_group: post_res.post_params_bind_group,
536 };
537
538 let fxaa = Some(crate::fxaa::FxaaState::new(
540 &device,
541 config.format,
542 size.width,
543 size.height,
544 ));
545
546 Self {
547 surface,
548 device,
549 queue,
550 config,
551 size,
552 depth_texture_view,
553 scene: scene_state,
554 post: post_state,
555 deferred,
556 gpu_cull,
557 ssao,
558 ssr,
559 ssgi,
560 volumetric,
561 decal,
562 taa,
563 fxaa,
564 gpu_particles,
565 gpu_physics,
566 gpu_fluid,
567 debug_renderer,
568 asset_manager: std::sync::RwLock::new(crate::asset::AssetManager::new()),
569 web_profile: crate::web_profile::WebProfile::auto(),
570 shading_mode: 0,
571 environment_preset: 0,
572 environment_preset_2: 0,
573 environment_blend_t: 0.0,
574 bloom_intensity: 0.8,
575 bloom_threshold: 0.85,
576 exposure: 1.15,
577 dof_enabled: true,
578 dof_focus_dist: 4.5, dof_focus_range: 2.0, dof_blur_size: 4.0, chromatic_aberration: 0.15, film_grain_intensity: 0.03, point_shadows_enabled: false,
584 }
585 }
586
587 pub fn rebuild_shaders(&mut self) {
588 tracing::info!("🚀 Rebuilding Shaders Pipeline...");
589 crate::pipeline::rebuild_pipelines(self);
590 }
591
592 pub fn ensure_instance_capacity(&mut self, needed: usize) -> bool {
593 self.scene.ensure_instance_capacity(&self.device, needed)
594 }
595
596 pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
597 if new_size.width > 0 && new_size.height > 0 {
598 self.size = new_size;
599 self.config.width = new_size.width;
600 self.config.height = new_size.height;
601 self.surface.configure(&self.device, &self.config);
602
603 self.depth_texture_view =
604 Self::create_depth_texture(&self.device, new_size.width, new_size.height);
605
606 if let Some(ref mut def) = self.deferred {
607 def.resize(&self.device, new_size.width, new_size.height);
608 if let Some(ref mut decal) = self.decal {
609 decal.resize(&self.device, def);
610 }
611 }
612
613 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
614 address_mode_u: wgpu::AddressMode::ClampToEdge,
615 address_mode_v: wgpu::AddressMode::ClampToEdge,
616 mag_filter: wgpu::FilterMode::Linear,
617 min_filter: wgpu::FilterMode::Linear,
618 ..Default::default()
619 });
620 let (hdr_t, hdr_tv, hdr_bg, be_tv, be_bg, bb_tv, bb_bg, cb_bg) =
621 crate::post_process::create_post_textures(
622 &self.device,
623 &self.post.post_bind_group_layout,
624 &self.post.composite_bloom_bind_group_layout,
625 &sampler,
626 new_size.width,
627 new_size.height,
628 &self.depth_texture_view,
629 );
630 self.post.hdr_texture = hdr_t;
631 self.post.hdr_texture_view = hdr_tv;
632 self.post.hdr_bind_group = hdr_bg;
633 self.post.bloom_extract_texture_view = be_tv;
634 self.post.bloom_extract_bind_group = be_bg;
635 self.post.bloom_blur_texture_view = bb_tv;
636 self.post.bloom_blur_bind_group = bb_bg;
637 self.post.composite_bloom_bind_group = cb_bg;
638
639 let (buf, h_bg, v_bg) = crate::post_process::create_blur_buffers(
640 &self.device,
641 &self.post.blur_params_bind_group_layout,
642 new_size.width,
643 new_size.height,
644 );
645 self.post.blur_params_buffer = buf;
646 self.post.blur_h_bind_group = h_bg;
647 self.post.blur_v_bind_group = v_bg;
648
649 if let (Some(ref mut taa), Some(ref def)) = (&mut self.taa, &self.deferred) {
651 taa.resize(
652 &self.device,
653 &self.post.hdr_texture_view,
654 &def.world_position_view,
655 new_size.width,
656 new_size.height,
657 );
658 }
659 if let (Some(ref mut ssgi), Some(ref def)) = (&mut self.ssgi, &self.deferred) {
660 ssgi.resize(
661 &self.device,
662 def,
663 &self.post.hdr_texture_view,
664 new_size.width,
665 new_size.height,
666 );
667 }
668 if let (Some(ref mut ssao), Some(ref def)) = (&mut self.ssao, &self.deferred) {
669 ssao.resize(
670 &self.device,
671 def,
672 new_size.width,
673 new_size.height,
674 );
675 }
676 if let (Some(ref mut vol), Some(ref def)) = (&mut self.volumetric, &self.deferred) {
677 vol.resize(
678 &self.device,
679 def,
680 new_size.width,
681 new_size.height,
682 );
683 }
684 if let Some(ref mut fxaa) = self.fxaa {
686 fxaa.resize(&self.device, &self.queue, self.config.format, new_size.width, new_size.height);
687 }
688 }
689 }
690
691 pub fn create_cube(&self) -> crate::components::Mesh {
699 crate::asset::AssetManager::create_cube(&self.device)
700 }
701
702 pub fn create_sphere(&self, radius: f32, stacks: u32, slices: u32) -> crate::components::Mesh {
704 crate::asset::AssetManager::create_sphere(&self.device, radius, stacks, slices)
705 }
706
707 pub fn create_plane(&self, size: f32) -> crate::components::Mesh {
709 crate::asset::AssetManager::create_plane(&self.device, size)
710 }
711
712 pub fn create_checkerboard_texture(&self) -> Arc<wgpu::BindGroup> {
715 self.asset_manager
716 .write()
717 .unwrap()
718 .create_checkerboard_texture(
719 &self.device,
720 &self.queue,
721 &self.scene.texture_bind_group_layout,
722 )
723 }
724
725 pub fn create_white_texture(&self) -> Arc<wgpu::BindGroup> {
728 self.asset_manager.write().unwrap().create_white_texture(
729 &self.device,
730 &self.queue,
731 &self.scene.texture_bind_group_layout,
732 )
733 }
734
735 pub fn load_texture(&self, path: &str) -> Result<Arc<wgpu::BindGroup>, String> {
738 self.asset_manager.write().unwrap().load_material_texture(
739 &self.device,
740 &self.queue,
741 &self.scene.texture_bind_group_layout,
742 path,
743 )
744 }
745
746 pub fn load_gltf(&self, path: &str) -> Result<crate::asset::loaders::GltfSceneAsset, String> {
748 let white_tex = self.create_white_texture();
749 self.asset_manager.write().unwrap().load_gltf_scene(
750 &self.device,
751 &self.queue,
752 &self.scene.texture_bind_group_layout,
753 white_tex,
754 path,
755 )
756 }
757
758 pub fn create_skeleton(
759 &self,
760 hierarchy: std::sync::Arc<crate::animation::SkeletonHierarchy>,
761 ) -> crate::components::Skeleton {
762 use wgpu::util::DeviceExt;
763
764 let local_poses: Vec<gizmo_math::Mat4> = hierarchy
766 .joints
767 .iter()
768 .map(|j| j.local_bind_transform)
769 .collect();
770
771 let global_matrices = hierarchy.calculate_global_matrices(&local_poses);
773 let mut joint_matrices = vec![gizmo_math::Mat4::IDENTITY; 128];
774 for (i, joint) in hierarchy.joints.iter().enumerate() {
775 if i < 128 {
776 joint_matrices[i] = global_matrices[i] * joint.inverse_bind_matrix;
777 }
778 }
779
780 let buffer = self
781 .device
782 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
783 label: Some("Skeleton Joint Buffer"),
784 contents: bytemuck::cast_slice(&joint_matrices),
785 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
786 });
787
788 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
789 label: Some("Skeleton Bind Group"),
790 layout: &self.scene.skeleton_bind_group_layout,
791 entries: &[wgpu::BindGroupEntry {
792 binding: 0,
793 resource: buffer.as_entire_binding(),
794 }],
795 });
796
797 crate::components::Skeleton::new(
798 std::sync::Arc::new(bind_group),
799 std::sync::Arc::new(buffer),
800 hierarchy,
801 local_poses,
802 )
803 }
804
805 pub fn run_post_processing(
806 &self,
807 encoder: &mut wgpu::CommandEncoder,
808 output_view: &wgpu::TextureView,
809 ) {
810 if let Some(ref fxaa) = self.fxaa {
811 if fxaa.enabled {
812 crate::post_process::run_post_processing(self, encoder, &fxaa.input_texture_view);
814 crate::fxaa::run_fxaa_pass(fxaa, encoder, output_view);
815 return;
816 }
817 }
818 crate::post_process::run_post_processing(self, encoder, output_view);
820 }
821
822 pub fn update_post_process(&self, queue: &wgpu::Queue, params: PostProcessUniforms) {
823 queue.write_buffer(
824 &self.post.post_params_buffer,
825 0,
826 bytemuck::cast_slice(&[params]),
827 );
828 }
829
830 pub fn create_mesh(&self, vertices: &[Vertex]) -> wgpu::Buffer {
831 self.device
832 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
833 label: Some("Mesh Vertex Buffer"),
834 contents: bytemuck::cast_slice(vertices),
835 usage: wgpu::BufferUsages::VERTEX,
836 })
837 }
838
839 pub fn create_texture(&self, rgba_bytes: &[u8], width: u32, height: u32) -> wgpu::BindGroup {
840 let mip_level_count = width.max(height).ilog2() + 1;
841 let size = wgpu::Extent3d {
842 width,
843 height,
844 depth_or_array_layers: 1,
845 };
846 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
847 label: Some("Game Texture"),
848 size,
849 mip_level_count,
850 sample_count: 1,
851 dimension: wgpu::TextureDimension::D2,
852 format: wgpu::TextureFormat::Rgba8UnormSrgb,
853 usage: wgpu::TextureUsages::TEXTURE_BINDING
854 | wgpu::TextureUsages::COPY_DST
855 | wgpu::TextureUsages::RENDER_ATTACHMENT,
856 view_formats: &[],
857 });
858 self.queue.write_texture(
859 wgpu::ImageCopyTexture {
860 texture: &texture,
861 mip_level: 0,
862 origin: wgpu::Origin3d::ZERO,
863 aspect: wgpu::TextureAspect::All,
864 },
865 rgba_bytes,
866 wgpu::ImageDataLayout {
867 offset: 0,
868 bytes_per_row: Some(4 * width),
869 rows_per_image: Some(height),
870 },
871 size,
872 );
873
874 Self::generate_mipmaps(
875 &self.device,
876 &self.queue,
877 &texture,
878 wgpu::TextureFormat::Rgba8UnormSrgb,
879 mip_level_count,
880 );
881 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
882 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
883 address_mode_u: wgpu::AddressMode::Repeat,
884 address_mode_v: wgpu::AddressMode::Repeat,
885 address_mode_w: wgpu::AddressMode::Repeat,
886 mag_filter: wgpu::FilterMode::Linear,
887 min_filter: wgpu::FilterMode::Linear,
888 mipmap_filter: wgpu::FilterMode::Linear,
889 ..Default::default()
890 });
891 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
892 layout: &self.scene.texture_bind_group_layout,
893 entries: &[
894 wgpu::BindGroupEntry {
895 binding: 0,
896 resource: wgpu::BindingResource::TextureView(&view),
897 },
898 wgpu::BindGroupEntry {
899 binding: 1,
900 resource: wgpu::BindingResource::Sampler(&sampler),
901 },
902 ],
903 label: Some("texture_bind_group"),
904 })
905 }
906
907 fn create_depth_texture(device: &wgpu::Device, width: u32, height: u32) -> wgpu::TextureView {
908 let tex = device.create_texture(&wgpu::TextureDescriptor {
909 label: Some("Depth Texture"),
910 size: wgpu::Extent3d {
911 width,
912 height,
913 depth_or_array_layers: 1,
914 },
915 mip_level_count: 1,
916 sample_count: 1,
917 dimension: wgpu::TextureDimension::D2,
918 format: wgpu::TextureFormat::Depth32Float,
919 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
920 view_formats: &[],
921 });
922 tex.create_view(&wgpu::TextureViewDescriptor::default())
923 }
924
925 fn generate_mipmaps(
926 device: &wgpu::Device,
927 queue: &wgpu::Queue,
928 texture: &wgpu::Texture,
929 format: wgpu::TextureFormat,
930 mip_level_count: u32,
931 ) {
932 if mip_level_count <= 1 {
933 return;
934 }
935
936 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
937 label: Some("Mipmap Blit Shader"),
938 source: wgpu::ShaderSource::Wgsl(include_str!("shaders/mipmap.wgsl").into()),
939 });
940
941 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
942 label: Some("Mipmap Blit Pipeline"),
943 layout: None,
944 vertex: wgpu::VertexState {
945 module: &shader,
946 entry_point: "vs_main",
947 compilation_options: Default::default(),
948 buffers: &[],
949 },
950 fragment: Some(wgpu::FragmentState {
951 module: &shader,
952 entry_point: "fs_main",
953 compilation_options: Default::default(),
954 targets: &[Some(wgpu::ColorTargetState {
955 format,
956 blend: None,
957 write_mask: wgpu::ColorWrites::ALL,
958 })],
959 }),
960 primitive: wgpu::PrimitiveState {
961 topology: wgpu::PrimitiveTopology::TriangleList,
962 ..Default::default()
963 },
964 depth_stencil: None,
965 multisample: wgpu::MultisampleState::default(),
966 multiview: None,
967 });
968
969 let bind_group_layout = pipeline.get_bind_group_layout(0);
970 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
971 address_mode_u: wgpu::AddressMode::ClampToEdge,
972 address_mode_v: wgpu::AddressMode::ClampToEdge,
973 address_mode_w: wgpu::AddressMode::ClampToEdge,
974 mag_filter: wgpu::FilterMode::Linear,
975 min_filter: wgpu::FilterMode::Linear,
976 mipmap_filter: wgpu::FilterMode::Nearest,
977 ..Default::default()
978 });
979
980 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
981 label: Some("Mipmap Encoder"),
982 });
983
984 let views: Vec<wgpu::TextureView> = (0..mip_level_count)
985 .map(|mip| {
986 texture.create_view(&wgpu::TextureViewDescriptor {
987 label: Some(&format!("Mip {}", mip)),
988 format: None,
989 dimension: None,
990 aspect: wgpu::TextureAspect::All,
991 base_mip_level: mip,
992 mip_level_count: Some(1),
993 base_array_layer: 0,
994 array_layer_count: None,
995 })
996 })
997 .collect();
998
999 for target_mip in 1..mip_level_count as usize {
1000 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1001 layout: &bind_group_layout,
1002 entries: &[
1003 wgpu::BindGroupEntry {
1004 binding: 0,
1005 resource: wgpu::BindingResource::TextureView(&views[target_mip - 1]),
1006 },
1007 wgpu::BindGroupEntry {
1008 binding: 1,
1009 resource: wgpu::BindingResource::Sampler(&sampler),
1010 },
1011 ],
1012 label: None,
1013 });
1014
1015 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1016 label: None,
1017 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1018 view: &views[target_mip],
1019 resolve_target: None,
1020 ops: wgpu::Operations {
1021 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1022 store: wgpu::StoreOp::Store,
1023 },
1024 })],
1025 depth_stencil_attachment: None,
1026 timestamp_writes: None,
1027 occlusion_query_set: None,
1028 });
1029 pass.set_pipeline(&pipeline);
1030 pass.set_bind_group(0, &bind_group, &[]);
1031 pass.draw(0..3, 0..1);
1032 }
1033
1034 queue.submit(Some(encoder.finish()));
1035 }
1036}
1037
1038#[cfg(test)]
1039mod tests {
1040 use super::*;
1041
1042 #[test]
1043 fn test_mipmap_level_calculation() {
1044 let width = 4096u32;
1045 let height = 2048u32;
1046 let mip_level_count = width.max(height).ilog2() + 1;
1047 assert_eq!(mip_level_count, 13); let width2 = 512u32;
1050 let height2 = 512u32;
1051 assert_eq!(width2.max(height2).ilog2() + 1, 10);
1052 }
1053
1054 #[test]
1055 fn test_environment_preset_ranges() {
1056 let renderer_presets = vec![0, 1, 2, 3];
1058 for preset in &renderer_presets {
1059 assert!(*preset < 4, "Preset ID {} exceeds maximum allowed atmospheric preset index 3!", preset);
1060 }
1061 }
1062
1063 #[test]
1064 fn test_environment_blend_weight_clamping() {
1065 let input_weights = vec![-0.5f32, 0.0f32, 0.45f32, 1.0f32, 1.5f32];
1067 let expected_clamps = vec![0.0f32, 0.0f32, 0.45f32, 1.0f32, 1.0f32];
1068 for (input, expected) in input_weights.into_iter().zip(expected_clamps) {
1069 let clamped = input.clamp(0.0, 1.0);
1070 assert_eq!(clamped, expected, "Clamped weight of {} did not match expected value {}!", input, expected);
1071 }
1072 }
1073
1074 #[test]
1075 fn test_gpu_uniform_struct_sizes() {
1076 assert_eq!(std::mem::size_of::<crate::gpu_types::SceneUniforms>(), 1104, "SceneUniforms size shifted from target 1104 bytes!");
1078 assert_eq!(std::mem::size_of::<crate::gpu_types::LightData>(), 64, "LightData size shifted from target 64 bytes!");
1079 assert_eq!(std::mem::size_of::<crate::gpu_types::PostProcessUniforms>(), 48, "PostProcessUniforms size shifted from target 48 bytes!");
1080 assert_eq!(std::mem::size_of::<crate::gpu_types::InstanceRaw>(), 96, "InstanceRaw size shifted from target 96 bytes!");
1081 }
1082
1083 #[test]
1084 fn test_headless_mipmap_generation() {
1085 pollster::block_on(async {
1086 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
1087 backends: wgpu::Backends::all(),
1088 ..Default::default()
1089 });
1090
1091 let adapter = instance
1092 .request_adapter(&wgpu::RequestAdapterOptions {
1093 power_preference: wgpu::PowerPreference::LowPower,
1094 compatible_surface: None,
1095 force_fallback_adapter: false,
1096 })
1097 .await;
1098
1099 let adapter = match adapter {
1100 Some(a) => a,
1101 None => {
1102 tracing::info!(
1103 "No suitable GPU adapter found for headless test. Skipping wgpu test."
1104 );
1105 return;
1106 }
1107 };
1108
1109 let (device, queue) = adapter
1110 .request_device(
1111 &wgpu::DeviceDescriptor {
1112 required_features: wgpu::Features::empty(),
1113 required_limits: wgpu::Limits::downlevel_defaults(),
1114 label: None,
1115 },
1116 None,
1117 )
1118 .await
1119 .unwrap();
1120
1121 let width = 256u32;
1122 let height = 256u32;
1123 let mip_level_count = width.max(height).ilog2() + 1;
1124
1125 let texture = device.create_texture(&wgpu::TextureDescriptor {
1126 label: Some("Test Texture"),
1127 size: wgpu::Extent3d {
1128 width,
1129 height,
1130 depth_or_array_layers: 1,
1131 },
1132 mip_level_count,
1133 sample_count: 1,
1134 dimension: wgpu::TextureDimension::D2,
1135 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1136 usage: wgpu::TextureUsages::TEXTURE_BINDING
1137 | wgpu::TextureUsages::COPY_DST
1138 | wgpu::TextureUsages::RENDER_ATTACHMENT,
1139 view_formats: &[],
1140 });
1141
1142 Renderer::generate_mipmaps(
1144 &device,
1145 &queue,
1146 &texture,
1147 wgpu::TextureFormat::Rgba8UnormSrgb,
1148 mip_level_count,
1149 );
1150
1151 device.poll(wgpu::Maintain::Wait);
1152 });
1153 }
1154}