Skip to main content

gizmo_engine/systems/
render.rs

1use super::physics::*;
2use crate::core::World;
3use crate::math::{Mat4, Vec3};
4use crate::renderer::{
5    components::{Camera, Material, Mesh, MeshRenderer},
6    Renderer,
7};
8use bytemuck;
9use wgpu;
10
11#[derive(Default)]
12pub struct WireframeConfig {
13    pub global: bool,
14}
15
16#[derive(Default)]
17pub struct RenderCache {
18    pub(crate) batches: std::collections::HashMap<BatchKey, BatchData>,
19    pub instances: Vec<crate::renderer::gpu_types::InstanceRaw>,
20    pub draw_items: Vec<DrawItem>,
21}
22
23thread_local! {
24    static RENDER_CACHE: std::cell::RefCell<RenderCache> = std::cell::RefCell::new(RenderCache::default());
25}
26
27pub fn clear_render_cache() {
28    RENDER_CACHE.with(|rc| {
29        let mut cache = rc.borrow_mut();
30        cache.batches.clear();
31        cache.instances.clear();
32        cache.draw_items.clear();
33    });
34}
35
36#[derive(Clone)]
37pub struct DrawItem {
38    vbuf: std::sync::Arc<wgpu::Buffer>,
39    vertex_count: u32,
40    bind_group: std::sync::Arc<wgpu::BindGroup>,
41    unlit: bool,
42    is_skybox: bool,
43    skeleton_bind_group: Option<std::sync::Arc<wgpu::BindGroup>>,
44    is_transparent: bool,
45    first_instance: u32,
46    instance_count: u32,
47}
48
49#[derive(Clone, PartialEq, Eq, Hash)]
50pub(crate) struct BatchKey {
51    vbuf_id: usize,
52    mat_id: usize,
53    skeleton_id: Option<usize>,
54}
55
56pub(crate) struct BatchData {
57    vbuf: std::sync::Arc<wgpu::Buffer>,
58    bind_group: std::sync::Arc<wgpu::BindGroup>,
59    vertex_count: u32,
60    unlit: bool,
61    is_skybox: bool,
62    skeleton_bind_group: Option<std::sync::Arc<wgpu::BindGroup>>,
63    is_transparent: bool,
64    instances: Vec<crate::renderer::gpu_types::InstanceRaw>,
65}
66
67/// Bevy'nin DefaultPlugins davranisini taklit eden, sadece modelleri
68/// isiklandirip hizlica ekrana basmaya yarayan kutudan cikmis Render Motoru.
69/// Yeni acilan `tut` gibi bos projelerde yuzlerce satir kod yazmamak icin kullanilir.
70#[tracing::instrument(skip_all, name = "render_system")]
71pub fn default_render_pass(
72    world: &mut World,
73    encoder: &mut wgpu::CommandEncoder,
74    view: &wgpu::TextureView,
75    renderer: &mut Renderer,
76) {
77    // Update post process parameters dynamically from renderer settings!
78    renderer.update_post_process(
79        &renderer.queue,
80        crate::renderer::gpu_types::PostProcessUniforms {
81            bloom_intensity: renderer.bloom_intensity,
82            bloom_threshold: renderer.bloom_threshold,
83            exposure: renderer.exposure,
84            chromatic_aberration: renderer.chromatic_aberration,
85            vignette_intensity: 0.25,
86            film_grain_intensity: renderer.film_grain_intensity,
87            dof_focus_dist: renderer.dof_focus_dist,
88            dof_focus_range: renderer.dof_focus_range,
89            dof_blur_size: if renderer.dof_enabled { renderer.dof_blur_size } else { 0.0 },
90            _padding: [0.0; 3],
91        },
92    );
93
94    let aspect = if renderer.size.height > 0 {
95        renderer.size.width as f32 / renderer.size.height as f32
96    } else {
97        1.0
98    };
99    let mut proj = Mat4::perspective_rh(std::f32::consts::FRAC_PI_4, aspect, 0.1, 2000.0);
100    let mut view_mat = Mat4::from_translation(Vec3::ZERO);
101    let mut cam_pos = Vec3::ZERO;
102    let mut cam_forward = Vec3::new(0.0, 0.0, -1.0);
103
104    // TODO: Bütün nesnelerin (özellikle kamera ve çizilecek objelerin) global matrix'leri
105    // bu pass çağrılmadan hemen önce bir `update_transforms(world)` sistemiyle güncellenmiş olmalıdır.
106
107    // ECS veri GPU'ya basılır ve GPU verisi ECS'ye alınır
108    gpu_physics_submit_system(world, renderer);
109    gpu_physics_readback_system(world, renderer);
110
111    let mut cam_exposure = 1.0;
112
113    // KAMERALARI BUL VE MATRIX YARAT
114    let cameras = world.borrow::<Camera>();
115    let transforms = world.borrow::<gizmo_physics_core::components::GlobalTransform>();
116    {
117        // TODO: Aktif kamera için `ActiveCamera` tarzı bir marker bileşeni kullanılmalı.
118        // ECS array sırası stabil değildir. Şimdilik geçici çözüm olarak ilki alınıyor.
119        if let Some((active_cam, _)) = cameras.iter().next() {
120            if let (Some(cam), Some(trans)) = (cameras.get(active_cam), transforms.get(active_cam))
121            {
122                let (_, _, pos) = trans.matrix.to_scale_rotation_translation();
123                proj = cam.get_projection(aspect);
124                view_mat = cam.get_view(pos);
125                cam_pos = pos;
126                cam_forward = cam.get_front();
127                cam_exposure = cam.exposure;
128            }
129        }
130    }
131
132    // Save unjittered projection before applying TAA offset (needed for reprojection next frame).
133    let unjittered_proj = proj;
134
135    // ── TAA Halton jitter: subpixel offset applied via z-column of projection ──
136    if let Some(ref taa) = renderer.taa {
137        if taa.enabled {
138            let jp = crate::renderer::taa::TaaState::get_jitter(taa.frame_index);
139            // Convert pixel jitter [−0.5, 0.5] to NDC offset (2 / viewport_size per axis)
140            let jx = jp[0] * 2.0 / renderer.size.width as f32;
141            let jy = jp[1] * 2.0 / renderer.size.height as f32;
142            // Adding jitter to NDC.x requires: new_clip.x = clip.x - jx*vz
143            // ↔ subtract jx from proj.z_axis.x (the M[0][2] element, row0·col2)
144            proj.z_axis.x -= jx;
145            proj.z_axis.y -= jy;
146        }
147    }
148
149    let view_proj = proj * view_mat; // jittered — used for SceneUniforms
150    let unjittered_view_proj = unjittered_proj * view_mat; // clean    — stored in TaaState for next frame
151
152    let mut sun_dir = gizmo_math::Vec3::new(0.0, -1.0, 0.0);
153    let mut sun_col = gizmo_math::Vec4::new(0.0, 0.0, 0.0, 0.0); // W=0.0 means NO SUN by default!
154    if let Some(q) = world.query::<(
155        &crate::renderer::components::DirectionalLight,
156        &gizmo_physics_core::components::GlobalTransform,
157    )>() {
158        for (_id, (light, transform)) in q.iter() {
159            if light.role == crate::renderer::components::LightRole::Sun {
160                let (_, rot, _) = transform.matrix.to_scale_rotation_translation();
161                sun_dir = rot
162                    .mul_vec3(gizmo_math::Vec3::new(0.0, 0.0, -1.0))
163                    .normalize();
164                sun_col = gizmo_math::Vec4::new(
165                    light.color.x,
166                    light.color.y,
167                    light.color.z,
168                    light.intensity,
169                );
170                break;
171            }
172        }
173    }
174
175    let cascade_splits = [20.0f32, 80.0, 250.0, 2000.0];
176    let cascade_vp = crate::renderer::directional_cascade_view_projs(
177        cam_pos,
178        cam_forward,
179        aspect,
180        std::f32::consts::FRAC_PI_4,
181        0.1,
182        &cascade_splits,
183        sun_dir,
184        crate::renderer::SHADOW_MAP_RES,
185    );
186    let light_view_projs: [[[f32; 4]; 4]; 4] = cascade_vp.map(|m| m.to_cols_array_2d());
187
188    // Dinamik Işıkları Bul
189    let mut lights_data = [crate::renderer::gpu_types::LightData {
190        position: [0.0; 4],
191        color: [0.0; 4],
192        direction: [0.0, -1.0, 0.0, 0.0],
193        params: [0.0; 4],
194    }; 10];
195    let mut num_lights = 0;
196
197    if let Some(q) = world.query::<(
198        &crate::renderer::components::PointLight,
199        &gizmo_physics_core::components::GlobalTransform,
200    )>() {
201        for (_id, (light, transform)) in q.iter() {
202            if num_lights >= 10 {
203                break;
204            }
205            let (_, _, pos) = transform.matrix.to_scale_rotation_translation();
206            lights_data[num_lights as usize] = crate::renderer::gpu_types::LightData {
207                position: [pos.x, pos.y, pos.z, light.intensity],
208                color: [light.color.x, light.color.y, light.color.z, light.radius],
209                direction: [0.0, -1.0, 0.0, 0.0],
210                params: [0.0, 0.0, 0.0, 0.0], // y = 0 means PointLight
211            };
212            num_lights += 1;
213        }
214    }
215
216    if let Some(q) = world.query::<(
217        &crate::renderer::components::SpotLight,
218        &gizmo_physics_core::components::GlobalTransform,
219    )>() {
220        for (_id, (light, transform)) in q.iter() {
221            if num_lights >= 10 {
222                break;
223            }
224            let (_, rot, pos) = transform.matrix.to_scale_rotation_translation();
225            let dir = rot
226                .mul_vec3(gizmo_math::Vec3::new(0.0, 0.0, -1.0))
227                .normalize();
228            lights_data[num_lights as usize] = crate::renderer::gpu_types::LightData {
229                position: [pos.x, pos.y, pos.z, light.intensity],
230                color: [light.color.x, light.color.y, light.color.z, light.radius],
231                direction: [dir.x, dir.y, dir.z, light.inner_angle],
232                params: [light.outer_angle, 1.0, 0.0, 0.0], // y = 1 means SpotLight
233            };
234            num_lights += 1;
235        }
236    }
237
238    #[allow(unused_assignments)]
239    let mut point_light_view_projs = [gizmo_math::Mat4::IDENTITY; 6];
240    if renderer.point_shadows_enabled {
241        if let Some(q) = world.query::<(
242            &crate::renderer::components::PointLight,
243            &gizmo_physics_core::components::GlobalTransform,
244        )>() {
245            if let Some((_id, (_light, transform))) = q.iter().next() {
246                let (_, _, pos) = transform.matrix.to_scale_rotation_translation();
247                let proj = gizmo_math::Mat4::perspective_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1, 100.0);
248                point_light_view_projs = [
249                    proj * gizmo_math::Mat4::look_to_rh(pos, gizmo_math::Vec3::X, -gizmo_math::Vec3::Y),
250                    proj * gizmo_math::Mat4::look_to_rh(pos, gizmo_math::Vec3::NEG_X, -gizmo_math::Vec3::Y),
251                    proj * gizmo_math::Mat4::look_to_rh(pos, gizmo_math::Vec3::Y, gizmo_math::Vec3::Z),
252                    proj * gizmo_math::Mat4::look_to_rh(pos, gizmo_math::Vec3::NEG_Y, gizmo_math::Vec3::NEG_Z),
253                    proj * gizmo_math::Mat4::look_to_rh(pos, gizmo_math::Vec3::Z, -gizmo_math::Vec3::Y),
254                    proj * gizmo_math::Mat4::look_to_rh(pos, gizmo_math::Vec3::NEG_Z, -gizmo_math::Vec3::Y),
255                ];
256                
257                for i in 0..6 {
258                    renderer.queue.write_buffer(
259                        &renderer.scene.point_shadow_uniform_buffers[i],
260                        0,
261                        bytemuck::bytes_of(&crate::renderer::gpu_types::ShadowVsUniform {
262                            light_view_proj: point_light_view_projs[i].to_cols_array_2d(),
263                        }),
264                    );
265                }
266            }
267        }
268    }
269
270
271    let scene_uniform_data = crate::renderer::gpu_types::SceneUniforms {
272        view_proj: view_proj.to_cols_array_2d(),
273        camera_pos: [cam_pos.x, cam_pos.y, cam_pos.z, 1.0],
274        sun_direction: [sun_dir.x, sun_dir.y, sun_dir.z, 1.0],
275        sun_color: [sun_col.x, sun_col.y, sun_col.z, sun_col.w],
276        lights: lights_data,
277        light_view_proj: light_view_projs,
278        cascade_splits,
279        camera_forward: [cam_forward.x, cam_forward.y, cam_forward.z, 0.0],
280        cascade_params: [0.1, 1.0 / crate::renderer::SHADOW_MAP_RES as f32, 0.0, 0.0],
281        num_lights,
282        exposure: cam_exposure,
283        _pre_align_pad: [0; 2],
284        _align_pad: [0; 3],
285        environment_blend_t: renderer.environment_blend_t,
286        environment_preset: renderer.environment_preset,
287        point_shadows_enabled: renderer.point_shadows_enabled as u32,
288        environment_preset_2: renderer.environment_preset_2,
289        shading_mode: renderer.shading_mode,
290    };
291    renderer.queue.write_buffer(
292        &renderer.scene.global_uniform_buffer,
293        0,
294        bytemuck::cast_slice(&[scene_uniform_data]),
295    );
296    for i in 0..crate::renderer::CASCADE_COUNT {
297        renderer.queue.write_buffer(
298            &renderer.scene.shadow_cascade_uniform_buffers[i],
299            0,
300            bytemuck::bytes_of(&crate::renderer::gpu_types::ShadowVsUniform {
301                light_view_proj: light_view_projs[i],
302            }),
303        );
304    }
305
306    // Upload TAA params (prev_vp from last frame, current jitter, blend alpha)
307    if let Some(ref mut taa) = renderer.taa {
308        if taa.enabled {
309            let jp = crate::renderer::taa::TaaState::get_jitter(taa.frame_index);
310            let jx = jp[0] * 2.0 / renderer.size.width as f32;
311            let jy = jp[1] * 2.0 / renderer.size.height as f32;
312            let alpha = if taa.frame_index == 0 { 1.0f32 } else { 0.1f32 };
313            taa.update_params(&renderer.queue, [jx, jy], alpha);
314            taa.store_prev_vp(unjittered_view_proj.to_cols_array_2d());
315        }
316    }
317
318    // ... inside default_render_pass ...
319    // ... before line 205 ...
320    let renderers = world.borrow::<MeshRenderer>();
321
322    // Get or create RenderCache
323    let frustum = crate::math::Frustum::from_matrix(&unjittered_view_proj);
324
325    let draw_items = RENDER_CACHE.with(|rc| {
326        let mut cache = rc.borrow_mut();
327        
328        // Clear instances but keep allocations
329        for batch in cache.batches.values_mut() {
330            batch.instances.clear();
331        }
332        cache.instances.clear();
333        cache.draw_items.clear();
334
335        let pooled_storage = world.borrow::<gizmo_core::pool::Pooled>();
336        
337        macro_rules! process_mesh {
338            ($e:expr, $mesh:expr, $trans:expr, $mat:expr, $skeleton:expr) => {
339                if renderers.get($e).is_none() {
340                    continue;
341                }
342                
343                // Pooled (havuzda pasif) nesneleri render etme
344                if pooled_storage.get($e).is_some() {
345                    continue;
346                }
347
348                let center_mat = Mat4::from_translation($mesh.center_offset);
349                let model = $trans.matrix * center_mat;
350
351                // CPU Frustum Culling
352                let local_cx = ($mesh.bounds.min.x + $mesh.bounds.max.x) * 0.5;
353                let local_cy = ($mesh.bounds.min.y + $mesh.bounds.max.y) * 0.5;
354                let local_cz = ($mesh.bounds.min.z + $mesh.bounds.max.z) * 0.5;
355                let world_c = model.transform_point3(Vec3::new(local_cx, local_cy, local_cz));
356                let hx = ($mesh.bounds.max.x - $mesh.bounds.min.x) * 0.5;
357                let hy = ($mesh.bounds.max.y - $mesh.bounds.min.y) * 0.5;
358                let hz = ($mesh.bounds.max.z - $mesh.bounds.min.z) * 0.5;
359                let local_r = (hx * hx + hy * hy + hz * hz).sqrt();
360                let sx = model.x_axis.truncate().length();
361                let sy = model.y_axis.truncate().length();
362                let sz = model.z_axis.truncate().length();
363                let world_r = local_r * sx.max(sy).max(sz);
364
365                if !frustum.intersects_sphere(world_c, world_r) {
366                    continue;
367                }
368
369                // Auto-LOD (Level of Detail) Seçimi
370                let dist_to_cam = (world_c - cam_pos).length();
371                let use_lod1 = if !$mesh.lod_vbufs.is_empty() {
372                    dist_to_cam > world_r * 15.0 // Nesne boyutuna göre uzaklaştıkça LOD1'e geç (örneğin 2m çapında bir nesne 30m uzaktayken geç)
373                } else {
374                    false
375                };
376
377                let active_vbuf = if use_lod1 {
378                    $mesh.lod_vbufs[0].clone()
379                } else {
380                    $mesh.vbuf.clone()
381                };
382                let active_vertex_count = if use_lod1 {
383                    $mesh.lod_vertex_counts[0]
384                } else {
385                    $mesh.vertex_count
386                };
387
388                let packed_params = (($mat.anisotropy * 1000.0).floor() + 1000.0 * ($mat.clear_coat * 1000.0).floor() + 1000000.0 * ($mat.subsurface * 100.0).floor()) as f32;
389
390                let instance_data = crate::renderer::gpu_types::InstanceRaw {
391                    model: model.to_cols_array_2d(),
392                    albedo_color: [$mat.albedo.x, $mat.albedo.y, $mat.albedo.z, $mat.albedo.w],
393                    roughness: $mat.roughness,
394                    metallic: $mat.metallic,
395                    unlit: match $mat.material_type {
396                        crate::renderer::components::MaterialType::Skybox => 2.0,
397                        crate::renderer::components::MaterialType::Unlit => 1.0,
398                        _ => 0.0,
399                    },
400                    _padding: packed_params,
401                };
402                let skel_bg = $skeleton.map(|s: &crate::renderer::components::Skeleton| s.bind_group.clone());
403                
404                let key = BatchKey {
405                    vbuf_id: std::sync::Arc::as_ptr(&active_vbuf) as usize,
406                    mat_id: std::sync::Arc::as_ptr(&$mat.bind_group) as usize,
407                    skeleton_id: skel_bg.as_ref().map(|bg| std::sync::Arc::as_ptr(bg) as usize),
408                };
409
410                let batch = cache.batches.entry(key).or_insert_with(|| BatchData {
411                    vbuf: active_vbuf.clone(),
412                    bind_group: $mat.bind_group.clone(),
413                    vertex_count: active_vertex_count,
414                    unlit: $mat.material_type == crate::renderer::components::MaterialType::Unlit
415                        || $mat.material_type == crate::renderer::components::MaterialType::Skybox,
416                    is_skybox: $mat.material_type == crate::renderer::components::MaterialType::Skybox,
417                    skeleton_bind_group: skel_bg,
418                    is_transparent: $mat.is_transparent || $mat.albedo.w < 0.99,
419                    instances: Vec::new(),
420                });
421                batch.instances.push(instance_data);
422            };
423        }
424
425        let skeletons = world.borrow::<crate::renderer::components::Skeleton>();
426
427        if let Some(mut q) = world.query::<(&Mesh, &gizmo_physics_core::components::GlobalTransform, &Material)>() {
428            for (e, (mesh, trans, mat)) in q.iter_mut() {
429                process_mesh!(e, mesh, trans, mat, skeletons.get(e));
430            }
431        }
432        
433        let meshes = world.try_get_resource::<gizmo_core::asset::Assets<Mesh>>().ok();
434        let materials = world.try_get_resource::<gizmo_core::asset::Assets<Material>>().ok();
435        
436        if let (Some(meshes), Some(materials)) = (meshes, materials) {
437            if let Some(mut q) = world.query::<(&gizmo_core::asset::Handle<Mesh>, &gizmo_physics_core::components::GlobalTransform, &gizmo_core::asset::Handle<Material>)>() {
438                for (e, (h_mesh, trans, h_mat)) in q.iter_mut() {
439                    if let (Some(mesh), Some(mat)) = (meshes.get(h_mesh), materials.get(h_mat)) {
440                        process_mesh!(e, mesh, trans, mat, skeletons.get(e));
441                    }
442                }
443            }
444        }
445        
446        let mut local_instances: Vec<crate::renderer::gpu_types::InstanceRaw> = std::mem::take(&mut cache.instances);
447        let mut local_draw_items: Vec<DrawItem> = std::mem::take(&mut cache.draw_items);
448
449        for (_, batch) in cache.batches.iter() {
450            if batch.instances.is_empty() { continue; }
451            let first_instance = local_instances.len() as u32;
452            let instance_count = batch.instances.len() as u32;
453            local_instances.extend(&batch.instances);
454            
455            local_draw_items.push(DrawItem {
456                vbuf: batch.vbuf.clone(),
457                vertex_count: batch.vertex_count,
458                bind_group: batch.bind_group.clone(),
459                unlit: batch.unlit,
460                is_skybox: batch.is_skybox,
461                skeleton_bind_group: batch.skeleton_bind_group.clone(),
462                is_transparent: batch.is_transparent,
463                first_instance,
464                instance_count,
465            });
466        }
467        
468        cache.instances = local_instances;
469        cache.draw_items = local_draw_items;
470
471        // Instance limiti kontrolü (Taşmaları önlemek için capaciteyi zorla)
472        let max_instances = renderer.scene.instance_capacity;
473        let instances_slice = if cache.instances.len() > max_instances {
474            &cache.instances[..max_instances]
475        } else {
476            &cache.instances
477        };
478
479        if !instances_slice.is_empty() {
480            renderer.queue.write_buffer(
481                &renderer.scene.instance_buffer,
482                0,
483                bytemuck::cast_slice(instances_slice),
484            );
485        }
486        
487        // Pass draw_items to rendering logic by cloning the small struct (Arc clones are cheap)
488        cache.draw_items.clone()
489    });
490    // CPU Batched Instancing replaces GPU cull for draw_items
491
492    if let Some(physics) = &renderer.gpu_physics {
493        // Her frame başında sıradaki state'i çekmek için WGPU CommandEncoder'a asenkron mapping iste.
494        physics.request_readback(encoder);
495
496        physics.compute_pass(encoder);
497        physics.debug_compute_pass(encoder);
498        physics.cull_pass(encoder, &renderer.scene.global_bind_group);
499    }
500
501    // Compute LOD (Level of Detail) Scaling
502    let fluid_pos = Vec3::new(0.0, 5.0, 0.0);
503    let dist_to_fluid = (cam_pos - fluid_pos).length();
504    let fluid_lod = if dist_to_fluid < 40.0 {
505        1.0
506    } else if dist_to_fluid < 80.0 {
507        0.5
508    } else if dist_to_fluid < 150.0 {
509        0.1
510    } else {
511        0.0
512    };
513
514    let dist_to_origin = cam_pos.length();
515    let particle_lod = if dist_to_origin < 50.0 {
516        1.0
517    } else if dist_to_origin < 100.0 {
518        0.5
519    } else if dist_to_origin < 200.0 {
520        0.1
521    } else {
522        0.0
523    };
524
525    // Gpu Fluid Processing
526    if let Some(fluid) = &renderer.gpu_fluid {
527        let active_fluid = (fluid.num_particles as f32 * fluid_lod) as u32;
528        fluid.compute_pass(encoder, &renderer.queue, true, active_fluid);
529    }
530
531    // Gpu Particles Processing
532    if let Some(particles) = &renderer.gpu_particles {
533        let active_parts = (particles.max_particles as f32 * particle_lod) as u32;
534        let dt = world
535            .get_resource::<gizmo_core::time::Time>()
536            .map(|t| t.dt())
537            .unwrap_or(0.016);
538        particles.update_params(&renderer.queue, dt); // Scale based on time_scale
539        particles.compute_pass(encoder, active_parts);
540    }
541
542    // GPU cull pass removed since we use CPU instancing
543
544    // Resize deferred G-buffers if window changed; resize SSAO + TAA to match
545    if let Some(ref mut def) = renderer.deferred {
546        def.resize(&renderer.device, renderer.size.width, renderer.size.height);
547    }
548    {
549        let w = renderer.size.width;
550        let h = renderer.size.height;
551        if let (Some(ssao), Some(def)) = (&mut renderer.ssao, &renderer.deferred) {
552            if ssao.width != w || ssao.height != h {
553                ssao.resize(&renderer.device, def, w, h);
554            }
555        }
556        if let (Some(ssr), Some(def)) = (&mut renderer.ssr, &renderer.deferred) {
557            if ssr.width != w || ssr.height != h {
558                ssr.resize(&renderer.device, def, &renderer.post.hdr_texture_view, w, h);
559            }
560        }
561        if let (Some(volumetric), Some(def)) = (&mut renderer.volumetric, &renderer.deferred) {
562            if volumetric.width != w || volumetric.height != h {
563                volumetric.resize(&renderer.device, def, w, h);
564            }
565        }
566    }
567    {
568        let w = renderer.size.width;
569        let h = renderer.size.height;
570        if let (Some(taa), Some(def)) = (&mut renderer.taa, &renderer.deferred) {
571            if taa.width != w || taa.height != h {
572                taa.resize(
573                    &renderer.device,
574                    &renderer.post.hdr_texture_view,
575                    &def.world_position_view,
576                    w,
577                    h,
578                );
579            }
580        }
581    }
582
583    // CSM shadow passes — one depth-only pass per cascade.
584    for i in 0..crate::renderer::CASCADE_COUNT {
585        let mut shadow_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
586            label: Some("Shadow Pass"),
587            color_attachments: &[],
588            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
589                view: &renderer.scene.shadow_cascade_layer_views[i],
590                depth_ops: Some(wgpu::Operations {
591                    load: wgpu::LoadOp::Clear(1.0),
592                    store: wgpu::StoreOp::Store,
593                }),
594                stencil_ops: None,
595            }),
596            timestamp_writes: None,
597            occlusion_query_set: None,
598        });
599        shadow_pass.set_pipeline(&renderer.scene.shadow_pipeline);
600        shadow_pass.set_bind_group(0, &renderer.scene.shadow_pass_bind_groups[i], &[]);
601        shadow_pass.set_bind_group(2, &renderer.scene.instance_bind_group, &[]);
602        for item in &draw_items {
603            if item.unlit || item.is_transparent {
604                continue;
605            }
606            let skel_bg = item
607                .skeleton_bind_group
608                .as_ref()
609                .unwrap_or(&renderer.scene.dummy_skeleton_bind_group);
610            shadow_pass.set_bind_group(1, skel_bg, &[]);
611            shadow_pass.set_vertex_buffer(0, item.vbuf.slice(..));
612            shadow_pass.draw(
613                0..item.vertex_count,
614                item.first_instance..(item.first_instance + item.instance_count),
615            );
616        }
617    }
618
619    // Point Light Shadow Passes — 6 faces
620    for i in 0..6 {
621        let mut shadow_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
622            label: Some("Point Shadow Pass"),
623            color_attachments: &[],
624            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
625                view: &renderer.scene.point_shadow_face_views[i],
626                depth_ops: Some(wgpu::Operations {
627                    load: wgpu::LoadOp::Clear(1.0),
628                    store: wgpu::StoreOp::Store,
629                }),
630                stencil_ops: None,
631            }),
632            timestamp_writes: None,
633            occlusion_query_set: None,
634        });
635        // We reuse the directional shadow pipeline because it only does position transformation
636        shadow_pass.set_pipeline(&renderer.scene.shadow_pipeline);
637        shadow_pass.set_bind_group(0, &renderer.scene.point_shadow_pass_bind_groups[i], &[]);
638        shadow_pass.set_bind_group(2, &renderer.scene.instance_bind_group, &[]);
639        for item in &draw_items {
640            if item.unlit || item.is_transparent { continue; }
641            let skel_bg = item
642                .skeleton_bind_group
643                .as_ref()
644                .unwrap_or(&renderer.scene.dummy_skeleton_bind_group);
645            shadow_pass.set_bind_group(1, skel_bg, &[]);
646            shadow_pass.set_vertex_buffer(0, item.vbuf.slice(..));
647            shadow_pass.draw(
648                0..item.vertex_count,
649                item.first_instance..(item.first_instance + item.instance_count),
650            );
651        }
652    }
653
654
655    // ── Z-Prepass (Depth Only) ────────────────────────────────────────────────
656    if let Some(ref def) = renderer.deferred {
657        let mut z_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
658            label: Some("Z-Prepass"),
659            color_attachments: &[], // No color targets
660            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
661                view: &renderer.depth_texture_view,
662                depth_ops: Some(wgpu::Operations {
663                    load: wgpu::LoadOp::Clear(1.0),
664                    store: wgpu::StoreOp::Store,
665                }),
666                stencil_ops: None,
667            }),
668            timestamp_writes: None,
669            occlusion_query_set: None,
670        });
671        z_pass.set_pipeline(&def.z_prepass_pipeline);
672        z_pass.set_bind_group(0, &renderer.scene.global_bind_group, &[]);
673        z_pass.set_bind_group(2, &renderer.scene.shadow_bind_group, &[]);
674        z_pass.set_bind_group(4, &renderer.scene.instance_bind_group, &[]);
675        for item in &draw_items {
676            if item.unlit || item.is_skybox || item.is_transparent {
677                continue;
678            }
679            let skel_bg = item
680                .skeleton_bind_group
681                .as_ref()
682                .unwrap_or(&renderer.scene.dummy_skeleton_bind_group);
683            z_pass.set_bind_group(3, skel_bg, &[]);
684            z_pass.set_bind_group(1, &item.bind_group, &[]);
685            z_pass.set_vertex_buffer(0, item.vbuf.slice(..));
686            z_pass.draw(
687                0..item.vertex_count,
688                item.first_instance..(item.first_instance + item.instance_count),
689            );
690        }
691    }
692
693    // ── G-buffer pass (PBR geometry → albedo / normal / world-position) ─────
694    if let Some(ref def) = renderer.deferred {
695        let mut gbuf_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
696            label: Some("G-Buffer Pass"),
697            color_attachments: &[
698                Some(wgpu::RenderPassColorAttachment {
699                    view: &def.albedo_metallic_view,
700                    resolve_target: None,
701                    ops: wgpu::Operations {
702                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
703                        store: wgpu::StoreOp::Store,
704                    },
705                }),
706                Some(wgpu::RenderPassColorAttachment {
707                    view: &def.normal_roughness_view,
708                    resolve_target: None,
709                    ops: wgpu::Operations {
710                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
711                        store: wgpu::StoreOp::Store,
712                    },
713                }),
714                Some(wgpu::RenderPassColorAttachment {
715                    view: &def.world_position_view,
716                    resolve_target: None,
717                    ops: wgpu::Operations {
718                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
719                        store: wgpu::StoreOp::Store,
720                    },
721                }),
722                Some(wgpu::RenderPassColorAttachment {
723                    view: &def.world_tangent_view,
724                    resolve_target: None,
725                    ops: wgpu::Operations {
726                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
727                        store: wgpu::StoreOp::Store,
728                    },
729                }),
730            ],
731            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
732                view: &renderer.depth_texture_view,
733                depth_ops: Some(wgpu::Operations {
734                    load: wgpu::LoadOp::Load, // Z-Prepass populated this!
735                    store: wgpu::StoreOp::Store,
736                }),
737                stencil_ops: None,
738            }),
739            timestamp_writes: None,
740            occlusion_query_set: None,
741        });
742        gbuf_pass.set_pipeline(&def.gbuffer_pipeline);
743        gbuf_pass.set_bind_group(0, &renderer.scene.global_bind_group, &[]);
744        gbuf_pass.set_bind_group(2, &renderer.scene.shadow_bind_group, &[]);
745        gbuf_pass.set_bind_group(4, &renderer.scene.instance_bind_group, &[]);
746        for item in &draw_items {
747            if item.unlit || item.is_transparent {
748                continue;
749            }
750            let skel_bg = item
751                .skeleton_bind_group
752                .as_ref()
753                .unwrap_or(&renderer.scene.dummy_skeleton_bind_group);
754            gbuf_pass.set_bind_group(3, skel_bg, &[]);
755            gbuf_pass.set_bind_group(1, &item.bind_group, &[]);
756            gbuf_pass.set_vertex_buffer(0, item.vbuf.slice(..));
757            gbuf_pass.draw(
758                0..item.vertex_count,
759                item.first_instance..(item.first_instance + item.instance_count),
760            );
761        }
762    }
763
764    // ── Decal Pass (Blend into G-buffer) ──────────────────────────
765    let mut decal_draws = Vec::new();
766    if let Some(ref decal_state) = renderer.decal {
767        let decals = world.borrow::<crate::renderer::components::Decal>();
768        let transforms = world.borrow::<gizmo_physics_core::Transform>();
769        let mut uniform_data = Vec::new();
770
771        for (id, decal) in decals.iter() {
772            if let Some(trans) = transforms.get(id) {
773                let model = trans.local_matrix;
774                let inv_model = model.inverse();
775
776                uniform_data.push(crate::renderer::decal::DecalUniforms {
777                    inv_model: inv_model.to_cols_array(),
778                    model: model.to_cols_array(),
779                    albedo_color: [decal.color.x, decal.color.y, decal.color.z, decal.color.w],
780                    _pad: [0.0; 28],
781                });
782
783                decal_draws.push(decal.bind_group.clone());
784                if uniform_data.len() >= 1024 {
785                    break;
786                } // Max 1024 decals limit
787            }
788        }
789
790        if !uniform_data.is_empty() {
791            renderer.queue.write_buffer(
792                &decal_state.uniform_buffer,
793                0,
794                bytemuck::cast_slice(&uniform_data),
795            );
796        }
797    }
798
799    if !decal_draws.is_empty() {
800        if let (Some(ref decal_state), Some(ref def)) = (&renderer.decal, &renderer.deferred) {
801            let mut decal_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
802                label: Some("Decal Pass"),
803                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
804                    view: &def.albedo_metallic_view,
805                    resolve_target: None,
806                    ops: wgpu::Operations {
807                        load: wgpu::LoadOp::Load,
808                        store: wgpu::StoreOp::Store,
809                    },
810                })],
811                depth_stencil_attachment: None, // No depth testing needed
812                timestamp_writes: None,
813                occlusion_query_set: None,
814            });
815
816            decal_pass.set_pipeline(&decal_state.pipeline);
817            decal_pass.set_bind_group(0, &renderer.scene.global_bind_group, &[]);
818            decal_pass.set_bind_group(1, &decal_state.world_pos_bg, &[]);
819            decal_pass.set_vertex_buffer(0, decal_state.vertex_buffer.slice(..));
820
821            for (i, bind_group) in decal_draws.iter().enumerate() {
822                let offset = (i * 256) as u32;
823                decal_pass.set_bind_group(2, bind_group, &[]);
824                decal_pass.set_bind_group(3, &decal_state.decal_uniform_bg, &[offset]);
825                decal_pass.draw(0..36, 0..1);
826            }
827        }
828    }
829
830    // ── Deferred lighting pass (G-buffers → HDR) ──────────────────────────
831    if let Some(ref def) = renderer.deferred {
832        let mut light_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
833            label: Some("Deferred Lighting Pass"),
834            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
835                view: &renderer.post.hdr_texture_view,
836                resolve_target: None,
837                ops: wgpu::Operations {
838                    load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.4, g: 0.6, b: 0.9, a: 1.0 }),
839                    store: wgpu::StoreOp::Store,
840                },
841            })],
842            depth_stencil_attachment: None,
843            timestamp_writes: None,
844            occlusion_query_set: None,
845        });
846        light_pass.set_pipeline(&def.lighting_pipeline);
847        light_pass.set_bind_group(0, &renderer.scene.global_bind_group, &[]);
848        light_pass.set_bind_group(1, &renderer.scene.shadow_bind_group, &[]);
849        light_pass.set_bind_group(2, &def.gbuffer_bind_group, &[]);
850        light_pass.draw(0..3, 0..1);
851    }
852
853    // ── SSAO: hemisphere sampling → raw AO texture ────────────────────────────
854    if let Some(ref ssao) = renderer.ssao {
855        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
856            label: Some("SSAO Pass"),
857            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
858                view: &ssao.ao_view,
859                resolve_target: None,
860                ops: wgpu::Operations {
861                    load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
862                    store: wgpu::StoreOp::Store,
863                },
864            })],
865            depth_stencil_attachment: None,
866            timestamp_writes: None,
867            occlusion_query_set: None,
868        });
869        pass.set_pipeline(&ssao.ssao_pipeline);
870        pass.set_bind_group(0, &renderer.scene.global_bind_group, &[]);
871        pass.set_bind_group(1, &ssao.ssao_gbuf_bind_group, &[]);
872        pass.draw(0..3, 0..1);
873    }
874
875    // ── SSAO blur: 5×5 box filter → blurred AO texture ───────────────────────
876    if let Some(ref ssao) = renderer.ssao {
877        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
878            label: Some("SSAO Blur Pass"),
879            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
880                view: &ssao.ao_blurred_view,
881                resolve_target: None,
882                ops: wgpu::Operations {
883                    load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
884                    store: wgpu::StoreOp::Store,
885                },
886            })],
887            depth_stencil_attachment: None,
888            timestamp_writes: None,
889            occlusion_query_set: None,
890        });
891        pass.set_pipeline(&ssao.blur_pipeline);
892        pass.set_bind_group(0, &ssao.blur_bind_group, &[]);
893        pass.draw(0..3, 0..1);
894    }
895
896    // ── SSAO apply: multiply AO into HDR (multiply blend) ─────────────────────
897    if let Some(ref ssao) = renderer.ssao {
898        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
899            label: Some("SSAO Apply Pass"),
900            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
901                view: &renderer.post.hdr_texture_view,
902                resolve_target: None,
903                ops: wgpu::Operations {
904                    load: wgpu::LoadOp::Load,
905                    store: wgpu::StoreOp::Store,
906                },
907            })],
908            depth_stencil_attachment: None,
909            timestamp_writes: None,
910            occlusion_query_set: None,
911        });
912        pass.set_pipeline(&ssao.apply_pipeline);
913        pass.set_bind_group(0, &ssao.apply_bind_group, &[]);
914        pass.draw(0..3, 0..1);
915    }
916
917    // ── Forward pass (unlit / skybox / GPU subsystems; PBR skipped if deferred) ──
918    {
919        let hdr_load = if renderer.deferred.is_some() {
920            wgpu::LoadOp::Load
921        } else {
922            wgpu::LoadOp::Clear(wgpu::Color {
923                r: 0.4,
924                g: 0.6,
925                b: 0.9,
926                a: 1.0,
927            })
928        };
929        let depth_load = if renderer.deferred.is_some() {
930            wgpu::LoadOp::Load
931        } else {
932            wgpu::LoadOp::Clear(1.0)
933        };
934        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
935            label: Some("Default Engine Render Pass"),
936            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
937                view: &renderer.post.hdr_texture_view,
938                resolve_target: None,
939                ops: wgpu::Operations {
940                    load: hdr_load,
941                    store: wgpu::StoreOp::Store,
942                },
943            })],
944            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
945                view: &renderer.depth_texture_view,
946                depth_ops: Some(wgpu::Operations {
947                    load: depth_load,
948                    store: wgpu::StoreOp::Store,
949                }),
950                stencil_ops: None,
951            }),
952            timestamp_writes: None,
953            occlusion_query_set: None,
954        });
955        render_pass.set_bind_group(0, &renderer.scene.global_bind_group, &[]);
956        render_pass.set_bind_group(2, &renderer.scene.shadow_bind_group, &[]);
957        render_pass.set_bind_group(4, &renderer.scene.instance_bind_group, &[]);
958
959        let show_wireframes = world.get_resource::<WireframeConfig>().map(|c| c.global).unwrap_or(false);
960
961        for item in &draw_items {
962            let mut draw_solid = false;
963            let draw_wire = show_wireframes && !item.is_skybox;
964
965            if item.is_skybox {
966                draw_solid = true;
967            } else if item.unlit {
968                draw_solid = true;
969            } else if renderer.deferred.is_none() {
970                draw_solid = true;
971            } else if item.is_transparent {
972                draw_solid = true;
973            }
974
975            let skel_bg = item
976                .skeleton_bind_group
977                .as_ref()
978                .unwrap_or(&renderer.scene.dummy_skeleton_bind_group);
979
980            if draw_solid {
981                let pipeline = if item.is_skybox {
982                    &renderer.scene.sky_pipeline
983                } else if item.unlit {
984                    &renderer.scene.unlit_pipeline
985                } else if item.is_transparent {
986                    &renderer.scene.transparent_pipeline
987                } else {
988                    &renderer.scene.render_pipeline
989                };
990                render_pass.set_pipeline(pipeline);
991                render_pass.set_bind_group(1, &item.bind_group, &[]);
992                render_pass.set_bind_group(3, skel_bg, &[]);
993                render_pass.set_vertex_buffer(0, item.vbuf.slice(..));
994                render_pass.draw(
995                    0..item.vertex_count,
996                    item.first_instance..(item.first_instance + item.instance_count),
997                );
998            }
999
1000            if draw_wire {
1001                render_pass.set_pipeline(&renderer.scene.wireframe_pipeline);
1002                render_pass.set_bind_group(1, &item.bind_group, &[]);
1003                render_pass.set_bind_group(3, skel_bg, &[]);
1004                render_pass.set_vertex_buffer(0, item.vbuf.slice(..));
1005                render_pass.draw(
1006                    0..item.vertex_count,
1007                    item.first_instance..(item.first_instance + item.instance_count),
1008                );
1009            }
1010        }
1011
1012        // Draw GPU Physics Spheres!
1013        if let Some(physics) = &renderer.gpu_physics {
1014            physics.render_pass(&mut render_pass, &renderer.scene.global_bind_group);
1015            physics.debug_render_pass(&mut render_pass, &renderer.scene.global_bind_group);
1016        }
1017
1018        // Draw SPH fluid
1019        if let Some(fluid) = &renderer.gpu_fluid {
1020            fluid.render_pass(&mut render_pass, &renderer.scene.global_bind_group);
1021        }
1022
1023        // Draw GPU Particles
1024        if let Some(particles) = &renderer.gpu_particles {
1025            let active_parts = (particles.max_particles as f32 * particle_lod) as u32;
1026            particles.render_pass(
1027                &mut render_pass,
1028                &renderer.scene.global_bind_group,
1029                active_parts,
1030            );
1031        }
1032
1033
1034    }
1035
1036    if let Some(fluid) = &renderer.gpu_fluid {
1037        let active_fluid = (fluid.num_particles as f32 * fluid_lod) as u32;
1038        fluid.render_ssfr(
1039            encoder,
1040            &renderer.post.hdr_texture,
1041            &renderer.post.hdr_texture_view,
1042            &renderer.depth_texture_view,
1043            &renderer.scene.global_bind_group,
1044            active_fluid,
1045        );
1046    }
1047
1048
1049
1050    // ── SSR: Screen Space Reflections ───────────────────────────────────────────
1051    if let Some(ref ssr) = renderer.ssr {
1052        // Pass 1: SSR Raymarch
1053        {
1054            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1055                label: Some("SSR Pass"),
1056                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1057                    view: &ssr.ssr_view,
1058                    resolve_target: None,
1059                    ops: wgpu::Operations {
1060                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1061                        store: wgpu::StoreOp::Store,
1062                    },
1063                })],
1064                depth_stencil_attachment: None,
1065                timestamp_writes: None,
1066                occlusion_query_set: None,
1067            });
1068            pass.set_pipeline(&ssr.ssr_pipeline);
1069            pass.set_bind_group(0, &renderer.scene.global_bind_group, &[]);
1070            pass.set_bind_group(1, &ssr.ssr_bind_group, &[]);
1071            pass.draw(0..3, 0..1);
1072        }
1073
1074        // Pass 2: SSR Apply (Additive blend into HDR texture)
1075        {
1076            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1077                label: Some("SSR Apply Pass"),
1078                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1079                    view: &renderer.post.hdr_texture_view,
1080                    resolve_target: None,
1081                    ops: wgpu::Operations {
1082                        load: wgpu::LoadOp::Load,
1083                        store: wgpu::StoreOp::Store,
1084                    },
1085                })],
1086                depth_stencil_attachment: None,
1087                timestamp_writes: None,
1088                occlusion_query_set: None,
1089            });
1090            pass.set_pipeline(&ssr.apply_pipeline);
1091            pass.set_bind_group(0, &ssr.apply_bind_group, &[]);
1092            pass.draw(0..3, 0..1);
1093        }
1094    }
1095
1096    // ── SSGI: Screen Space Global Illumination ────────────────────────────────
1097    if let Some(ref ssgi) = renderer.ssgi {
1098        // Pass 1: SSGI Raymarch
1099        {
1100            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1101                label: Some("SSGI Pass"),
1102                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1103                    view: &ssgi.ssgi_view,
1104                    resolve_target: None,
1105                    ops: wgpu::Operations {
1106                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1107                        store: wgpu::StoreOp::Store,
1108                    },
1109                })],
1110                depth_stencil_attachment: None,
1111                timestamp_writes: None,
1112                occlusion_query_set: None,
1113            });
1114            pass.set_pipeline(&ssgi.ssgi_pipeline);
1115            pass.set_bind_group(0, &renderer.scene.global_bind_group, &[]);
1116            pass.set_bind_group(1, &ssgi.ssgi_bind_group, &[]);
1117            pass.draw(0..3, 0..1);
1118        }
1119
1120        // Pass 2: SSGI Blur
1121        {
1122            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1123                label: Some("SSGI Blur Pass"),
1124                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1125                    view: &ssgi.ssgi_blurred_view,
1126                    resolve_target: None,
1127                    ops: wgpu::Operations {
1128                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1129                        store: wgpu::StoreOp::Store,
1130                    },
1131                })],
1132                depth_stencil_attachment: None,
1133                timestamp_writes: None,
1134                occlusion_query_set: None,
1135            });
1136            pass.set_pipeline(&ssgi.blur_pipeline);
1137            pass.set_bind_group(0, &ssgi.blur_bind_group, &[]);
1138            pass.draw(0..3, 0..1);
1139        }
1140
1141        // Pass 3: SSGI Apply (Additive blend into HDR texture)
1142        {
1143            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1144                label: Some("SSGI Apply Pass"),
1145                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1146                    view: &renderer.post.hdr_texture_view,
1147                    resolve_target: None,
1148                    ops: wgpu::Operations {
1149                        load: wgpu::LoadOp::Load,
1150                        store: wgpu::StoreOp::Store,
1151                    },
1152                })],
1153                depth_stencil_attachment: None,
1154                timestamp_writes: None,
1155                occlusion_query_set: None,
1156            });
1157            pass.set_pipeline(&ssgi.apply_pipeline);
1158            pass.set_bind_group(0, &ssgi.apply_bind_group, &[]);
1159            pass.draw(0..3, 0..1);
1160        }
1161    }
1162
1163    // ── Volumetric Lighting (God Rays) ──────────────────────────────────────────
1164    if let Some(ref vol) = renderer.volumetric {
1165        // Pass 1: Volumetric Raymarch
1166        {
1167            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1168                label: Some("Volumetric Pass"),
1169                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1170                    view: &vol.volumetric_view,
1171                    resolve_target: None,
1172                    ops: wgpu::Operations {
1173                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1174                        store: wgpu::StoreOp::Store,
1175                    },
1176                })],
1177                depth_stencil_attachment: None,
1178                timestamp_writes: None,
1179                occlusion_query_set: None,
1180            });
1181            pass.set_pipeline(&vol.volumetric_pipeline);
1182            pass.set_bind_group(0, &renderer.scene.global_bind_group, &[]);
1183            pass.set_bind_group(1, &renderer.scene.shadow_bind_group, &[]);
1184            pass.set_bind_group(2, &vol.volumetric_bind_group, &[]);
1185            pass.draw(0..3, 0..1);
1186        }
1187
1188        // Pass 2: Volumetric Apply (Additive blend into HDR texture)
1189        {
1190            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1191                label: Some("Volumetric Apply Pass"),
1192                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1193                    view: &renderer.post.hdr_texture_view,
1194                    resolve_target: None,
1195                    ops: wgpu::Operations {
1196                        load: wgpu::LoadOp::Load,
1197                        store: wgpu::StoreOp::Store,
1198                    },
1199                })],
1200                depth_stencil_attachment: None,
1201                timestamp_writes: None,
1202                occlusion_query_set: None,
1203            });
1204            pass.set_pipeline(&vol.apply_pipeline);
1205            pass.set_bind_group(0, &vol.apply_bind_group, &[]);
1206            pass.draw(0..3, 0..1);
1207        }
1208    }
1209
1210    // ── TAA resolve: blend jittered HDR with clamped history ─────────────────
1211    if let Some(ref taa) = renderer.taa {
1212        if taa.enabled {
1213            let (resolve_bg, output_view) = taa.current_resolve_inputs_output();
1214            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1215                label: Some("TAA Resolve Pass"),
1216                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1217                    view: output_view,
1218                    resolve_target: None,
1219                    ops: wgpu::Operations {
1220                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1221                        store: wgpu::StoreOp::Store,
1222                    },
1223                })],
1224                depth_stencil_attachment: None,
1225                timestamp_writes: None,
1226                occlusion_query_set: None,
1227            });
1228            pass.set_pipeline(&taa.resolve_pipeline);
1229            pass.set_bind_group(0, resolve_bg, &[]);
1230            pass.draw(0..3, 0..1);
1231        }
1232    }
1233
1234    // ── TAA blit: copy stabilized history output back into HDR texture ───────
1235    if let Some(ref taa) = renderer.taa {
1236        if taa.enabled {
1237            let blit_bg = taa.current_blit_bg();
1238            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1239                label: Some("TAA Blit Pass"),
1240                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1241                    view: &renderer.post.hdr_texture_view,
1242                    resolve_target: None,
1243                    ops: wgpu::Operations {
1244                        load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1245                        store: wgpu::StoreOp::Store,
1246                    },
1247                })],
1248                depth_stencil_attachment: None,
1249                timestamp_writes: None,
1250                occlusion_query_set: None,
1251            });
1252            pass.set_pipeline(&taa.blit_pipeline);
1253            pass.set_bind_group(0, &taa.empty_bg, &[]);
1254            pass.set_bind_group(1, blit_bg, &[]);
1255            pass.draw(0..3, 0..1);
1256        }
1257    }
1258
1259    // Render Gizmos AFTER TAA to avoid ghosting/washing out dynamic overlays
1260    if let Some(gizmos) = world.get_resource::<crate::renderer::Gizmos>() {
1261        if let Some(debug_renderer) = &mut renderer.debug_renderer {
1262            debug_renderer.update(&renderer.queue, &gizmos);
1263            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1264                label: Some("Gizmo Render Pass (Post-TAA)"),
1265                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1266                    view: &renderer.post.hdr_texture_view,
1267                    resolve_target: None,
1268                    ops: wgpu::Operations {
1269                        load: wgpu::LoadOp::Load, // Keep TAA-stabilized geometry scene
1270                        store: wgpu::StoreOp::Store,
1271                    },
1272                })],
1273                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1274                    view: &renderer.depth_texture_view,
1275                    depth_ops: Some(wgpu::Operations {
1276                        load: wgpu::LoadOp::Load,
1277                        store: wgpu::StoreOp::Store,
1278                    }),
1279                    stencil_ops: None,
1280                }),
1281                timestamp_writes: None,
1282                occlusion_query_set: None,
1283            });
1284            debug_renderer.render(
1285                &mut pass,
1286                &renderer.scene.global_bind_group,
1287                gizmos.depth_test,
1288            );
1289        }
1290    }
1291
1292    // Auto-clear gizmos for the next frame ONLY if a physics step occurred
1293    let mut should_clear = true;
1294    let current_step = world.get_resource::<gizmo_core::time::PhysicsTime>()
1295        .map(|pt| pt.step_count());
1296
1297    if let Some(current_step) = current_step {
1298        struct GizmosLastStepCount(u64);
1299        
1300        let has_resource = world.get_resource::<GizmosLastStepCount>().is_some();
1301        if has_resource {
1302            if let Some(mut last_step) = world.get_resource_mut::<GizmosLastStepCount>() {
1303                if last_step.0 == current_step {
1304                    should_clear = false;
1305                } else {
1306                    last_step.0 = current_step;
1307                }
1308            }
1309        } else {
1310            world.insert_resource(GizmosLastStepCount(current_step));
1311        }
1312    }
1313
1314    if should_clear {
1315        if let Some(mut gizmos) = world.get_resource_mut::<crate::renderer::Gizmos>() {
1316            gizmos.clear();
1317        }
1318    }
1319
1320    // Advance TAA ping-pong and frame counter
1321    if let Some(ref mut taa) = renderer.taa {
1322        if taa.enabled {
1323            taa.advance_frame();
1324        }
1325    }
1326
1327    renderer.run_post_processing(encoder, view);
1328}
1329
1330// ============================================================
1331//  RenderContext Kolaylık Metodu
1332//  `ctx.default_render(world)` ile varsayılan pipeline çalışır.
1333// ============================================================
1334
1335/// `RenderContext` üzerine eklenen kolaylık metodları.
1336/// `use gizmo::prelude::*;` ile otomatik olarak dahil edilir.
1337pub trait RenderContextExt {
1338    /// Motorun varsayılan render pipeline'ını çalıştırır.
1339    /// Deferred rendering, gölgeler, SSAO, SSR, TAA ve post-processing dahildir.
1340    ///
1341    /// ```ignore
1342    /// fn render(world: &mut World, _state: &GameState, ctx: &mut RenderContext) {
1343    ///     ctx.disable_gpu_compute();
1344    ///     ctx.default_render(world);
1345    /// }
1346    /// ```
1347    fn default_render(&mut self, world: &mut crate::core::World);
1348}
1349
1350impl<'a> RenderContextExt for crate::renderer::RenderContext<'a> {
1351    fn default_render(&mut self, world: &mut crate::core::World) {
1352        let (encoder, view, renderer) = self.parts_mut();
1353        default_render_pass(world, encoder, view, renderer);
1354    }
1355}