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