use crate::ecs::generational_registry::registry_entry_by_name;
use crate::ecs::mesh::components::{
    Mesh, Vertex, create_cone_mesh, create_cube_mesh, create_cylinder_mesh, create_plane_mesh,
    create_sphere_mesh, create_subdivided_plane_mesh, create_torus_mesh,
};
use crate::ecs::prefab::resources::{mesh_cache_iter, mesh_cache_take_dirty};
use crate::render::wgpu::passes::geometry::HizPass;
use std::collections::{HashMap, HashSet};
use wgpu::util::DeviceExt;

use super::types::{
    BUFFER_GROWTH_FACTOR, BUFFER_SHRINK_THRESHOLD, CLUSTER_GRID_X, CLUSTER_GRID_Y, CLUSTER_GRID_Z,
    ClusterBounds, ClusterUniforms, CullingUniforms, DrawIndexedIndirect, GpuEntityRegistry,
    GpuLocalTransform, INITIAL_INDEX_BUFFER_SIZE, INITIAL_INSTANCES, INITIAL_LIGHTS,
    INITIAL_LOCAL_TRANSFORM_BUFFER_SIZE, INITIAL_MATERIALS, INITIAL_VERTEX_BUFFER_SIZE, LightData,
    LightGrid, MAX_HIERARCHY_DEPTH, MAX_LIGHTS, MAX_LIGHTS_PER_CLUSTER, MAX_POINT_LIGHT_SHADOWS,
    MAX_SPOTLIGHT_SHADOWS, MaterialData, MaterialTextures, MeshBoundsAABB, MeshBoundsData,
    MeshData, MeshUniforms, ModelMatrix, MorphDisplacement, ObjectData, PointLightShadowData,
    TOTAL_CLUSTERS, TransformComputeUniforms, compute_normal_matrix,
};

pub struct MeshPass {
    pub(super) opaque_pipeline: wgpu::RenderPipeline,
    pub(super) opaque_double_sided_pipeline: wgpu::RenderPipeline,
    pub(super) overlay_opaque_pipeline: wgpu::RenderPipeline,
    pub(super) overlay_opaque_double_sided_pipeline: wgpu::RenderPipeline,
    pub(super) oit_pipeline: wgpu::RenderPipeline,
    pub(super) oit_composite_pipeline: wgpu::RenderPipeline,

    pub(super) oit_accum_view: wgpu::TextureView,
    pub(super) oit_reveal_view: wgpu::TextureView,
    pub(super) oit_composite_bind_group: wgpu::BindGroup,
    pub(super) oit_composite_bind_group_layout: wgpu::BindGroupLayout,
    pub(super) oit_sampler: wgpu::Sampler,

    pub(super) overlay_depth_view: wgpu::TextureView,

    pub(super) uniform_buffer: wgpu::Buffer,
    pub(super) uniform_bind_group: wgpu::BindGroup,
    pub(super) overlay_uniform_buffer: wgpu::Buffer,
    pub(super) overlay_uniform_bind_group: wgpu::BindGroup,

    pub(super) transform_buffer: wgpu::Buffer,
    pub(super) materials_buffer: wgpu::Buffer,
    pub(super) lights_buffer: wgpu::Buffer,
    pub(super) object_buffer: wgpu::Buffer,
    pub(super) custom_data_buffer: wgpu::Buffer,
    pub(super) instance_bind_group: wgpu::BindGroup,
    pub(super) instance_bind_group_layout: wgpu::BindGroupLayout,

    pub(super) texture_bind_group_layout: wgpu::BindGroupLayout,
    pub(super) scene_bind_group: wgpu::BindGroup,
    pub(super) scene_bind_group_layout: wgpu::BindGroupLayout,
    pub(super) shadow_sampler: wgpu::Sampler,
    pub(super) spotlight_shadow_buffer: wgpu::Buffer,
    pub(super) point_shadow_buffer: wgpu::Buffer,
    pub(super) point_shadow_sampler: wgpu::Sampler,
    pub(super) ibl_sampler: wgpu::Sampler,

    pub(super) dummy_white_view: wgpu::TextureView,
    pub(super) dummy_black_view: wgpu::TextureView,
    pub(super) dummy_normal_view: wgpu::TextureView,
    pub(super) dummy_sampler: wgpu::Sampler,

    pub(super) transform_buffer_size: usize,
    pub(super) materials_buffer_size: usize,
    pub(super) lights_buffer_size: usize,
    pub(super) object_buffer_size: usize,
    pub(super) custom_data_buffer_size: usize,

    pub(super) vertex_buffer: wgpu::Buffer,
    pub(super) index_buffer: wgpu::Buffer,
    pub(super) vertex_buffer_size: u64,
    pub(super) index_buffer_size: u64,

    pub(super) meshes: HashMap<String, u32>,
    pub(super) mesh_data: Vec<MeshData>,

    pub(super) opaque_instances: Vec<(u32, u32, u32, u32)>,
    pub(super) opaque_double_sided_instances: Vec<(u32, u32, u32, u32)>,
    pub(super) transparent_instances: Vec<(u32, u32, u32, u32)>,
    pub(super) overlay_opaque_instances: Vec<(u32, u32, u32, u32)>,
    pub(super) overlay_opaque_double_sided_instances: Vec<(u32, u32, u32, u32)>,
    pub(super) overlay_transparent_instances: Vec<(u32, u32, u32, u32)>,
    pub(super) regular_object_count: u32,
    pub(super) material_bind_groups: HashMap<u32, wgpu::BindGroup>,
    pub(super) material_bind_group_cache_key: HashMap<u32, MaterialTextures>,

    pub(super) registered_textures: HashMap<String, (wgpu::TextureView, wgpu::Sampler)>,
    pub(super) newly_registered_textures: HashSet<String>,

    pub(super) current_vertex_offset: u32,
    pub(super) current_index_offset: u32,

    pub(super) compaction_frame_counter: u64,
    pub(super) last_vertex_utilization: f32,
    pub(super) last_index_utilization: f32,

    pub(super) culling_pipeline: wgpu::ComputePipeline,
    pub(super) culling_bind_group_layout: wgpu::BindGroupLayout,
    pub(super) culling_bind_group: Option<wgpu::BindGroup>,
    pub(super) culling_uniform_buffer: wgpu::Buffer,
    pub(super) object_count: u32,

    pub(super) mesh_bounds_buffer: wgpu::Buffer,
    pub(super) mesh_bounds_buffer_size: usize,
    pub(super) mesh_bounds_data: Vec<MeshBoundsData>,
    pub(super) mesh_aabbs_buffer: wgpu::Buffer,
    pub(super) mesh_aabbs_buffer_size: usize,
    pub(super) mesh_aabbs_data: Vec<MeshBoundsAABB>,
    pub(super) hiz_pass: HizPass,
    pub(super) indirect_buffer: wgpu::Buffer,
    pub(super) indirect_buffer_size: usize,
    pub(super) indirect_reset_buffer: wgpu::Buffer,
    pub(super) indirect_reset_count: usize,
    pub(super) visible_indices_buffer: wgpu::Buffer,

    pub(super) depth_prepass_pipeline: wgpu::RenderPipeline,
    pub(super) phase1_indirect_buffer: wgpu::Buffer,
    pub(super) phase1_indirect_reset_buffer: wgpu::Buffer,
    pub(super) phase1_visible_indices_buffer: wgpu::Buffer,
    pub(super) phase1_culling_bind_group: Option<wgpu::BindGroup>,
    pub(super) phase1_instance_bind_group: wgpu::BindGroup,
    pub(super) phase1_culling_uniform_buffer: wgpu::Buffer,

    pub(super) morph_displacement_buffer: wgpu::Buffer,
    pub(super) morph_displacement_buffer_size: u64,
    pub(super) current_morph_displacement_offset: u32,

    pub(super) brdf_lut_view: wgpu::TextureView,
    pub(super) irradiance_view: wgpu::TextureView,
    pub(super) prefiltered_view: wgpu::TextureView,
    pub(super) point_shadow_cubemap_view: wgpu::TextureView,

    pub(super) gpu_registry: GpuEntityRegistry,
    pub(super) cached_transforms: Vec<ModelMatrix>,
    pub(super) cached_objects: Vec<ObjectData>,
    pub(super) cached_entities: Vec<crate::ecs::world::Entity>,

    pub(super) cluster_uniforms_buffer: wgpu::Buffer,
    pub(super) cluster_bounds_buffer: wgpu::Buffer,
    pub(super) light_grid_buffer: wgpu::Buffer,
    pub(super) light_grid_reset_buffer: wgpu::Buffer,
    pub(super) light_indices_buffer: wgpu::Buffer,
    pub(super) cluster_bounds_pipeline: wgpu::ComputePipeline,
    pub(super) cluster_assign_pipeline: wgpu::ComputePipeline,
    pub(super) _cluster_bounds_bind_group_layout: wgpu::BindGroupLayout,
    pub(super) cluster_bounds_bind_group: wgpu::BindGroup,
    pub(super) cluster_assign_bind_group_layout: wgpu::BindGroupLayout,
    pub(super) cluster_assign_bind_group: Option<wgpu::BindGroup>,
    pub(super) view_matrix_buffer: wgpu::Buffer,
    pub(super) last_camera_hash: u64,
    pub(super) camera_changed: bool,
    pub(super) num_directional_lights: u32,
    pub(super) num_total_lights: u32,

    pub(super) local_transform_buffer: wgpu::Buffer,
    pub(super) transform_compute_pipeline: wgpu::ComputePipeline,
    pub(super) transform_compute_bind_group_layout: wgpu::BindGroupLayout,
    pub(super) transform_compute_bind_group: Option<wgpu::BindGroup>,
    pub(super) transform_compute_uniforms_buffer: wgpu::Buffer,
    pub(super) transform_compute_staging_buffer: wgpu::Buffer,
    pub(super) max_hierarchy_depth: u32,
    pub(super) hierarchy_dirty: bool,
    pub(super) entity_depths: Vec<u32>,
    pub(super) entity_parent_indices: Vec<i32>,
    pub(super) entity_to_transform_index: std::collections::HashMap<crate::ecs::world::Entity, u32>,
    pub(super) local_transform_buffer_size: usize,
}

