Skip to main content

gizmo_renderer/
renderer.rs

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
11// ============================================================
12//  RenderContext — wgpu detaylarını kullanıcıdan gizler
13// ============================================================
14
15/// Kullanıcı kodunun doğrudan `wgpu::CommandEncoder` veya `wgpu::TextureView`
16/// görmesine gerek kalmadan render işlemi yapmasını sağlayan bağlam nesnesi.
17///
18/// ```ignore
19/// fn render(world: &mut World, _state: &GameState, ctx: &mut RenderContext) {
20///     ctx.disable_gpu_compute();           // GPU Compute kapalı
21///     ctx.default_render(world);           // Varsayılan render pipeline
22/// }
23/// ```
24pub 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    /// Yeni bir RenderContext oluşturur (motor tarafından dahili olarak çağrılır).
33    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    /// GPU Compute alt sistemlerini devre dışı bırakır (fluid, particles, physics).
48    /// Basit sahnelerde gereksiz GPU iş yükünü sıfırlar.
49    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    /// Mevcut sahne ışık zamanını döndürür (saniye).
56    pub fn light_time(&self) -> f32 {
57        self.light_time
58    }
59
60    /// Renderer'a doğrudan erişim (ileri düzey kullanım).
61    pub fn renderer(&self) -> &Renderer {
62        self.renderer
63    }
64
65    /// Renderer'a mutable erişim (ileri düzey kullanım).
66    pub fn renderer_mut(&mut self) -> &mut Renderer {
67        self.renderer
68    }
69
70    /// İleri düzey kullanım: ham wgpu encoder'a erişim.
71    pub fn encoder(&mut self) -> &mut wgpu::CommandEncoder {
72        self.encoder
73    }
74
75    /// İleri düzey kullanım: çıkış texture view'ına erişim.
76    pub fn output_view(&self) -> &wgpu::TextureView {
77        self.view
78    }
79
80    /// Dahili bileşenlere eşzamanlı erişim — `default_render_pass` gibi
81    /// fonksiyonlara geçirmek için kullanılır.
82    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    // === TEMEL WGPU KAYNAKLARI ===
89    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    // === SAHNE (Scene) — Pipeline'lar, Shadow, Skeleton ===
97    pub scene: SceneState,
98
99    // === POST-PROCESSING — HDR, Bloom, Blur, Composite ===
100    pub post: PostProcessState,
101
102    // === PARTİKÜL SİSTEMİ ===
103    pub gpu_particles: Option<crate::gpu_particles::GpuParticleSystem>,
104
105    pub gpu_physics: Option<crate::gpu_physics::GpuPhysicsSystem>,
106
107    // === GPU SIVI SİSTEMİ ===
108    pub gpu_fluid: Option<crate::gpu_fluid::GpuFluidSystem>,
109
110    // === DEFERRED RENDERING — G-Buffer + Lighting pass ===
111    pub deferred: Option<crate::deferred::DeferredState>,
112
113    // === GPU-DRIVEN MESH CULLING — Compute frustum cull + indirect draw ===
114    pub gpu_cull: Option<crate::gpu_cull::GpuCullState>,
115
116    // === SSAO — Screen-Space Ambient Occlusion ===
117    pub ssao: Option<crate::ssao::SsaoState>,
118
119    // === SSR — Screen-Space Reflections ===
120    pub ssr: Option<crate::ssr::SsrState>,
121
122    // === SSGI — Screen-Space Global Illumination ===
123    pub ssgi: Option<crate::ssgi::SsgiState>,
124
125    // === Volumetric Lighting (God Rays) ===
126    pub volumetric: Option<crate::volumetric::VolumetricState>,
127
128    // === DEFERRED DECALS ===
129    pub decal: Option<crate::decal::DecalState>,
130
131    // === TAA — Temporal Anti-Aliasing (ping-pong history + Halton jitter) ===
132    pub taa: Option<crate::taa::TaaState>,
133
134    // === GIZMO HATA AYIKLAMA (Debug Lines) ===
135    pub debug_renderer: Option<crate::debug_renderer::GizmoRendererSystem>,
136
137    // === DAHİLİ ASSET YÖNETİCİSİ (Kolaylık metodları için cache) ===
138    pub asset_manager: std::sync::RwLock<crate::asset::AssetManager>,
139
140    // === WEB PROFİLİ — Platform bazlı GPU kaynak yönetimi ===
141    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        // WASM'da canvas boyutu 0x0 olabilir, en az 1x1 garanti et
162        if size.width == 0 || size.height == 0 {
163            size = winit::dpi::PhysicalSize::new(1280, 720);
164        }
165
166        #[cfg(target_arch = "wasm32")]
167        {
168            // Web'de 4K/Retina ekranlarda devasa çözünürlükler performansı katleder.
169            // Internal rendering çözünürlüğünü 1280x720'ye (veya aspect ratio'ya göre) caple.
170            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        // Enumerate available adapters for diagnostic info
196        #[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                // Surface'siz adapter dene
241                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, // 256 MB buffer limit
281                        ..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, // 128 MB
299                        ..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            // VSync tercihi: Mailbox (uncapped FPS) varsa kullan, yoksa Fifo (VSync)
323            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        // GPU particle buffer boyutu — ihtiyaca göre ayarlanabilir
342        #[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            // TAA history textures + bind groups (needs fresh hdr_view + position_view)
601            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    // ==========================================================
623    //  Kolaylık Metodları — Asset Oluşturma
624    //  Kullanıcı `AssetManager` oluşturmak zorunda kalmadan
625    //  doğrudan `renderer.create_cube()` gibi çağırabilir.
626    // ==========================================================
627
628    /// Küp mesh oluşturur.
629    pub fn create_cube(&self) -> crate::components::Mesh {
630        crate::asset::AssetManager::create_cube(&self.device)
631    }
632
633    /// Küre mesh oluşturur.
634    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    /// Düzlem mesh oluşturur.
639    pub fn create_plane(&self, size: f32) -> crate::components::Mesh {
640        crate::asset::AssetManager::create_plane(&self.device, size)
641    }
642
643    /// Dama dokusu (checkerboard) oluşturur — test materyalleri için idealdir.
644    /// Cache'lenir: aynı doku tekrar oluşturulmaz.
645    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    /// Düz beyaz doku — varsayılan materyal için.
657    /// Cache'lenir: aynı doku tekrar oluşturulmaz.
658    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    /// Diskten doku yükler (BC7 pipeline dahil).
667    /// Cache'lenir: aynı dosya yolu tekrar yüklenmez.
668    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    /// Diskten bir GLTF (veya GLB) modelini senkron olarak yükler.
678    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        // İlk local_poses'u her kemiğin orijinal local_bind_transform'undan al.
696        let local_poses: Vec<gizmo_math::Mat4> = hierarchy
697            .joints
698            .iter()
699            .map(|j| j.local_bind_transform)
700            .collect();
701
702        // Global matrislerden doğru joint_matrices hesapla (bind-pose)
703        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); // 4096 -> 2^12. Level count is 13 (with level 0)
970
971        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            // This should compile the WGSL and execute without panicking or creating wgpu validation errors
1036            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}