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