impl MeshPass {
    pub fn new(
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        color_format: wgpu::TextureFormat,
        depth_format: wgpu::TextureFormat,
        window_size: (u32, u32),
    ) -> Self {
        let shader = device.create_shader_module(wgpu::include_wgsl!("../../../shaders/mesh.wgsl"));
        let oit_shader =
            device.create_shader_module(wgpu::include_wgsl!("../../../shaders/mesh_oit.wgsl"));
        let composite_shader =
            device.create_shader_module(wgpu::include_wgsl!("../../../shaders/oit_composite.wgsl"));
        let depth_prepass_shader = device.create_shader_module(wgpu::include_wgsl!(
            "../../../shaders/mesh_depth_prepass.wgsl"
        ));

        let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Uniform Buffer"),
            size: std::mem::size_of::<MeshUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let uniform_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Mesh Uniform Bind Group Layout"),
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                }],
            });

        let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Mesh Uniform Bind Group"),
            layout: &uniform_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: uniform_buffer.as_entire_binding(),
            }],
        });

        let overlay_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Overlay Uniform Buffer"),
            size: std::mem::size_of::<MeshUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let overlay_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Mesh Overlay Uniform Bind Group"),
            layout: &uniform_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: overlay_uniform_buffer.as_entire_binding(),
            }],
        });

        let transform_buffer_size = INITIAL_INSTANCES;
        let transform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Transform Buffer"),
            size: (std::mem::size_of::<ModelMatrix>() * transform_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let materials_buffer_size = INITIAL_MATERIALS;
        let materials_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Materials Buffer"),
            size: (std::mem::size_of::<MaterialData>() * materials_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let lights_buffer_size = INITIAL_LIGHTS;
        let lights_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Lights Buffer"),
            size: (std::mem::size_of::<LightData>() * lights_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let object_buffer_size = INITIAL_INSTANCES;
        let object_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Object Buffer"),
            size: (std::mem::size_of::<ObjectData>() * object_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let custom_data_buffer_size = INITIAL_INSTANCES;
        let custom_data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Custom Data Buffer"),
            size: (std::mem::size_of::<[f32; 4]>() * custom_data_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let morph_displacement_buffer_size =
            (std::mem::size_of::<MorphDisplacement>() * 1000) as u64;
        let morph_displacement_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Morph Displacement Buffer"),
            size: morph_displacement_buffer_size,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let cluster_uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Cluster Uniforms Buffer"),
            size: std::mem::size_of::<ClusterUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let cluster_bounds_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Cluster Bounds Buffer"),
            size: (std::mem::size_of::<ClusterBounds>() * TOTAL_CLUSTERS as usize) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let light_grid_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Light Grid Buffer"),
            size: (std::mem::size_of::<LightGrid>() * TOTAL_CLUSTERS as usize) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let light_grid_reset_data: Vec<LightGrid> = vec![
            LightGrid {
                offset: 0,
                count: 0
            };
            TOTAL_CLUSTERS as usize
        ];
        let light_grid_reset_buffer =
            device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
                label: Some("Light Grid Reset Buffer"),
                contents: bytemuck::cast_slice(&light_grid_reset_data),
                usage: wgpu::BufferUsages::COPY_SRC,
            });

        let light_indices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Light Indices Buffer"),
            size: (std::mem::size_of::<u32>()
                * TOTAL_CLUSTERS as usize
                * MAX_LIGHTS_PER_CLUSTER as usize) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let view_matrix_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("View Matrix Buffer"),
            size: std::mem::size_of::<[[f32; 4]; 4]>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let instance_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Mesh Instance Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::VERTEX,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 3,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 4,
                        visibility: wgpu::ShaderStages::VERTEX,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 5,
                        visibility: wgpu::ShaderStages::VERTEX,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 6,
                        visibility: wgpu::ShaderStages::VERTEX,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 7,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 8,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 9,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                ],
            });

        let visible_indices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Visible Indices Buffer"),
            size: (std::mem::size_of::<u32>() * INITIAL_INSTANCES) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let phase1_visible_indices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Phase 1 Visible Indices Buffer"),
            size: (std::mem::size_of::<u32>() * INITIAL_INSTANCES) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Mesh Instance Bind Group"),
            layout: &instance_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: transform_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: materials_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: object_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: lights_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 4,
                    resource: visible_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 5,
                    resource: custom_data_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 6,
                    resource: morph_displacement_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 7,
                    resource: light_grid_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 8,
                    resource: light_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 9,
                    resource: cluster_uniforms_buffer.as_entire_binding(),
                },
            ],
        });

        let phase1_instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Phase 1 Instance Bind Group"),
            layout: &instance_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: transform_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: materials_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: object_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: lights_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 4,
                    resource: phase1_visible_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 5,
                    resource: custom_data_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 6,
                    resource: morph_displacement_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 7,
                    resource: light_grid_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 8,
                    resource: light_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 9,
                    resource: cluster_uniforms_buffer.as_entire_binding(),
                },
            ],
        });

        let oit_accum_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("OIT Accumulation Texture"),
            size: wgpu::Extent3d {
                width: window_size.0,
                height: window_size.1,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba16Float,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
            view_formats: &[],
        });

        let oit_reveal_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("OIT Reveal Texture"),
            size: wgpu::Extent3d {
                width: window_size.0,
                height: window_size.1,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::R8Unorm,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
            view_formats: &[],
        });

        let oit_accum_view = oit_accum_texture.create_view(&wgpu::TextureViewDescriptor::default());
        let oit_reveal_view =
            oit_reveal_texture.create_view(&wgpu::TextureViewDescriptor::default());

        let overlay_depth_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Overlay Depth Texture"),
            size: wgpu::Extent3d {
                width: window_size.0,
                height: window_size.1,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: depth_format,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });

        let overlay_depth_view =
            overlay_depth_texture.create_view(&wgpu::TextureViewDescriptor::default());

        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("OIT Sampler"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            mipmap_filter: wgpu::FilterMode::Nearest,
            ..Default::default()
        });

        let texture_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Mesh Texture Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 3,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 4,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 5,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 6,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 7,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 8,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 9,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 10,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 11,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 12,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 13,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 14,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 15,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 16,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 17,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                ],
            });

        let dummy_white_texture = device.create_texture_with_data(
            queue,
            &wgpu::TextureDescriptor {
                label: Some("Dummy White Texture"),
                size: wgpu::Extent3d {
                    width: 1,
                    height: 1,
                    depth_or_array_layers: 1,
                },
                mip_level_count: 1,
                sample_count: 1,
                dimension: wgpu::TextureDimension::D2,
                format: wgpu::TextureFormat::Rgba8Unorm,
                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
                view_formats: &[],
            },
            wgpu::util::TextureDataOrder::LayerMajor,
            &[255, 255, 255, 255],
        );

        let dummy_white_view =
            dummy_white_texture.create_view(&wgpu::TextureViewDescriptor::default());

        let dummy_black_texture = device.create_texture_with_data(
            queue,
            &wgpu::TextureDescriptor {
                label: Some("Dummy Black Texture"),
                size: wgpu::Extent3d {
                    width: 1,
                    height: 1,
                    depth_or_array_layers: 1,
                },
                mip_level_count: 1,
                sample_count: 1,
                dimension: wgpu::TextureDimension::D2,
                format: wgpu::TextureFormat::Rgba8Unorm,
                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
                view_formats: &[],
            },
            wgpu::util::TextureDataOrder::LayerMajor,
            &[0, 0, 0, 255],
        );

        let dummy_black_view =
            dummy_black_texture.create_view(&wgpu::TextureViewDescriptor::default());

        let dummy_normal_texture = device.create_texture_with_data(
            queue,
            &wgpu::TextureDescriptor {
                label: Some("Dummy Normal Texture"),
                size: wgpu::Extent3d {
                    width: 1,
                    height: 1,
                    depth_or_array_layers: 1,
                },
                mip_level_count: 1,
                sample_count: 1,
                dimension: wgpu::TextureDimension::D2,
                format: wgpu::TextureFormat::Rgba8Unorm,
                usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
                view_formats: &[],
            },
            wgpu::util::TextureDataOrder::LayerMajor,
            &[127, 127, 255, 255],
        );

        let dummy_normal_view =
            dummy_normal_texture.create_view(&wgpu::TextureViewDescriptor::default());

        let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Dummy Texture Sampler"),
            address_mode_u: wgpu::AddressMode::Repeat,
            address_mode_v: wgpu::AddressMode::Repeat,
            address_mode_w: wgpu::AddressMode::Repeat,
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            mipmap_filter: wgpu::FilterMode::Linear,
            ..Default::default()
        });

        let dummy_shadow_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Dummy Shadow Texture"),
            size: wgpu::Extent3d {
                width: 1,
                height: 1,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Depth32Float,
            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });

        let dummy_shadow_view =
            dummy_shadow_texture.create_view(&wgpu::TextureViewDescriptor::default());

        let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Shadow Sampler"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Nearest,
            min_filter: wgpu::FilterMode::Nearest,
            mipmap_filter: wgpu::FilterMode::Nearest,
            ..Default::default()
        });

        let spotlight_shadow_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Spotlight Shadow Buffer"),
            size: (std::mem::size_of::<crate::render::wgpu::passes::SpotlightShadowData>()
                * MAX_SPOTLIGHT_SHADOWS) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let point_shadow_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Point Light Shadow Buffer"),
            size: (std::mem::size_of::<PointLightShadowData>() * MAX_POINT_LIGHT_SHADOWS) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let point_shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Point Shadow Sampler"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Nearest,
            min_filter: wgpu::FilterMode::Nearest,
            mipmap_filter: wgpu::FilterMode::Nearest,
            ..Default::default()
        });

        let scene_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Scene Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Depth,
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Depth,
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 3,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 4,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 5,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 6,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 7,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::Cube,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 8,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 9,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::Cube,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 10,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 11,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
                            view_dimension: wgpu::TextureViewDimension::CubeArray,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 12,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 13,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                ],
            });

        let ibl_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("IBL Sampler"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            mipmap_filter: wgpu::FilterMode::Linear,
            ..Default::default()
        });

        let dummy_brdf_lut = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Dummy BRDF LUT"),
            size: wgpu::Extent3d {
                width: 1,
                height: 1,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba16Float,
            usage: wgpu::TextureUsages::TEXTURE_BINDING,
            view_formats: &[],
        });
        let dummy_brdf_lut_view =
            dummy_brdf_lut.create_view(&wgpu::TextureViewDescriptor::default());

        let dummy_cubemap = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Dummy IBL Cubemap"),
            size: wgpu::Extent3d {
                width: 1,
                height: 1,
                depth_or_array_layers: 6,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba16Float,
            usage: wgpu::TextureUsages::TEXTURE_BINDING,
            view_formats: &[],
        });
        let dummy_cubemap_view = dummy_cubemap.create_view(&wgpu::TextureViewDescriptor {
            dimension: Some(wgpu::TextureViewDimension::Cube),
            ..Default::default()
        });

        let dummy_point_shadow_cubemap = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Dummy Point Shadow Cubemap Array"),
            size: wgpu::Extent3d {
                width: 1,
                height: 1,
                depth_or_array_layers: 6 * MAX_POINT_LIGHT_SHADOWS as u32,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::R32Float,
            usage: wgpu::TextureUsages::TEXTURE_BINDING,
            view_formats: &[],
        });
        let dummy_point_shadow_view =
            dummy_point_shadow_cubemap.create_view(&wgpu::TextureViewDescriptor {
                dimension: Some(wgpu::TextureViewDimension::CubeArray),
                ..Default::default()
            });

        let scene_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Scene Bind Group"),
            layout: &scene_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&dummy_shadow_view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&shadow_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: wgpu::BindingResource::TextureView(&dummy_shadow_view),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: wgpu::BindingResource::Sampler(&shadow_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 4,
                    resource: spotlight_shadow_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 5,
                    resource: wgpu::BindingResource::TextureView(&dummy_brdf_lut_view),
                },
                wgpu::BindGroupEntry {
                    binding: 6,
                    resource: wgpu::BindingResource::Sampler(&ibl_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 7,
                    resource: wgpu::BindingResource::TextureView(&dummy_cubemap_view),
                },
                wgpu::BindGroupEntry {
                    binding: 8,
                    resource: wgpu::BindingResource::Sampler(&ibl_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 9,
                    resource: wgpu::BindingResource::TextureView(&dummy_cubemap_view),
                },
                wgpu::BindGroupEntry {
                    binding: 10,
                    resource: wgpu::BindingResource::Sampler(&ibl_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 11,
                    resource: wgpu::BindingResource::TextureView(&dummy_point_shadow_view),
                },
                wgpu::BindGroupEntry {
                    binding: 12,
                    resource: wgpu::BindingResource::Sampler(&point_shadow_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 13,
                    resource: point_shadow_buffer.as_entire_binding(),
                },
            ],
        });

        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("Mesh Pipeline Layout"),
            bind_group_layouts: &[
                &uniform_bind_group_layout,
                &instance_bind_group_layout,
                &texture_bind_group_layout,
                &scene_bind_group_layout,
            ],
            push_constant_ranges: &[],
        });

        let depth_prepass_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Depth Prepass Pipeline Layout"),
                bind_group_layouts: &[&uniform_bind_group_layout, &instance_bind_group_layout],
                push_constant_ranges: &[],
            });

        let vertex_attributes = [
            wgpu::VertexAttribute {
                offset: 0,
                shader_location: 0,
                format: wgpu::VertexFormat::Float32x3,
            },
            wgpu::VertexAttribute {
                offset: 12,
                shader_location: 1,
                format: wgpu::VertexFormat::Float32x3,
            },
            wgpu::VertexAttribute {
                offset: 24,
                shader_location: 2,
                format: wgpu::VertexFormat::Float32x2,
            },
            wgpu::VertexAttribute {
                offset: 32,
                shader_location: 3,
                format: wgpu::VertexFormat::Float32x2,
            },
            wgpu::VertexAttribute {
                offset: 40,
                shader_location: 4,
                format: wgpu::VertexFormat::Float32x4,
            },
            wgpu::VertexAttribute {
                offset: 56,
                shader_location: 5,
                format: wgpu::VertexFormat::Float32x4,
            },
        ];

        let vertex_buffer_layout = wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Vertex,
            attributes: &vertex_attributes,
        };

        let opaque_targets: Vec<Option<wgpu::ColorTargetState>> = vec![
            Some(wgpu::ColorTargetState {
                format: color_format,
                blend: Some(wgpu::BlendState::REPLACE),
                write_mask: wgpu::ColorWrites::ALL,
            }),
            Some(wgpu::ColorTargetState {
                format: wgpu::TextureFormat::R32Float,
                blend: None,
                write_mask: wgpu::ColorWrites::ALL,
            }),
            Some(wgpu::ColorTargetState {
                format: wgpu::TextureFormat::Rgba16Float,
                blend: None,
                write_mask: wgpu::ColorWrites::ALL,
            }),
        ];

        let opaque_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("Opaque Mesh Pipeline"),
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: Some("vs_main"),
                buffers: std::slice::from_ref(&vertex_buffer_layout),
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: Some("fs_main"),
                targets: &opaque_targets,
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: Some(wgpu::Face::Back),
                polygon_mode: wgpu::PolygonMode::Fill,
                unclipped_depth: false,
                conservative: false,
            },
            depth_stencil: Some(wgpu::DepthStencilState {
                format: depth_format,
                depth_write_enabled: true,
                depth_compare: wgpu::CompareFunction::GreaterEqual,
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let opaque_double_sided_pipeline =
            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                label: Some("Opaque Double-Sided Mesh Pipeline"),
                layout: Some(&pipeline_layout),
                vertex: wgpu::VertexState {
                    module: &shader,
                    entry_point: Some("vs_main"),
                    buffers: std::slice::from_ref(&vertex_buffer_layout),
                    compilation_options: Default::default(),
                },
                fragment: Some(wgpu::FragmentState {
                    module: &shader,
                    entry_point: Some("fs_main"),
                    targets: &opaque_targets,
                    compilation_options: Default::default(),
                }),
                primitive: wgpu::PrimitiveState {
                    topology: wgpu::PrimitiveTopology::TriangleList,
                    strip_index_format: None,
                    front_face: wgpu::FrontFace::Ccw,
                    cull_mode: None,
                    polygon_mode: wgpu::PolygonMode::Fill,
                    unclipped_depth: false,
                    conservative: false,
                },
                depth_stencil: Some(wgpu::DepthStencilState {
                    format: depth_format,
                    depth_write_enabled: true,
                    depth_compare: wgpu::CompareFunction::GreaterEqual,
                    stencil: wgpu::StencilState::default(),
                    bias: wgpu::DepthBiasState::default(),
                }),
                multisample: wgpu::MultisampleState::default(),
                multiview: None,
                cache: None,
            });

        let overlay_opaque_pipeline =
            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                label: Some("Overlay Opaque Mesh Pipeline"),
                layout: Some(&pipeline_layout),
                vertex: wgpu::VertexState {
                    module: &shader,
                    entry_point: Some("vs_main"),
                    buffers: &[vertex_buffer_layout],
                    compilation_options: Default::default(),
                },
                fragment: Some(wgpu::FragmentState {
                    module: &shader,
                    entry_point: Some("fs_main"),
                    targets: &opaque_targets,
                    compilation_options: Default::default(),
                }),
                primitive: wgpu::PrimitiveState {
                    topology: wgpu::PrimitiveTopology::TriangleList,
                    strip_index_format: None,
                    front_face: wgpu::FrontFace::Ccw,
                    cull_mode: Some(wgpu::Face::Back),
                    polygon_mode: wgpu::PolygonMode::Fill,
                    unclipped_depth: false,
                    conservative: false,
                },
                depth_stencil: Some(wgpu::DepthStencilState {
                    format: depth_format,
                    depth_write_enabled: true,
                    depth_compare: wgpu::CompareFunction::Greater,
                    stencil: wgpu::StencilState::default(),
                    bias: wgpu::DepthBiasState::default(),
                }),
                multisample: wgpu::MultisampleState::default(),
                multiview: None,
                cache: None,
            });

        let vertex_buffer_layout2 = wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Vertex,
            attributes: &vertex_attributes,
        };

        let overlay_opaque_double_sided_pipeline =
            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                label: Some("Overlay Opaque Double-Sided Mesh Pipeline"),
                layout: Some(&pipeline_layout),
                vertex: wgpu::VertexState {
                    module: &shader,
                    entry_point: Some("vs_main"),
                    buffers: std::slice::from_ref(&vertex_buffer_layout2),
                    compilation_options: Default::default(),
                },
                fragment: Some(wgpu::FragmentState {
                    module: &shader,
                    entry_point: Some("fs_main"),
                    targets: &opaque_targets,
                    compilation_options: Default::default(),
                }),
                primitive: wgpu::PrimitiveState {
                    topology: wgpu::PrimitiveTopology::TriangleList,
                    strip_index_format: None,
                    front_face: wgpu::FrontFace::Ccw,
                    cull_mode: None,
                    polygon_mode: wgpu::PolygonMode::Fill,
                    unclipped_depth: false,
                    conservative: false,
                },
                depth_stencil: Some(wgpu::DepthStencilState {
                    format: depth_format,
                    depth_write_enabled: true,
                    depth_compare: wgpu::CompareFunction::Greater,
                    stencil: wgpu::StencilState::default(),
                    bias: wgpu::DepthBiasState::default(),
                }),
                multisample: wgpu::MultisampleState::default(),
                multiview: None,
                cache: None,
            });

        let depth_prepass_vertex_buffer_layout = wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Vertex,
            attributes: &vertex_attributes,
        };

        let depth_prepass_pipeline =
            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                label: Some("Depth Prepass Pipeline"),
                layout: Some(&depth_prepass_pipeline_layout),
                vertex: wgpu::VertexState {
                    module: &depth_prepass_shader,
                    entry_point: Some("vs_main"),
                    buffers: std::slice::from_ref(&depth_prepass_vertex_buffer_layout),
                    compilation_options: Default::default(),
                },
                fragment: None,
                primitive: wgpu::PrimitiveState {
                    topology: wgpu::PrimitiveTopology::TriangleList,
                    strip_index_format: None,
                    front_face: wgpu::FrontFace::Ccw,
                    cull_mode: Some(wgpu::Face::Back),
                    polygon_mode: wgpu::PolygonMode::Fill,
                    unclipped_depth: false,
                    conservative: false,
                },
                depth_stencil: Some(wgpu::DepthStencilState {
                    format: depth_format,
                    depth_write_enabled: true,
                    depth_compare: wgpu::CompareFunction::Greater,
                    stencil: wgpu::StencilState::default(),
                    bias: wgpu::DepthBiasState::default(),
                }),
                multisample: wgpu::MultisampleState::default(),
                multiview: None,
                cache: None,
            });

        let oit_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("OIT Mesh Pipeline"),
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &oit_shader,
                entry_point: Some("vs_main"),
                buffers: &[wgpu::VertexBufferLayout {
                    array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
                    step_mode: wgpu::VertexStepMode::Vertex,
                    attributes: &vertex_attributes,
                }],
                compilation_options: Default::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &oit_shader,
                entry_point: Some("fs_main"),
                targets: &[
                    Some(wgpu::ColorTargetState {
                        format: wgpu::TextureFormat::Rgba16Float,
                        blend: Some(wgpu::BlendState {
                            color: wgpu::BlendComponent {
                                src_factor: wgpu::BlendFactor::One,
                                dst_factor: wgpu::BlendFactor::One,
                                operation: wgpu::BlendOperation::Add,
                            },
                            alpha: wgpu::BlendComponent {
                                src_factor: wgpu::BlendFactor::One,
                                dst_factor: wgpu::BlendFactor::One,
                                operation: wgpu::BlendOperation::Add,
                            },
                        }),
                        write_mask: wgpu::ColorWrites::ALL,
                    }),
                    Some(wgpu::ColorTargetState {
                        format: wgpu::TextureFormat::R8Unorm,
                        blend: Some(wgpu::BlendState {
                            color: wgpu::BlendComponent {
                                src_factor: wgpu::BlendFactor::Zero,
                                dst_factor: wgpu::BlendFactor::Src,
                                operation: wgpu::BlendOperation::Add,
                            },
                            alpha: wgpu::BlendComponent {
                                src_factor: wgpu::BlendFactor::Zero,
                                dst_factor: wgpu::BlendFactor::Src,
                                operation: wgpu::BlendOperation::Add,
                            },
                        }),
                        write_mask: wgpu::ColorWrites::ALL,
                    }),
                ],
                compilation_options: Default::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                strip_index_format: None,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                polygon_mode: wgpu::PolygonMode::Fill,
                unclipped_depth: false,
                conservative: false,
            },
            depth_stencil: Some(wgpu::DepthStencilState {
                format: depth_format,
                depth_write_enabled: false,
                depth_compare: wgpu::CompareFunction::Greater,
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        let composite_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("OIT Composite Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 3,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                ],
            });

        let oit_composite_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("OIT Composite Bind Group"),
            layout: &composite_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&oit_accum_view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: wgpu::BindingResource::TextureView(&oit_reveal_view),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: wgpu::BindingResource::Sampler(&sampler),
                },
            ],
        });

        let composite_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("OIT Composite Pipeline Layout"),
                bind_group_layouts: &[&composite_bind_group_layout],
                push_constant_ranges: &[],
            });

        let oit_composite_pipeline =
            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                label: Some("OIT Composite Pipeline"),
                layout: Some(&composite_pipeline_layout),
                vertex: wgpu::VertexState {
                    module: &composite_shader,
                    entry_point: Some("vs_main"),
                    buffers: &[],
                    compilation_options: Default::default(),
                },
                fragment: Some(wgpu::FragmentState {
                    module: &composite_shader,
                    entry_point: Some("fs_main"),
                    targets: &[Some(wgpu::ColorTargetState {
                        format: color_format,
                        blend: Some(wgpu::BlendState::ALPHA_BLENDING),
                        write_mask: wgpu::ColorWrites::ALL,
                    })],
                    compilation_options: Default::default(),
                }),
                primitive: wgpu::PrimitiveState {
                    topology: wgpu::PrimitiveTopology::TriangleList,
                    strip_index_format: None,
                    front_face: wgpu::FrontFace::Ccw,
                    cull_mode: None,
                    polygon_mode: wgpu::PolygonMode::Fill,
                    unclipped_depth: false,
                    conservative: false,
                },
                depth_stencil: None,
                multisample: wgpu::MultisampleState::default(),
                multiview: None,
                cache: None,
            });

        let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Vertex Buffer"),
            size: (std::mem::size_of::<Vertex>() * INITIAL_VERTEX_BUFFER_SIZE) as u64,
            usage: wgpu::BufferUsages::VERTEX
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Index Buffer"),
            size: (std::mem::size_of::<u32>() * INITIAL_INDEX_BUFFER_SIZE) as u64,
            usage: wgpu::BufferUsages::INDEX
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let vertex_buffer_size =
            (std::mem::size_of::<Vertex>() * INITIAL_VERTEX_BUFFER_SIZE) as u64;
        let index_buffer_size = (std::mem::size_of::<u32>() * INITIAL_INDEX_BUFFER_SIZE) as u64;

        let culling_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("Mesh Culling Shader"),
            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
                "../../../shaders/mesh_culling.wgsl"
            ))),
        });

        let culling_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Mesh Culling Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 3,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 4,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: false },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 5,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: false },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 6,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 7,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                ],
            });

        let culling_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Mesh Culling Pipeline Layout"),
                bind_group_layouts: &[&culling_bind_group_layout],
                push_constant_ranges: &[],
            });

        let culling_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
            label: Some("Mesh Culling Pipeline"),
            layout: Some(&culling_pipeline_layout),
            module: &culling_shader,
            entry_point: Some("main"),
            compilation_options: Default::default(),
            cache: None,
        });

        let culling_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Culling Uniform Buffer"),
            size: std::mem::size_of::<CullingUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let mesh_bounds_buffer_size = 64;
        let mesh_bounds_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh Bounds Buffer"),
            size: (std::mem::size_of::<MeshBoundsData>() * mesh_bounds_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let mesh_aabbs_buffer_size = 64;
        let mesh_aabbs_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Mesh AABBs Buffer"),
            size: (std::mem::size_of::<MeshBoundsAABB>() * mesh_aabbs_buffer_size) as u64,
            usage: wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let hiz_pass = HizPass::new(device);

        let indirect_buffer_size = INITIAL_INSTANCES;
        let indirect_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Indirect Draw Buffer"),
            size: (std::mem::size_of::<DrawIndexedIndirect>() * indirect_buffer_size) as u64,
            usage: wgpu::BufferUsages::INDIRECT
                | wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::COPY_DST
                | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let indirect_reset_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Indirect Reset Buffer"),
            size: (std::mem::size_of::<DrawIndexedIndirect>() * indirect_buffer_size) as u64,
            usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let phase1_indirect_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Phase 1 Indirect Buffer"),
            size: (std::mem::size_of::<DrawIndexedIndirect>() * indirect_buffer_size) as u64,
            usage: wgpu::BufferUsages::INDIRECT
                | wgpu::BufferUsages::STORAGE
                | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let phase1_indirect_reset_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Phase 1 Indirect Reset Buffer"),
            size: (std::mem::size_of::<DrawIndexedIndirect>() * indirect_buffer_size) as u64,
            usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let phase1_culling_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Phase 1 Culling Uniform Buffer"),
            size: std::mem::size_of::<CullingUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let cluster_bounds_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("Cluster Bounds Shader"),
            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
                "../../../shaders/cluster_bounds.wgsl"
            ))),
        });

        let cluster_assign_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("Cluster Light Assign Shader"),
            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
                "../../../shaders/cluster_light_assign.wgsl"
            ))),
        });

        let cluster_bounds_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Cluster Bounds Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: false },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                ],
            });

        let cluster_bounds_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Cluster Bounds Bind Group"),
            layout: &cluster_bounds_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: cluster_uniforms_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: cluster_bounds_buffer.as_entire_binding(),
                },
            ],
        });

        let cluster_bounds_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Cluster Bounds Pipeline Layout"),
                bind_group_layouts: &[&cluster_bounds_bind_group_layout],
                push_constant_ranges: &[],
            });

        let cluster_bounds_pipeline =
            device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
                label: Some("Cluster Bounds Pipeline"),
                layout: Some(&cluster_bounds_pipeline_layout),
                module: &cluster_bounds_shader,
                entry_point: Some("main"),
                compilation_options: Default::default(),
                cache: None,
            });

        let cluster_assign_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Cluster Light Assign Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: false },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 3,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: false },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 4,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 5,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                ],
            });

        let cluster_assign_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Cluster Light Assign Pipeline Layout"),
                bind_group_layouts: &[&cluster_assign_bind_group_layout],
                push_constant_ranges: &[],
            });

        let cluster_assign_pipeline =
            device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
                label: Some("Cluster Light Assign Pipeline"),
                layout: Some(&cluster_assign_pipeline_layout),
                module: &cluster_assign_shader,
                entry_point: Some("main"),
                compilation_options: Default::default(),
                cache: None,
            });

        let transform_compute_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("Transform Compute Shader"),
            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
                "../../../shaders/transform_compute.wgsl"
            ))),
        });

        let transform_compute_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Transform Compute Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: true },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: false },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                ],
            });

        let transform_compute_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Transform Compute Pipeline Layout"),
                bind_group_layouts: &[&transform_compute_bind_group_layout],
                push_constant_ranges: &[],
            });

        let transform_compute_pipeline =
            device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
                label: Some("Transform Compute Pipeline"),
                layout: Some(&transform_compute_pipeline_layout),
                module: &transform_compute_shader,
                entry_point: Some("main"),
                compilation_options: Default::default(),
                cache: None,
            });

        let local_transform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Local Transform Buffer"),
            size: (std::mem::size_of::<GpuLocalTransform>() * INITIAL_INSTANCES) as u64,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let transform_compute_uniforms_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Transform Compute Uniforms Buffer"),
            size: std::mem::size_of::<TransformComputeUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let transform_compute_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Transform Compute Staging Buffer"),
            size: (std::mem::size_of::<TransformComputeUniforms>() * MAX_HIERARCHY_DEPTH as usize)
                as u64,
            usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let mut pass = Self {
            opaque_pipeline,
            opaque_double_sided_pipeline,
            overlay_opaque_pipeline,
            overlay_opaque_double_sided_pipeline,
            oit_pipeline,
            oit_composite_pipeline,
            oit_accum_view,
            oit_reveal_view,
            oit_composite_bind_group,
            oit_composite_bind_group_layout: composite_bind_group_layout,
            oit_sampler: sampler,
            overlay_depth_view,
            uniform_buffer,
            uniform_bind_group,
            overlay_uniform_buffer,
            overlay_uniform_bind_group,
            transform_buffer,
            materials_buffer,
            lights_buffer,
            object_buffer,
            custom_data_buffer,
            instance_bind_group,
            instance_bind_group_layout,
            texture_bind_group_layout,
            scene_bind_group,
            scene_bind_group_layout: scene_bind_group_layout.clone(),
            shadow_sampler,
            spotlight_shadow_buffer,
            point_shadow_buffer,
            point_shadow_sampler,
            ibl_sampler,
            dummy_white_view,
            dummy_black_view,
            dummy_normal_view,
            dummy_sampler,
            transform_buffer_size,
            materials_buffer_size,
            lights_buffer_size,
            object_buffer_size,
            custom_data_buffer_size,
            vertex_buffer,
            index_buffer,
            vertex_buffer_size,
            index_buffer_size,
            meshes: HashMap::new(),
            mesh_data: Vec::new(),
            opaque_instances: Vec::new(),
            opaque_double_sided_instances: Vec::new(),
            transparent_instances: Vec::new(),
            overlay_opaque_instances: Vec::new(),
            overlay_opaque_double_sided_instances: Vec::new(),
            overlay_transparent_instances: Vec::new(),
            regular_object_count: 0,
            material_bind_groups: HashMap::new(),
            material_bind_group_cache_key: HashMap::new(),
            registered_textures: HashMap::new(),
            newly_registered_textures: HashSet::new(),
            current_vertex_offset: 0,
            current_index_offset: 0,
            compaction_frame_counter: 0,
            last_vertex_utilization: 1.0,
            last_index_utilization: 1.0,
            culling_pipeline,
            culling_bind_group_layout,
            culling_bind_group: None,
            culling_uniform_buffer,
            object_count: 0,

            mesh_bounds_buffer,
            mesh_bounds_buffer_size,
            mesh_bounds_data: Vec::new(),
            mesh_aabbs_buffer,
            mesh_aabbs_buffer_size,
            mesh_aabbs_data: Vec::new(),
            hiz_pass,
            indirect_buffer,
            indirect_buffer_size,
            indirect_reset_buffer,
            indirect_reset_count: 0,
            visible_indices_buffer,

            depth_prepass_pipeline,
            phase1_indirect_buffer,
            phase1_indirect_reset_buffer,
            phase1_visible_indices_buffer,
            phase1_culling_bind_group: None,
            phase1_instance_bind_group,
            phase1_culling_uniform_buffer,

            morph_displacement_buffer,
            morph_displacement_buffer_size,
            current_morph_displacement_offset: 0,

            brdf_lut_view: dummy_brdf_lut_view,
            irradiance_view: dummy_cubemap_view.clone(),
            prefiltered_view: dummy_cubemap_view,
            point_shadow_cubemap_view: dummy_point_shadow_view,

            gpu_registry: GpuEntityRegistry::new(),
            cached_transforms: Vec::new(),
            cached_objects: Vec::new(),
            cached_entities: Vec::new(),

            cluster_uniforms_buffer,
            cluster_bounds_buffer,
            light_grid_buffer,
            light_grid_reset_buffer,
            light_indices_buffer,
            cluster_bounds_pipeline,
            cluster_assign_pipeline,
            _cluster_bounds_bind_group_layout: cluster_bounds_bind_group_layout,
            cluster_bounds_bind_group,
            cluster_assign_bind_group_layout,
            cluster_assign_bind_group: None,
            view_matrix_buffer,
            last_camera_hash: 0,
            camera_changed: true,
            num_directional_lights: 0,
            num_total_lights: 0,

            local_transform_buffer,
            transform_compute_pipeline,
            transform_compute_bind_group_layout,
            transform_compute_bind_group: None,
            transform_compute_uniforms_buffer,
            transform_compute_staging_buffer,
            max_hierarchy_depth: 0,
            hierarchy_dirty: true,
            entity_depths: Vec::new(),
            entity_parent_indices: Vec::new(),
            entity_to_transform_index: HashMap::new(),
            local_transform_buffer_size: INITIAL_LOCAL_TRANSFORM_BUFFER_SIZE,
        };

        pass.add_mesh(device, queue, "Cube", create_cube_mesh());
        pass.add_mesh(device, queue, "Sphere", create_sphere_mesh(1.0, 16));
        pass.add_mesh(device, queue, "Plane", create_plane_mesh(2.0));
        pass.add_mesh(
            device,
            queue,
            "SubdividedPlane",
            create_subdivided_plane_mesh(2.0, 20),
        );
        pass.add_mesh(
            device,
            queue,
            "Cylinder",
            create_cylinder_mesh(0.5, 1.0, 16),
        );
        pass.add_mesh(device, queue, "Cone", create_cone_mesh(0.5, 1.0, 16));
        pass.add_mesh(device, queue, "Torus", create_torus_mesh(1.0, 0.3, 16, 16));

        pass.rebuild_transform_compute_bind_group(device);

        pass
    }

    pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
        let oit_accum_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("OIT Accumulation Texture"),
            size: wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba16Float,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
            view_formats: &[],
        });

        let oit_reveal_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("OIT Reveal Texture"),
            size: wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::R8Unorm,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
            view_formats: &[],
        });

        self.oit_accum_view =
            oit_accum_texture.create_view(&wgpu::TextureViewDescriptor::default());
        self.oit_reveal_view =
            oit_reveal_texture.create_view(&wgpu::TextureViewDescriptor::default());

        let overlay_depth_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Overlay Depth Texture"),
            size: wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Depth32Float,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        });

        self.overlay_depth_view =
            overlay_depth_texture.create_view(&wgpu::TextureViewDescriptor::default());

        self.oit_composite_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("OIT Composite Bind Group"),
            layout: &self.oit_composite_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&self.oit_accum_view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&self.oit_sampler),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: wgpu::BindingResource::TextureView(&self.oit_reveal_view),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: wgpu::BindingResource::Sampler(&self.oit_sampler),
                },
            ],
        });

        self.culling_bind_group = None;
        self.hiz_pass.resize(device, width, height);
    }

    pub fn update_ibl_textures(
        &mut self,
        brdf_lut_view: wgpu::TextureView,
        irradiance_view: wgpu::TextureView,
        prefiltered_view: wgpu::TextureView,
    ) {
        self.brdf_lut_view = brdf_lut_view;
        self.irradiance_view = irradiance_view;
        self.prefiltered_view = prefiltered_view;
    }

    pub fn update_point_shadow_cubemap(&mut self, view: wgpu::TextureView) {
        self.point_shadow_cubemap_view = view;
    }

    pub fn register_texture(
        &mut self,
        name: String,
        view: wgpu::TextureView,
        sampler: wgpu::Sampler,
    ) {
        self.newly_registered_textures.insert(name.clone());
        self.registered_textures.insert(name, (view, sampler));
    }

    pub fn register_texture_with_data(
        &mut self,
        name: String,
        view: wgpu::TextureView,
        sampler: wgpu::Sampler,
        _rgba_data: Vec<u8>,
        _width: u32,
        _height: u32,
    ) {
        self.newly_registered_textures.insert(name.clone());
        self.registered_textures.insert(name, (view, sampler));
    }

    pub fn unregister_texture(&mut self, name: &str) {
        self.registered_textures.remove(name);
    }

    pub fn sync_textures(
        &mut self,
        texture_cache: &crate::render::wgpu::texture_cache::TextureCache,
    ) {
        self.registered_textures
            .retain(|name, _| texture_cache.registry.name_to_index.contains_key(name));
    }

    pub fn sync_meshes_from_cache(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        mesh_cache: &crate::ecs::prefab::resources::MeshCache,
    ) {
        for dirty_name in mesh_cache_take_dirty(mesh_cache) {
            if let Some(&mesh_id) = self.meshes.get(&dirty_name)
                && let Some(mesh) = registry_entry_by_name(&mesh_cache.registry, &dirty_name)
            {
                let mesh_data = &mut self.mesh_data[mesh_id as usize];
                if mesh.vertices.len() as u32 == mesh_data.vertex_count {
                    let vertex_data = bytemuck::cast_slice(&mesh.vertices);
                    let vertex_offset_bytes =
                        (mesh_data.vertex_offset as usize * std::mem::size_of::<Vertex>()) as u64;
                    queue.write_buffer(&self.vertex_buffer, vertex_offset_bytes, vertex_data);

                    if let Some(morph_targets) = &mesh.morph_targets {
                        let vertex_count = mesh.vertices.len();
                        let target_count = morph_targets.targets.len().min(8);
                        let total_displacements = vertex_count * target_count;

                        if total_displacements > 0 {
                            if mesh_data.morph_target_count == 0 {
                                mesh_data.morph_displacement_offset =
                                    self.current_morph_displacement_offset;
                                mesh_data.morph_target_count = target_count as u32;
                                self.current_morph_displacement_offset +=
                                    total_displacements as u32;
                            }

                            let mut displacements = Vec::with_capacity(total_displacements);

                            for target_index in 0..target_count {
                                let target = &morph_targets.targets[target_index];
                                for vertex_index in 0..vertex_count {
                                    let position = target
                                        .position_displacements
                                        .get(vertex_index)
                                        .copied()
                                        .unwrap_or([0.0, 0.0, 0.0]);
                                    let normal = target
                                        .normal_displacements
                                        .as_ref()
                                        .and_then(|n| n.get(vertex_index))
                                        .copied()
                                        .unwrap_or([0.0, 0.0, 0.0]);
                                    let tangent = target
                                        .tangent_displacements
                                        .as_ref()
                                        .and_then(|t| t.get(vertex_index))
                                        .copied()
                                        .unwrap_or([0.0, 0.0, 0.0]);

                                    displacements.push(MorphDisplacement {
                                        position,
                                        _pad0: 0.0,
                                        normal,
                                        _pad1: 0.0,
                                        tangent,
                                        _pad2: 0.0,
                                    });
                                }
                            }

                            let displacement_data = bytemuck::cast_slice(&displacements);
                            let displacement_offset_bytes = mesh_data.morph_displacement_offset
                                as u64
                                * std::mem::size_of::<MorphDisplacement>() as u64;
                            let required_size =
                                displacement_offset_bytes + displacement_data.len() as u64;

                            if required_size > self.morph_displacement_buffer_size {
                                let new_size = (required_size as f64 * BUFFER_GROWTH_FACTOR as f64)
                                    .ceil() as u64;
                                let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                                    label: Some("Mesh Morph Displacement Buffer (Resized)"),
                                    size: new_size,
                                    usage: wgpu::BufferUsages::STORAGE
                                        | wgpu::BufferUsages::COPY_DST
                                        | wgpu::BufferUsages::COPY_SRC,
                                    mapped_at_creation: false,
                                });

                                if displacement_offset_bytes > 0 {
                                    let mut encoder = device.create_command_encoder(
                                        &wgpu::CommandEncoderDescriptor {
                                            label: Some("Morph Displacement Buffer Reallocation"),
                                        },
                                    );
                                    encoder.copy_buffer_to_buffer(
                                        &self.morph_displacement_buffer,
                                        0,
                                        &new_buffer,
                                        0,
                                        displacement_offset_bytes,
                                    );
                                    queue.submit(std::iter::once(encoder.finish()));
                                }

                                self.morph_displacement_buffer = new_buffer;
                                self.morph_displacement_buffer_size = new_size;
                                self.rebuild_instance_bind_group(device);
                            }

                            queue.write_buffer(
                                &self.morph_displacement_buffer,
                                displacement_offset_bytes,
                                displacement_data,
                            );
                        }
                    }

                    continue;
                }
            }
            self.meshes.remove(&dirty_name);
        }

        let mut meshes_to_add = Vec::new();

        for (name, mesh) in mesh_cache_iter(mesh_cache) {
            if !self.meshes.contains_key(name) {
                meshes_to_add.push((name.clone(), mesh.clone()));
            }
        }

        for (name, mesh) in meshes_to_add {
            self.add_mesh(device, queue, &name, mesh);
        }
    }

    pub(super) fn compute_vertex_utilization(&self) -> f32 {
        let actual_vertex_count: u64 = self.mesh_data.iter().map(|m| m.vertex_count as u64).sum();
        let actual_vertex_bytes = actual_vertex_count * std::mem::size_of::<Vertex>() as u64;
        if self.vertex_buffer_size > 0 {
            actual_vertex_bytes as f32 / self.vertex_buffer_size as f32
        } else {
            1.0
        }
    }

    pub(super) fn compute_index_utilization(&self) -> f32 {
        let actual_index_count: u64 = self.mesh_data.iter().map(|m| m.index_count as u64).sum();
        let actual_index_bytes = actual_index_count * std::mem::size_of::<u32>() as u64;
        if self.index_buffer_size > 0 {
            actual_index_bytes as f32 / self.index_buffer_size as f32
        } else {
            1.0
        }
    }

    pub(super) fn check_and_compact_buffers(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        mesh_cache: &crate::ecs::prefab::resources::MeshCache,
    ) {
        let actual_vertex_count: u64 = self.mesh_data.iter().map(|m| m.vertex_count as u64).sum();
        let actual_index_count: u64 = self.mesh_data.iter().map(|m| m.index_count as u64).sum();

        let actual_vertex_bytes = actual_vertex_count * std::mem::size_of::<Vertex>() as u64;
        let actual_index_bytes = actual_index_count * std::mem::size_of::<u32>() as u64;

        let vertex_utilization = if self.vertex_buffer_size > 0 {
            actual_vertex_bytes as f32 / self.vertex_buffer_size as f32
        } else {
            1.0
        };

        let index_utilization = if self.index_buffer_size > 0 {
            actual_index_bytes as f32 / self.index_buffer_size as f32
        } else {
            1.0
        };

        let should_compact = vertex_utilization < BUFFER_SHRINK_THRESHOLD
            || index_utilization < BUFFER_SHRINK_THRESHOLD;

        if should_compact && actual_vertex_bytes > 0 && actual_index_bytes > 0 {
            self.meshes.clear();
            self.mesh_data.clear();
            self.current_vertex_offset = 0;
            self.current_index_offset = 0;
            self.current_morph_displacement_offset = 0;

            self.add_mesh(device, queue, "Cube", create_cube_mesh());
            self.add_mesh(device, queue, "Sphere", create_sphere_mesh(1.0, 16));
            self.add_mesh(device, queue, "Plane", create_plane_mesh(2.0));
            self.add_mesh(
                device,
                queue,
                "SubdividedPlane",
                create_subdivided_plane_mesh(2.0, 20),
            );
            self.add_mesh(
                device,
                queue,
                "Cylinder",
                create_cylinder_mesh(0.5, 1.0, 16),
            );
            self.add_mesh(device, queue, "Cone", create_cone_mesh(0.5, 1.0, 16));
            self.add_mesh(device, queue, "Torus", create_torus_mesh(1.0, 0.3, 16, 16));

            for (name, mesh) in mesh_cache_iter(mesh_cache) {
                if !self.meshes.contains_key(name) {
                    self.add_mesh(device, queue, name, mesh.clone());
                }
            }

            let new_vertex_size = (actual_vertex_bytes as f32 * BUFFER_GROWTH_FACTOR).ceil() as u64;
            let new_index_size = (actual_index_bytes as f32 * BUFFER_GROWTH_FACTOR).ceil() as u64;

            if new_vertex_size < self.vertex_buffer_size {
                let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                    label: Some("Mesh Vertex Buffer (Compacted)"),
                    size: new_vertex_size,
                    usage: wgpu::BufferUsages::VERTEX
                        | wgpu::BufferUsages::COPY_DST
                        | wgpu::BufferUsages::COPY_SRC,
                    mapped_at_creation: false,
                });

                let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                    label: Some("Mesh Vertex Buffer Compaction"),
                });

                encoder.copy_buffer_to_buffer(
                    &self.vertex_buffer,
                    0,
                    &new_buffer,
                    0,
                    actual_vertex_bytes,
                );

                queue.submit(std::iter::once(encoder.finish()));
                self.vertex_buffer = new_buffer;
                self.vertex_buffer_size = new_vertex_size;
            }

            if new_index_size < self.index_buffer_size {
                let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                    label: Some("Mesh Index Buffer (Compacted)"),
                    size: new_index_size,
                    usage: wgpu::BufferUsages::INDEX
                        | wgpu::BufferUsages::COPY_DST
                        | wgpu::BufferUsages::COPY_SRC,
                    mapped_at_creation: false,
                });

                let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                    label: Some("Mesh Index Buffer Compaction"),
                });

                encoder.copy_buffer_to_buffer(
                    &self.index_buffer,
                    0,
                    &new_buffer,
                    0,
                    actual_index_bytes,
                );

                queue.submit(std::iter::once(encoder.finish()));
                self.index_buffer = new_buffer;
                self.index_buffer_size = new_index_size;
            }
        }
    }

    pub(super) fn add_mesh(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        name: &str,
        mesh: Mesh,
    ) {
        let mesh_id = self.mesh_data.len() as u32;

        let vertex_data = bytemuck::cast_slice(&mesh.vertices);
        let vertex_offset_bytes =
            (self.current_vertex_offset as usize * std::mem::size_of::<Vertex>()) as u64;
        let required_vertex_size = vertex_offset_bytes + vertex_data.len() as u64;

        if required_vertex_size > self.vertex_buffer_size {
            let new_size = (required_vertex_size as f32 * BUFFER_GROWTH_FACTOR).ceil() as u64;

            let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Mesh Vertex Buffer (Resized)"),
                size: new_size,
                usage: wgpu::BufferUsages::VERTEX
                    | wgpu::BufferUsages::COPY_DST
                    | wgpu::BufferUsages::COPY_SRC,
                mapped_at_creation: false,
            });

            let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Mesh Vertex Buffer Reallocation"),
            });

            let used_size =
                (self.current_vertex_offset as usize * std::mem::size_of::<Vertex>()) as u64;
            if used_size > 0 {
                encoder.copy_buffer_to_buffer(&self.vertex_buffer, 0, &new_buffer, 0, used_size);
            }

            queue.submit(std::iter::once(encoder.finish()));
            self.vertex_buffer = new_buffer;
            self.vertex_buffer_size = new_size;
        }

        queue.write_buffer(&self.vertex_buffer, vertex_offset_bytes, vertex_data);

        let index_data = bytemuck::cast_slice(&mesh.indices);
        let index_offset_bytes =
            (self.current_index_offset as usize * std::mem::size_of::<u32>()) as u64;
        let required_index_size = index_offset_bytes + index_data.len() as u64;

        if required_index_size > self.index_buffer_size {
            let new_size = (required_index_size as f32 * BUFFER_GROWTH_FACTOR).ceil() as u64;

            let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Mesh Index Buffer (Resized)"),
                size: new_size,
                usage: wgpu::BufferUsages::INDEX
                    | wgpu::BufferUsages::COPY_DST
                    | wgpu::BufferUsages::COPY_SRC,
                mapped_at_creation: false,
            });

            let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Mesh Index Buffer Reallocation"),
            });

            let used_size =
                (self.current_index_offset as usize * std::mem::size_of::<u32>()) as u64;
            if used_size > 0 {
                encoder.copy_buffer_to_buffer(&self.index_buffer, 0, &new_buffer, 0, used_size);
            }

            queue.submit(std::iter::once(encoder.finish()));
            self.index_buffer = new_buffer;
            self.index_buffer_size = new_size;
        }

        queue.write_buffer(&self.index_buffer, index_offset_bytes, index_data);

        let (bounding_center, bounding_radius, aabb_min, aabb_max) = {
            let mut min_x = f32::MAX;
            let mut min_y = f32::MAX;
            let mut min_z = f32::MAX;
            let mut max_x = f32::MIN;
            let mut max_y = f32::MIN;
            let mut max_z = f32::MIN;

            for vertex in &mesh.vertices {
                min_x = min_x.min(vertex.position[0]);
                min_y = min_y.min(vertex.position[1]);
                min_z = min_z.min(vertex.position[2]);
                max_x = max_x.max(vertex.position[0]);
                max_y = max_y.max(vertex.position[1]);
                max_z = max_z.max(vertex.position[2]);
            }

            let center_x = (min_x + max_x) * 0.5;
            let center_y = (min_y + max_y) * 0.5;
            let center_z = (min_z + max_z) * 0.5;

            let mut max_dist_sq = 0.0f32;
            for vertex in &mesh.vertices {
                let dx = vertex.position[0] - center_x;
                let dy = vertex.position[1] - center_y;
                let dz = vertex.position[2] - center_z;
                let dist_sq = dx * dx + dy * dy + dz * dz;
                max_dist_sq = max_dist_sq.max(dist_sq);
            }

            (
                [center_x, center_y, center_z],
                max_dist_sq.sqrt(),
                [min_x, min_y, min_z],
                [max_x, max_y, max_z],
            )
        };

        let (morph_displacement_offset, morph_target_count) =
            if let Some(morph_targets) = &mesh.morph_targets {
                let target_count = morph_targets.targets.len().min(8) as u32;
                let offset = self.current_morph_displacement_offset;
                (offset, target_count)
            } else {
                (0, 0)
            };

        let mesh_data = MeshData {
            vertex_offset: self.current_vertex_offset,
            vertex_count: mesh.vertices.len() as u32,
            index_offset: self.current_index_offset,
            index_count: mesh.indices.len() as u32,
            morph_displacement_offset,
            morph_target_count,
        };

        self.meshes.insert(name.to_string(), mesh_id);
        self.mesh_data.push(mesh_data);

        if mesh_id as usize >= self.mesh_bounds_buffer_size {
            let new_size = ((mesh_id as usize + 1) as f32 * BUFFER_GROWTH_FACTOR).ceil() as usize;
            let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Mesh Bounds Buffer (Resized)"),
                size: (std::mem::size_of::<MeshBoundsData>() * new_size) as u64,
                usage: wgpu::BufferUsages::STORAGE
                    | wgpu::BufferUsages::COPY_DST
                    | wgpu::BufferUsages::COPY_SRC,
                mapped_at_creation: false,
            });

            let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Mesh Bounds Buffer Reallocation"),
            });

            let used_size = (mesh_id as usize * std::mem::size_of::<MeshBoundsData>()) as u64;
            if used_size > 0 {
                encoder.copy_buffer_to_buffer(
                    &self.mesh_bounds_buffer,
                    0,
                    &new_buffer,
                    0,
                    used_size,
                );
            }

            queue.submit(std::iter::once(encoder.finish()));
            self.mesh_bounds_buffer = new_buffer;
            self.mesh_bounds_buffer_size = new_size;
            self.culling_bind_group = None;
        }

        let bounds_data = MeshBoundsData {
            center: bounding_center,
            radius: bounding_radius,
        };
        queue.write_buffer(
            &self.mesh_bounds_buffer,
            (mesh_id as usize * std::mem::size_of::<MeshBoundsData>()) as u64,
            bytemuck::bytes_of(&bounds_data),
        );
        self.mesh_bounds_data.push(bounds_data);

        if mesh_id as usize >= self.mesh_aabbs_buffer_size {
            let new_size = ((mesh_id as usize + 1) as f32 * BUFFER_GROWTH_FACTOR).ceil() as usize;
            let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Mesh AABBs Buffer (Resized)"),
                size: (std::mem::size_of::<MeshBoundsAABB>() * new_size) as u64,
                usage: wgpu::BufferUsages::STORAGE
                    | wgpu::BufferUsages::COPY_DST
                    | wgpu::BufferUsages::COPY_SRC,
                mapped_at_creation: false,
            });

            let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Mesh AABBs Buffer Reallocation"),
            });

            let used_size = (mesh_id as usize * std::mem::size_of::<MeshBoundsAABB>()) as u64;
            if used_size > 0 {
                encoder.copy_buffer_to_buffer(
                    &self.mesh_aabbs_buffer,
                    0,
                    &new_buffer,
                    0,
                    used_size,
                );
            }

            queue.submit(std::iter::once(encoder.finish()));
            self.mesh_aabbs_buffer = new_buffer;
            self.mesh_aabbs_buffer_size = new_size;
            self.culling_bind_group = None;
        }

        let aabb_data = MeshBoundsAABB {
            min: aabb_min,
            _pad0: 0.0,
            max: aabb_max,
            _pad1: 0.0,
        };
        queue.write_buffer(
            &self.mesh_aabbs_buffer,
            (mesh_id as usize * std::mem::size_of::<MeshBoundsAABB>()) as u64,
            bytemuck::bytes_of(&aabb_data),
        );

        self.mesh_aabbs_data.push(aabb_data);

        if let Some(morph_targets) = &mesh.morph_targets {
            let vertex_count = mesh.vertices.len();
            let target_count = morph_targets.targets.len().min(8);
            let total_displacements = vertex_count * target_count;

            if total_displacements > 0 {
                let mut displacements = Vec::with_capacity(total_displacements);

                for target_index in 0..target_count {
                    let target = &morph_targets.targets[target_index];
                    for vertex_index in 0..vertex_count {
                        let position = target
                            .position_displacements
                            .get(vertex_index)
                            .copied()
                            .unwrap_or([0.0, 0.0, 0.0]);
                        let normal = target
                            .normal_displacements
                            .as_ref()
                            .and_then(|n| n.get(vertex_index))
                            .copied()
                            .unwrap_or([0.0, 0.0, 0.0]);
                        let tangent = target
                            .tangent_displacements
                            .as_ref()
                            .and_then(|t| t.get(vertex_index))
                            .copied()
                            .unwrap_or([0.0, 0.0, 0.0]);

                        displacements.push(MorphDisplacement {
                            position,
                            _pad0: 0.0,
                            normal,
                            _pad1: 0.0,
                            tangent,
                            _pad2: 0.0,
                        });
                    }
                }

                let displacement_data = bytemuck::cast_slice(&displacements);
                let displacement_offset_bytes = self.current_morph_displacement_offset as u64
                    * std::mem::size_of::<MorphDisplacement>() as u64;
                let required_size = displacement_offset_bytes + displacement_data.len() as u64;

                if required_size > self.morph_displacement_buffer_size {
                    let new_size =
                        (required_size as f64 * BUFFER_GROWTH_FACTOR as f64).ceil() as u64;
                    let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                        label: Some("Mesh Morph Displacement Buffer (Resized)"),
                        size: new_size,
                        usage: wgpu::BufferUsages::STORAGE
                            | wgpu::BufferUsages::COPY_DST
                            | wgpu::BufferUsages::COPY_SRC,
                        mapped_at_creation: false,
                    });

                    if displacement_offset_bytes > 0 {
                        let mut encoder =
                            device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                                label: Some("Morph Displacement Buffer Reallocation"),
                            });
                        encoder.copy_buffer_to_buffer(
                            &self.morph_displacement_buffer,
                            0,
                            &new_buffer,
                            0,
                            displacement_offset_bytes,
                        );
                        queue.submit(std::iter::once(encoder.finish()));
                    }

                    self.morph_displacement_buffer = new_buffer;
                    self.morph_displacement_buffer_size = new_size;
                    self.rebuild_instance_bind_group(device);
                }

                queue.write_buffer(
                    &self.morph_displacement_buffer,
                    displacement_offset_bytes,
                    displacement_data,
                );

                self.current_morph_displacement_offset += total_displacements as u32;
            }
        }

        self.current_vertex_offset += mesh.vertices.len() as u32;
        self.current_index_offset += mesh.indices.len() as u32;
    }

    pub(super) fn draw_batches<'a>(
        pass: &mut wgpu::RenderPass<'a>,
        instances: &[(u32, u32, u32, u32)],
        batch_offset: usize,
        material_bind_groups: &'a std::collections::HashMap<u32, wgpu::BindGroup>,
        indirect_buffer: &'a wgpu::Buffer,
    ) {
        let mut current_material: Option<u32> = None;
        let mut batch_start_index: usize = 0;

        for (batch_index, &(_mesh_id, material_id, _start, _end)) in instances.iter().enumerate() {
            if current_material != Some(material_id) {
                if let Some(prev_material) = current_material {
                    let count = batch_index - batch_start_index;
                    if count > 0 {
                        if let Some(bind_group) = material_bind_groups.get(&prev_material) {
                            pass.set_bind_group(2, bind_group, &[]);
                        }
                        let indirect_offset = ((batch_offset + batch_start_index)
                            * std::mem::size_of::<DrawIndexedIndirect>())
                            as u64;
                        pass.multi_draw_indexed_indirect(
                            indirect_buffer,
                            indirect_offset,
                            count as u32,
                        );
                    }
                }
                current_material = Some(material_id);
                batch_start_index = batch_index;
            }
        }

        if let Some(material_id) = current_material {
            let count = instances.len() - batch_start_index;
            if count > 0 {
                if let Some(bind_group) = material_bind_groups.get(&material_id) {
                    pass.set_bind_group(2, bind_group, &[]);
                }
                let indirect_offset = ((batch_offset + batch_start_index)
                    * std::mem::size_of::<DrawIndexedIndirect>())
                    as u64;
                pass.multi_draw_indexed_indirect(indirect_buffer, indirect_offset, count as u32);
            }
        }
    }

    pub(super) fn rebuild_instance_bind_group(&mut self, device: &wgpu::Device) {
        self.instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Mesh Instance Bind Group"),
            layout: &self.instance_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: self.transform_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: self.materials_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: self.object_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: self.lights_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 4,
                    resource: self.visible_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 5,
                    resource: self.custom_data_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 6,
                    resource: self.morph_displacement_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 7,
                    resource: self.light_grid_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 8,
                    resource: self.light_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 9,
                    resource: self.cluster_uniforms_buffer.as_entire_binding(),
                },
            ],
        });

        self.phase1_instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Phase 1 Instance Bind Group"),
            layout: &self.instance_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: self.transform_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: self.materials_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 2,
                    resource: self.object_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 3,
                    resource: self.lights_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 4,
                    resource: self.phase1_visible_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 5,
                    resource: self.custom_data_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 6,
                    resource: self.morph_displacement_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 7,
                    resource: self.light_grid_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 8,
                    resource: self.light_indices_buffer.as_entire_binding(),
                },
                wgpu::BindGroupEntry {
                    binding: 9,
                    resource: self.cluster_uniforms_buffer.as_entire_binding(),
                },
            ],
        });
    }

    pub(super) fn update_dirty_transforms(
        &mut self,
        world: &crate::ecs::world::World,
        queue: &wgpu::Queue,
    ) -> bool {
        let render_state = &world.resources.mesh_render_state;
        let dirty_count = render_state.dirty_transform_count();
        if dirty_count == 0 {
            return true;
        }

        let total = self.gpu_registry.len();
        let threshold = (total as f32 * 0.25).ceil() as usize;

        if dirty_count > threshold || total == 0 {
            return false;
        }

        let dirty_entities = render_state.take_transform_dirty();

        let mut updates: Vec<(u32, ModelMatrix)> = Vec::with_capacity(dirty_count);
        for entity in dirty_entities {
            if let Some(&slot) = self.gpu_registry.entity_to_slot.get(&entity)
                && let Some(transform) = world.get_global_transform(entity)
            {
                let model_matrix = ModelMatrix {
                    model: transform.0.into(),
                    normal_matrix: compute_normal_matrix(&transform.0),
                };

                if (slot as usize) < self.cached_transforms.len() {
                    self.cached_transforms[slot as usize] = model_matrix;
                }

                updates.push((slot, model_matrix));
            }
        }

        if updates.is_empty() {
            return true;
        }

        updates.sort_by_key(|(slot, _)| *slot);

        let element_size = std::mem::size_of::<ModelMatrix>() as u64;
        let mut range_start = updates[0].0;
        let mut range_data: Vec<ModelMatrix> = vec![updates[0].1];

        for &(slot, matrix) in updates.iter().skip(1) {
            let expected_slot = range_start + range_data.len() as u32;

            if slot == expected_slot {
                range_data.push(matrix);
            } else {
                let offset = (range_start as u64) * element_size;
                queue.write_buffer(
                    &self.transform_buffer,
                    offset,
                    bytemuck::cast_slice(&range_data),
                );

                range_start = slot;
                range_data.clear();
                range_data.push(matrix);
            }
        }

        let offset = (range_start as u64) * element_size;
        queue.write_buffer(
            &self.transform_buffer,
            offset,
            bytemuck::cast_slice(&range_data),
        );

        true
    }

    pub(super) fn can_do_incremental_update(&self, world: &crate::ecs::world::World) -> bool {
        let render_state = &world.resources.mesh_render_state;

        render_state.frame_initialized()
            && !render_state.full_rebuild_needed()
            && !render_state.batches_invalidated()
            && render_state.entities_added_is_empty()
            && render_state.entities_removed_is_empty()
            && render_state.material_dirty_is_empty()
            && render_state.visibility_dirty_is_empty()
            && !render_state.transform_dirty_is_empty()
    }

    pub(super) fn prepare_uniforms_and_lights(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        world: &crate::ecs::world::World,
    ) {
        use super::super::projection::*;

        let light_result = collect_lights(world, MAX_LIGHTS);
        let mut lights_data = light_result.lights_data;
        let directional_light = light_result.directional_light;
        let entity_to_lights_index = light_result.entity_to_index;
        self.num_directional_lights = light_result.num_directional_lights;
        self.num_total_lights = lights_data.len() as u32;

        let directional_light_direction = directional_light
            .as_ref()
            .map(|(_light, transform)| {
                let dir = transform.forward_vector();
                [dir.x, dir.y, dir.z, 0.0]
            })
            .unwrap_or([0.0, -1.0, 0.0, 0.0]);

        let cascade_result = calculate_cascade_shadows(world, directional_light.as_ref());
        let cascade_view_projections = cascade_result.cascade_view_projections;
        let cascade_diameters = cascade_result.cascade_diameters;
        let light_view_projection = cascade_result.light_view_projection;
        let shadow_bias = cascade_result.shadow_bias;
        let shadows_enabled = cascade_result.shadows_enabled;

        let cascade_texture_resolution = if cfg!(target_arch = "wasm32") {
            2048.0
        } else {
            4096.0
        };

        let cascade_atlas_offsets: [[f32; 4]; crate::render::wgpu::passes::NUM_SHADOW_CASCADES] = [
            [
                0.0,
                0.0,
                cascade_diameters[0] / cascade_texture_resolution,
                0.0,
            ],
            [
                0.5,
                0.0,
                cascade_diameters[1] / cascade_texture_resolution,
                0.0,
            ],
            [
                0.0,
                0.5,
                cascade_diameters[2] / cascade_texture_resolution,
                0.0,
            ],
            [
                0.5,
                0.5,
                cascade_diameters[3] / cascade_texture_resolution,
                0.0,
            ],
        ];

        if let Some(camera_matrices) =
            crate::ecs::camera::queries::query_active_camera_matrices(world)
        {
            let global_unlit = if world.resources.graphics.unlit_mode {
                1.0
            } else {
                0.0
            };

            let (snap_resolution, snap_enabled) =
                if let Some(ref vertex_snap) = world.resources.graphics.vertex_snap {
                    (vertex_snap.resolution, 1)
                } else {
                    ([320.0, 240.0], 0)
                };

            let affine_enabled = if world.resources.graphics.affine_texture_mapping {
                1
            } else {
                0
            };

            let (fog_color, fog_enabled, fog_start, fog_end) =
                if let Some(ref fog) = world.resources.graphics.fog {
                    (fog.color, 1, fog.start, fog.end)
                } else {
                    ([0.5, 0.5, 0.6], 0, 5.0, 30.0)
                };

            let time = world.resources.window.timing.uptime_milliseconds as f32 / 1000.0;

            let uniforms = MeshUniforms {
                view: camera_matrices.view.into(),
                projection: camera_matrices.projection.into(),
                camera_position: [
                    camera_matrices.camera_position.x,
                    camera_matrices.camera_position.y,
                    camera_matrices.camera_position.z,
                    1.0,
                ],
                num_lights: [lights_data.len() as u32, 0, 0, 0],
                ambient_light: [0.1, 0.1, 0.1, 1.0],
                light_view_projection,
                shadow_bias,
                shadows_enabled,
                global_unlit,
                shadow_normal_bias: 1.8,
                snap_resolution,
                snap_enabled,
                affine_enabled,
                fog_color,
                fog_enabled,
                fog_start,
                fog_end,
                cascade_count: crate::render::wgpu::passes::NUM_SHADOW_CASCADES as u32,
                directional_light_size: 1.0,
                cascade_view_projections,
                cascade_split_distances: CASCADE_SPLIT_DISTANCES,
                cascade_atlas_offsets,
                cascade_atlas_scale: [0.5, 0.5, 0.0, 0.0],
                time,
                pbr_debug_mode: world.resources.graphics.pbr_debug_mode.as_u32(),
                texture_debug_stripes: world.resources.graphics.texture_debug_stripes as u32,
                texture_debug_stripes_speed: world.resources.graphics.texture_debug_stripes_speed,
                directional_light_direction,
                _padding3: [0.0; 20],
            };
            queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));

            let overlay_uniforms = MeshUniforms {
                view: camera_matrices.view.into(),
                projection: camera_matrices.projection.into(),
                camera_position: [
                    camera_matrices.camera_position.x,
                    camera_matrices.camera_position.y,
                    camera_matrices.camera_position.z,
                    1.0,
                ],
                num_lights: [lights_data.len() as u32, 0, 0, 0],
                ambient_light: [0.1, 0.1, 0.1, 1.0],
                light_view_projection,
                shadow_bias,
                shadows_enabled: 0.0,
                global_unlit,
                shadow_normal_bias: 1.8,
                snap_resolution,
                snap_enabled,
                affine_enabled,
                fog_color,
                fog_enabled,
                fog_start,
                fog_end,
                cascade_count: crate::render::wgpu::passes::NUM_SHADOW_CASCADES as u32,
                directional_light_size: 1.0,
                cascade_view_projections,
                cascade_split_distances: CASCADE_SPLIT_DISTANCES,
                cascade_atlas_offsets,
                cascade_atlas_scale: [0.5, 0.5, 0.0, 0.0],
                time,
                pbr_debug_mode: world.resources.graphics.pbr_debug_mode.as_u32(),
                texture_debug_stripes: world.resources.graphics.texture_debug_stripes as u32,
                texture_debug_stripes_speed: world.resources.graphics.texture_debug_stripes_speed,
                directional_light_direction,
                _padding3: [0.0; 20],
            };
            queue.write_buffer(
                &self.overlay_uniform_buffer,
                0,
                bytemuck::cast_slice(&[overlay_uniforms]),
            );

            let view_proj = camera_matrices.projection * camera_matrices.view;
            let frustum_planes = extract_frustum_planes(&view_proj);

            let (screen_width, screen_height) = self.hiz_pass.screen_size();

            let occlusion_enabled =
                screen_width > 0 && world.resources.graphics.occlusion_culling_enabled;

            let culling_uniforms = CullingUniforms {
                frustum_planes: frustum_planes.map(|v| [v.x, v.y, v.z, v.w]),
                view_projection: view_proj.into(),
                screen_size: [screen_width as f32, screen_height as f32],
                hiz_mip_count: self.hiz_pass.mip_count() as f32,
                occlusion_enabled: if occlusion_enabled { 1 } else { 0 },
                object_count: self.object_count,
                _padding: [0; 3],
            };
            queue.write_buffer(
                &self.culling_uniform_buffer,
                0,
                bytemuck::cast_slice(&[culling_uniforms]),
            );

            let (z_near, z_far) = world
                .resources
                .active_camera
                .and_then(|entity| world.get_camera(entity))
                .map(|camera| match &camera.projection {
                    crate::ecs::camera::components::Projection::Perspective(persp) => {
                        (persp.z_near, persp.z_far.unwrap_or(1000.0))
                    }
                    crate::ecs::camera::components::Projection::Orthographic(ortho) => {
                        (ortho.z_near, ortho.z_far)
                    }
                })
                .unwrap_or((0.1, 1000.0));

            let tile_size_x = (screen_width as f32) / (CLUSTER_GRID_X as f32);
            let tile_size_y = (screen_height as f32) / (CLUSTER_GRID_Y as f32);

            let inverse_projection: [[f32; 4]; 4] =
                nalgebra_glm::inverse(&camera_matrices.projection).into();

            let cluster_uniforms = ClusterUniforms {
                inverse_projection,
                screen_size: [screen_width as f32, screen_height as f32],
                z_near,
                z_far,
                cluster_count: [CLUSTER_GRID_X, CLUSTER_GRID_Y, CLUSTER_GRID_Z, 0],
                tile_size: [tile_size_x, tile_size_y],
                num_lights: lights_data.len() as u32,
                num_directional_lights: self.num_directional_lights,
            };
            queue.write_buffer(
                &self.cluster_uniforms_buffer,
                0,
                bytemuck::cast_slice(&[cluster_uniforms]),
            );

            let view_matrix: [[f32; 4]; 4] = camera_matrices.view.into();
            queue.write_buffer(
                &self.view_matrix_buffer,
                0,
                bytemuck::cast_slice(&[view_matrix]),
            );

            let view_proj_hash = {
                use std::hash::{Hash, Hasher};
                let mut hasher = std::collections::hash_map::DefaultHasher::new();
                for row in &inverse_projection {
                    for val in row {
                        val.to_bits().hash(&mut hasher);
                    }
                }
                screen_width.hash(&mut hasher);
                screen_height.hash(&mut hasher);
                hasher.finish()
            };

            self.camera_changed = view_proj_hash != self.last_camera_hash;
            if self.camera_changed {
                self.last_camera_hash = view_proj_hash;
            }

            self.cluster_assign_bind_group =
                Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
                    label: Some("Cluster Light Assign Bind Group"),
                    layout: &self.cluster_assign_bind_group_layout,
                    entries: &[
                        wgpu::BindGroupEntry {
                            binding: 0,
                            resource: self.cluster_uniforms_buffer.as_entire_binding(),
                        },
                        wgpu::BindGroupEntry {
                            binding: 1,
                            resource: self.cluster_bounds_buffer.as_entire_binding(),
                        },
                        wgpu::BindGroupEntry {
                            binding: 2,
                            resource: self.light_grid_buffer.as_entire_binding(),
                        },
                        wgpu::BindGroupEntry {
                            binding: 3,
                            resource: self.light_indices_buffer.as_entire_binding(),
                        },
                        wgpu::BindGroupEntry {
                            binding: 4,
                            resource: self.lights_buffer.as_entire_binding(),
                        },
                        wgpu::BindGroupEntry {
                            binding: 5,
                            resource: self.view_matrix_buffer.as_entire_binding(),
                        },
                    ],
                }));
        }

        let camera_position = world
            .resources
            .active_camera
            .and_then(|cam| world.get_global_transform(cam))
            .map(|t| nalgebra_glm::vec3(t.0[(0, 3)], t.0[(1, 3)], t.0[(2, 3)]))
            .unwrap_or_else(|| nalgebra_glm::vec3(0.0, 0.0, 0.0));

        let spotlight_result = collect_spotlight_shadows(world, camera_position);
        apply_spotlight_shadow_indices(
            &mut lights_data,
            &spotlight_result.entity_to_shadow_index,
            &entity_to_lights_index,
        );

        if !spotlight_result.shadow_data.is_empty() {
            queue.write_buffer(
                &self.spotlight_shadow_buffer,
                0,
                bytemuck::cast_slice(&spotlight_result.shadow_data),
            );
        }

        let point_shadow_result = collect_point_light_shadows(
            world,
            camera_position,
            &mut lights_data,
            &entity_to_lights_index,
        );
        if !point_shadow_result.is_empty() {
            queue.write_buffer(
                &self.point_shadow_buffer,
                0,
                bytemuck::cast_slice(&point_shadow_result),
            );
        }

        if !lights_data.is_empty() {
            if lights_data.len() > self.lights_buffer_size {
                let new_size = (lights_data.len() as f32 * BUFFER_GROWTH_FACTOR).ceil() as usize;
                let new_size = new_size.min(MAX_LIGHTS);

                let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                    label: Some("Mesh Lights Buffer (Resized)"),
                    size: (std::mem::size_of::<LightData>() * new_size) as u64,
                    usage: wgpu::BufferUsages::STORAGE
                        | wgpu::BufferUsages::COPY_DST
                        | wgpu::BufferUsages::COPY_SRC,
                    mapped_at_creation: false,
                });

                self.lights_buffer = new_buffer;
                self.lights_buffer_size = new_size;
                self.rebuild_instance_bind_group(device);
            }

            queue.write_buffer(&self.lights_buffer, 0, bytemuck::cast_slice(&lights_data));
        }
    }

    pub(super) fn populate_gpu_registry_from_instances(&mut self) {
        self.gpu_registry.clear();

        for (slot, entity) in self.cached_entities.iter().enumerate() {
            let slot = slot as u32;
            self.gpu_registry.entity_to_slot.insert(*entity, slot);
            if self.gpu_registry.slot_to_entity.len() <= slot as usize {
                self.gpu_registry
                    .slot_to_entity
                    .resize(slot as usize + 1, None);
            }
            self.gpu_registry.slot_to_entity[slot as usize] = Some(*entity);
            self.gpu_registry.slot_count = self.gpu_registry.slot_count.max(slot + 1);
        }
    }

    pub(super) fn rebuild_transform_compute_bind_group(&mut self, device: &wgpu::Device) {
        self.transform_compute_bind_group =
            Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Transform Compute Bind Group"),
                layout: &self.transform_compute_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: self.local_transform_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: self.transform_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: self.transform_compute_uniforms_buffer.as_entire_binding(),
                    },
                ],
            }));
    }

    pub(super) fn dispatch_transform_compute(
        &self,
        encoder: &mut wgpu::CommandEncoder,
        queue: &wgpu::Queue,
        entity_count: u32,
    ) {
        if entity_count == 0 || self.max_hierarchy_depth == 0 {
            return;
        }

        if let Some(ref bind_group) = self.transform_compute_bind_group {
            let dispatch_count = entity_count.div_ceil(256);
            let uniform_size = std::mem::size_of::<super::types::TransformComputeUniforms>();

            let mut staging_data =
                Vec::with_capacity(uniform_size * (self.max_hierarchy_depth as usize + 1));
            for depth in 0..=self.max_hierarchy_depth {
                let uniforms = super::types::TransformComputeUniforms {
                    entity_count,
                    current_depth: depth,
                    _pad: [0; 2],
                };
                staging_data.extend_from_slice(bytemuck::cast_slice(&[uniforms]));
            }
            queue.write_buffer(&self.transform_compute_staging_buffer, 0, &staging_data);

            for depth in 0..=self.max_hierarchy_depth {
                let staging_offset = (depth as usize * uniform_size) as u64;

                encoder.copy_buffer_to_buffer(
                    &self.transform_compute_staging_buffer,
                    staging_offset,
                    &self.transform_compute_uniforms_buffer,
                    0,
                    uniform_size as u64,
                );

                let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
                    label: Some("Transform Compute Pass"),
                    timestamp_writes: None,
                });

                compute_pass.set_pipeline(&self.transform_compute_pipeline);
                compute_pass.set_bind_group(0, bind_group, &[]);
                compute_pass.dispatch_workgroups(dispatch_count, 1, 1);
            }
        }
    }

    pub(super) fn upload_local_transforms(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        local_transforms: &[super::types::GpuLocalTransform],
    ) {
        if local_transforms.is_empty() {
            return;
        }

        if local_transforms.len() > self.local_transform_buffer_size {
            let new_size = std::cmp::min(
                (local_transforms.len() as f32 * super::types::BUFFER_GROWTH_FACTOR).ceil()
                    as usize,
                super::types::MAX_INSTANCES,
            );

            self.local_transform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Local Transform Buffer (Resized)"),
                size: (std::mem::size_of::<super::types::GpuLocalTransform>() * new_size) as u64,
                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });
            self.local_transform_buffer_size = new_size;
            self.rebuild_transform_compute_bind_group(device);
        }

        queue.write_buffer(
            &self.local_transform_buffer,
            0,
            bytemuck::cast_slice(local_transforms),
        );
    }
}
