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    // === FXAA — Fast Approximate Anti-Aliasing (son post-process pass) ===
135    pub fxaa: Option<crate::fxaa::FxaaState>,
136
137    // === GIZMO HATA AYIKLAMA (Debug Lines) ===
138    pub debug_renderer: Option<crate::debug_renderer::GizmoRendererSystem>,
139
140    // === DAHİLİ ASSET YÖNETİCİSİ (Kolaylık metodları için cache) ===
141    pub asset_manager: std::sync::RwLock<crate::asset::AssetManager>,
142
143    // === WEB PROFİLİ — Platform bazlı GPU kaynak yönetimi ===
144    pub web_profile: crate::web_profile::WebProfile,
145
146    // === RENDER SETTINGS & DIAGNOSTICS ===
147    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        // WASM'da canvas boyutu 0x0 olabilir, en az 1x1 garanti et
181        if size.width == 0 || size.height == 0 {
182            size = winit::dpi::PhysicalSize::new(1280, 720);
183        }
184
185        #[cfg(target_arch = "wasm32")]
186        {
187            // Web'de 4K/Retina ekranlarda devasa çözünürlükler performansı katleder.
188            // Internal rendering çözünürlüğünü 1280x720'ye (veya aspect ratio'ya göre) caple.
189            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        // Enumerate available adapters for diagnostic info
215        #[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                // Surface'siz adapter dene
260                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, // 256 MB buffer limit
300                        ..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, // 128 MB
318                        ..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            // VSync tercihi: Mailbox (uncapped FPS) varsa kullan, yoksa Fifo (VSync)
342            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        // GPU particle buffer boyutu — ihtiyaca göre ayarlanabilir
361        #[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        // === FXAA Başlatma ===
539        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, // 4.5 meters (fits our lamp setup)
579            dof_focus_range: 2.0, // 2.0 meters focus range
580            dof_blur_size: 4.0, // Beautiful smooth blur
581            chromatic_aberration: 0.15, // Cinematic soft fringe
582            film_grain_intensity: 0.03, // Photographic film grain
583            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            // TAA history textures + bind groups (needs fresh hdr_view + position_view)
650            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            // FXAA resize
685            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    // ==========================================================
692    //  Kolaylık Metodları — Asset Oluşturma
693    //  Kullanıcı `AssetManager` oluşturmak zorunda kalmadan
694    //  doğrudan `renderer.create_cube()` gibi çağırabilir.
695    // ==========================================================
696
697    /// Küp mesh oluşturur.
698    pub fn create_cube(&self) -> crate::components::Mesh {
699        crate::asset::AssetManager::create_cube(&self.device)
700    }
701
702    /// Küre mesh oluşturur.
703    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    /// Düzlem mesh oluşturur.
708    pub fn create_plane(&self, size: f32) -> crate::components::Mesh {
709        crate::asset::AssetManager::create_plane(&self.device, size)
710    }
711
712    /// Dama dokusu (checkerboard) oluşturur — test materyalleri için idealdir.
713    /// Cache'lenir: aynı doku tekrar oluşturulmaz.
714    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    /// Düz beyaz doku — varsayılan materyal için.
726    /// Cache'lenir: aynı doku tekrar oluşturulmaz.
727    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    /// Diskten doku yükler (BC7 pipeline dahil).
736    /// Cache'lenir: aynı dosya yolu tekrar yüklenmez.
737    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    /// Diskten bir GLTF (veya GLB) modelini senkron olarak yükler.
747    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        // İlk local_poses'u her kemiğin orijinal local_bind_transform'undan al.
765        let local_poses: Vec<gizmo_math::Mat4> = hierarchy
766            .joints
767            .iter()
768            .map(|j| j.local_bind_transform)
769            .collect();
770
771        // Global matrislerden doğru joint_matrices hesapla (bind-pose)
772        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                // Composite → FXAA input texture → FXAA → output_view
813                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        // FXAA kapalıysa doğrudan output'a yaz
819        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); // 4096 -> 2^12. Level count is 13 (with level 0)
1048
1049        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        // Enforce valid atmospheric preset range constraints [0, 3]
1057        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        // Dynamic weight blend_t must lie within [0.0, 1.0] and clamp gracefully if out-of-bounds
1066        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        // Extremely critical alignment checks to prevent runtime pipeline crashes on GPU
1077        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            // This should compile the WGSL and execute without panicking or creating wgpu validation errors
1143            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}