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