use crate::ecs::mesh::components::{
    Mesh, Vertex, create_cone_mesh, create_cube_mesh, create_cylinder_mesh, create_plane_mesh,
    create_sphere_mesh, create_torus_mesh,
};
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use std::collections::HashMap;
use wgpu::util::DeviceExt;

const INITIAL_INSTANCES: usize = 1_000;
const MAX_INSTANCES: usize = 1_000_000;
const INITIAL_VERTEX_BUFFER_SIZE: usize = 10_000;
const INITIAL_INDEX_BUFFER_SIZE: usize = 30_000;
const INITIAL_MATERIALS: usize = 100;
const INITIAL_LIGHTS: usize = 32;
const MAX_LIGHTS: usize = 128;
const BUFFER_GROWTH_FACTOR: f32 = 2.0;
const BUFFER_SHRINK_THRESHOLD: f32 = 0.25;
const BUFFER_COMPACTION_INTERVAL_FRAMES: u64 = 600;

#[repr(C, align(256))]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct MeshUniforms {
    view: [[f32; 4]; 4],
    projection: [[f32; 4]; 4],
    camera_position: [f32; 4],
    num_lights: [u32; 4],
    ambient_light: [f32; 4],
    light_view_projection: [[f32; 4]; 4],
    shadow_bias: f32,
    shadows_enabled: f32,
    _padding: [f32; 2],
    _padding2: [[f32; 4]; 16],
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct ModelMatrix {
    model: [[f32; 4]; 4],
    normal_matrix: [[f32; 4]; 3],
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct ObjectData {
    transform_index: u32,
    mesh_id: u32,
    material_id: u32,
    batch_id: u32,
}

#[repr(C, align(16))]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct CullingUniforms {
    frustum_planes: [[f32; 4]; 6],
    view_projection: [[f32; 4]; 4],
    hiz_size: [f32; 2],
    hiz_mip_count: u32,
    object_count: u32,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct MaterialData {
    pub base_color: [f32; 4],
    pub emissive_factor: [f32; 3],
    pub alpha_mode: u32,
    pub alpha_cutoff: f32,
    pub has_base_texture: u32,
    pub has_emissive_texture: u32,
    pub roughness: f32,
    pub metallic: f32,
    pub unlit: u32,
    pub _padding: [f32; 2],
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct LightData {
    position: [f32; 4],
    direction: [f32; 4],
    color: [f32; 4],
    light_type: u32,
    range: f32,
    inner_cone: f32,
    outer_cone: f32,
}

#[derive(Debug, Clone)]
struct MeshData {
    vertex_offset: u32,
    vertex_count: u32,
    index_offset: u32,
    index_count: u32,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct MeshBoundsData {
    center: [f32; 3],
    radius: f32,
}

#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct DrawIndexedIndirect {
    index_count: u32,
    instance_count: u32,
    first_index: u32,
    base_vertex: i32,
    first_instance: u32,
}

fn compute_normal_matrix(model_matrix: &nalgebra_glm::Mat4) -> [[f32; 4]; 3] {
    let mat3 = nalgebra_glm::mat4_to_mat3(model_matrix);

    let scale_x_sq =
        mat3[(0, 0)] * mat3[(0, 0)] + mat3[(1, 0)] * mat3[(1, 0)] + mat3[(2, 0)] * mat3[(2, 0)];
    let scale_y_sq =
        mat3[(0, 1)] * mat3[(0, 1)] + mat3[(1, 1)] * mat3[(1, 1)] + mat3[(2, 1)] * mat3[(2, 1)];
    let scale_z_sq =
        mat3[(0, 2)] * mat3[(0, 2)] + mat3[(1, 2)] * mat3[(1, 2)] + mat3[(2, 2)] * mat3[(2, 2)];

    let is_uniform_scale =
        (scale_x_sq - scale_y_sq).abs() < 0.001 && (scale_y_sq - scale_z_sq).abs() < 0.001;

    let normal_mat3 = if is_uniform_scale {
        let inv_scale = 1.0 / scale_x_sq.sqrt();
        nalgebra_glm::Mat3::new(
            mat3[(0, 0)] * inv_scale,
            mat3[(0, 1)] * inv_scale,
            mat3[(0, 2)] * inv_scale,
            mat3[(1, 0)] * inv_scale,
            mat3[(1, 1)] * inv_scale,
            mat3[(1, 2)] * inv_scale,
            mat3[(2, 0)] * inv_scale,
            mat3[(2, 1)] * inv_scale,
            mat3[(2, 2)] * inv_scale,
        )
    } else {
        match mat3.try_inverse() {
            Some(inv) => inv.transpose(),
            None => nalgebra_glm::Mat3::identity(),
        }
    };

    [
        [
            normal_mat3[(0, 0)],
            normal_mat3[(1, 0)],
            normal_mat3[(2, 0)],
            0.0,
        ],
        [
            normal_mat3[(0, 1)],
            normal_mat3[(1, 1)],
            normal_mat3[(2, 1)],
            0.0,
        ],
        [
            normal_mat3[(0, 2)],
            normal_mat3[(1, 2)],
            normal_mat3[(2, 2)],
            0.0,
        ],
    ]
}

pub struct MeshPass {
    opaque_pipeline: wgpu::RenderPipeline,
    overlay_opaque_pipeline: wgpu::RenderPipeline,
    oit_pipeline: wgpu::RenderPipeline,
    oit_composite_pipeline: wgpu::RenderPipeline,

    oit_accum_view: wgpu::TextureView,
    oit_reveal_view: wgpu::TextureView,
    oit_composite_bind_group: wgpu::BindGroup,
    oit_composite_bind_group_layout: wgpu::BindGroupLayout,
    oit_sampler: wgpu::Sampler,

    overlay_depth_view: wgpu::TextureView,

    uniform_buffer: wgpu::Buffer,
    uniform_bind_group: wgpu::BindGroup,
    overlay_uniform_buffer: wgpu::Buffer,
    overlay_uniform_bind_group: wgpu::BindGroup,

    transform_buffer: wgpu::Buffer,
    materials_buffer: wgpu::Buffer,
    lights_buffer: wgpu::Buffer,
    object_buffer: wgpu::Buffer,
    instance_bind_group: wgpu::BindGroup,
    instance_bind_group_layout: wgpu::BindGroupLayout,

    texture_bind_group_layout: wgpu::BindGroupLayout,
    shadow_bind_group: wgpu::BindGroup,
    shadow_bind_group_layout: wgpu::BindGroupLayout,
    shadow_sampler: wgpu::Sampler,

    dummy_white_view: wgpu::TextureView,
    dummy_black_view: wgpu::TextureView,
    dummy_sampler: wgpu::Sampler,

    transform_buffer_size: usize,
    materials_buffer_size: usize,
    lights_buffer_size: usize,
    object_buffer_size: usize,

    vertex_buffer: wgpu::Buffer,
    index_buffer: wgpu::Buffer,
    vertex_buffer_size: u64,
    index_buffer_size: u64,

    meshes: HashMap<String, u32>,
    mesh_data: Vec<MeshData>,

    opaque_instances: Vec<(u32, u32, u32, u32)>,
    transparent_instances: Vec<(u32, u32, u32, u32)>,
    overlay_opaque_instances: Vec<(u32, u32, u32, u32)>,
    overlay_transparent_instances: Vec<(u32, u32, u32, u32)>,
    regular_object_count: u32,
    material_bind_groups: HashMap<u32, wgpu::BindGroup>,
    material_bind_group_cache_key: HashMap<u32, (Option<String>, Option<String>)>,

    registered_textures: HashMap<String, (wgpu::TextureView, wgpu::Sampler)>,

    current_vertex_offset: u32,
    current_index_offset: u32,

    compaction_frame_counter: u64,

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

    mesh_bounds_buffer: wgpu::Buffer,
    mesh_bounds_buffer_size: usize,
    indirect_buffer: wgpu::Buffer,
    indirect_buffer_size: usize,
    indirect_reset_buffer: wgpu::Buffer,
    indirect_reset_count: usize,
    visible_indices_buffer: wgpu::Buffer,
    visibility_buffer: wgpu::Buffer,

    hiz_texture: wgpu::Texture,
    hiz_views: Vec<wgpu::TextureView>,
    hiz_sampler: wgpu::Sampler,
    hiz_generate_pipeline: wgpu::ComputePipeline,
    hiz_bind_group_layout: wgpu::BindGroupLayout,

    depth_copy_bind_group_layout: wgpu::BindGroupLayout,
    depth_copy_pipeline: wgpu::ComputePipeline,
}

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 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 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::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,
                    },
                ],
            });

        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 visibility_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Visibility 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(),
                },
            ],
        });

        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,
                    },
                ],
            });

        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::Rgba8UnormSrgb,
                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::Rgba8UnormSrgb,
                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_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::Linear,
            min_filter: wgpu::FilterMode::Linear,
            mipmap_filter: wgpu::FilterMode::Nearest,
            compare: Some(wgpu::CompareFunction::GreaterEqual),
            ..Default::default()
        });

        let shadow_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Shadow 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::Comparison),
                        count: None,
                    },
                ],
            });

        let shadow_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("Shadow Bind Group"),
            layout: &shadow_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),
                },
            ],
        });

        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,
                &shadow_bind_group_layout,
            ],
            push_constant_ranges: &[],
        });

        let vertex_attributes = [
            wgpu::VertexAttribute {
                offset: 0,
                shader_location: 0,
                format: wgpu::VertexFormat::Float32x3,
            },
            wgpu::VertexAttribute {
                offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
                shader_location: 1,
                format: wgpu::VertexFormat::Float32x3,
            },
            wgpu::VertexAttribute {
                offset: (std::mem::size_of::<[f32; 3]>() * 2) as wgpu::BufferAddress,
                shader_location: 2,
                format: wgpu::VertexFormat::Float32x2,
            },
        ];

        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_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: &[Some(wgpu::ColorTargetState {
                    format: color_format,
                    blend: Some(wgpu::BlendState::REPLACE),
                    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: 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 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: &[Some(wgpu::ColorTargetState {
                        format: color_format,
                        blend: Some(wgpu::BlendState::REPLACE),
                        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: 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::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 5,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 6,
                        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: 7,
                        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: 8,
                        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 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,
            mapped_at_creation: false,
        });

        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 hiz_mip_count = ((window_size.0.max(window_size.1) as f32).log2().ceil() as u32).max(1);
        let hiz_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Hi-Z Texture"),
            size: wgpu::Extent3d {
                width: window_size.0.max(1),
                height: window_size.1.max(1),
                depth_or_array_layers: 1,
            },
            mip_level_count: hiz_mip_count,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::R32Float,
            usage: wgpu::TextureUsages::TEXTURE_BINDING
                | wgpu::TextureUsages::STORAGE_BINDING
                | wgpu::TextureUsages::COPY_DST,
            view_formats: &[],
        });

        let mut hiz_views = Vec::with_capacity(hiz_mip_count as usize);
        for mip in 0..hiz_mip_count {
            hiz_views.push(hiz_texture.create_view(&wgpu::TextureViewDescriptor {
                label: Some(&format!("Hi-Z View Mip {}", mip)),
                base_mip_level: mip,
                mip_level_count: Some(1),
                ..Default::default()
            }));
        }

        let hiz_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Hi-Z 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 hiz_generate_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("Hi-Z Generate Shader"),
            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
                "../../shaders/hiz_generate.wgsl"
            ))),
        });

        let hiz_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Hi-Z Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        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: 1,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::StorageTexture {
                            access: wgpu::StorageTextureAccess::WriteOnly,
                            format: wgpu::TextureFormat::R32Float,
                            view_dimension: wgpu::TextureViewDimension::D2,
                        },
                        count: None,
                    },
                ],
            });

        let hiz_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("Hi-Z Pipeline Layout"),
            bind_group_layouts: &[&hiz_bind_group_layout],
            push_constant_ranges: &[],
        });

        let hiz_generate_pipeline =
            device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
                label: Some("Hi-Z Generate Pipeline"),
                layout: Some(&hiz_pipeline_layout),
                module: &hiz_generate_shader,
                entry_point: Some("main"),
                compilation_options: Default::default(),
                cache: None,
            });

        let depth_copy_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("Depth Copy Shader"),
            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
                "../../shaders/depth_copy.wgsl"
            ))),
        });

        let depth_copy_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Depth Copy Bind Group Layout"),
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        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::COMPUTE,
                        ty: wgpu::BindingType::StorageTexture {
                            access: wgpu::StorageTextureAccess::WriteOnly,
                            format: wgpu::TextureFormat::R32Float,
                            view_dimension: wgpu::TextureViewDimension::D2,
                        },
                        count: None,
                    },
                ],
            });

        let depth_copy_pipeline_layout =
            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Depth Copy Pipeline Layout"),
                bind_group_layouts: &[&depth_copy_bind_group_layout],
                push_constant_ranges: &[],
            });

        let depth_copy_pipeline =
            device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
                label: Some("Depth Copy Pipeline"),
                layout: Some(&depth_copy_pipeline_layout),
                module: &depth_copy_shader,
                entry_point: Some("main"),
                compilation_options: Default::default(),
                cache: None,
            });

        let mut pass = Self {
            opaque_pipeline,
            overlay_opaque_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,
            instance_bind_group,
            instance_bind_group_layout,
            texture_bind_group_layout,
            shadow_bind_group,
            shadow_bind_group_layout,
            shadow_sampler,
            dummy_white_view,
            dummy_black_view,
            dummy_sampler,
            transform_buffer_size,
            materials_buffer_size,
            lights_buffer_size,
            object_buffer_size,
            vertex_buffer,
            index_buffer,
            vertex_buffer_size,
            index_buffer_size,
            meshes: HashMap::new(),
            mesh_data: Vec::new(),
            opaque_instances: Vec::new(),
            transparent_instances: Vec::new(),
            overlay_opaque_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(),
            current_vertex_offset: 0,
            current_index_offset: 0,
            compaction_frame_counter: 0,
            culling_pipeline,
            culling_bind_group_layout,
            culling_bind_group: None,
            culling_uniform_buffer,
            object_count: 0,

            mesh_bounds_buffer,
            mesh_bounds_buffer_size,
            indirect_buffer,
            indirect_buffer_size,
            indirect_reset_buffer,
            indirect_reset_count: 0,
            visible_indices_buffer,
            visibility_buffer,

            hiz_texture,
            hiz_views,
            hiz_sampler,
            hiz_generate_pipeline,
            hiz_bind_group_layout,

            depth_copy_bind_group_layout,
            depth_copy_pipeline,
        };

        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,
            "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
    }

    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),
                },
            ],
        });

        let hiz_mip_count = ((width.max(height) as f32).log2().ceil() as u32).max(1);
        self.hiz_texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Hi-Z Texture"),
            size: wgpu::Extent3d {
                width: width.max(1),
                height: height.max(1),
                depth_or_array_layers: 1,
            },
            mip_level_count: hiz_mip_count,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::R32Float,
            usage: wgpu::TextureUsages::TEXTURE_BINDING
                | wgpu::TextureUsages::STORAGE_BINDING
                | wgpu::TextureUsages::COPY_DST,
            view_formats: &[],
        });

        self.hiz_views.clear();
        for mip in 0..hiz_mip_count {
            self.hiz_views
                .push(self.hiz_texture.create_view(&wgpu::TextureViewDescriptor {
                    label: Some(&format!("Hi-Z View Mip {}", mip)),
                    base_mip_level: mip,
                    mip_level_count: Some(1),
                    ..Default::default()
                }));
        }

        self.culling_bind_group = None;
    }

    pub fn register_texture(
        &mut self,
        name: String,
        view: wgpu::TextureView,
        sampler: wgpu::Sampler,
    ) {
        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.contains(name));
    }

    pub fn sync_meshes_from_cache(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        mesh_cache: &crate::ecs::prefab::resources::MeshCache,
    ) {
        let mut meshes_to_add = Vec::new();

        for (name, mesh) in mesh_cache.iter() {
            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);
        }
    }

    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.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,
                "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() {
                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;
            }
        }
    }

    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) = {
            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())
        };

        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,
        };

        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;
            self.mesh_bounds_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,
                mapped_at_creation: false,
            });
            self.mesh_bounds_buffer_size = new_size;
        }

        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.current_vertex_offset += mesh.vertices.len() as u32;
        self.current_index_offset += mesh.indices.len() as u32;
    }
}

impl PassNode<crate::ecs::world::World> for MeshPass {
    fn name(&self) -> &str {
        "mesh_pass"
    }

    fn reads(&self) -> Vec<&str> {
        vec!["shadow_depth"]
    }

    fn writes(&self) -> Vec<&str> {
        vec![]
    }

    fn reads_writes(&self) -> Vec<&str> {
        vec!["color", "depth"]
    }

    fn prepare(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        world: &crate::ecs::world::World,
    ) {
        self.sync_meshes_from_cache(device, queue, &world.resources.mesh_cache);
        self.sync_textures(&world.resources.texture_cache);

        self.compaction_frame_counter += 1;
        if self.compaction_frame_counter >= BUFFER_COMPACTION_INTERVAL_FRAMES {
            self.compaction_frame_counter = 0;
            self.check_and_compact_buffers(device, queue, &world.resources.mesh_cache);
        }

        let mut lights_data = Vec::new();

        for entity in
            world.query_entities(crate::ecs::world::LIGHT | crate::ecs::world::GLOBAL_TRANSFORM)
        {
            if let (Some(light), Some(transform)) =
                (world.get_light(entity), world.get_global_transform(entity))
            {
                let light_type = match light.light_type {
                    crate::ecs::light::components::LightType::Directional => 0,
                    crate::ecs::light::components::LightType::Point => 1,
                    crate::ecs::light::components::LightType::Spot => 2,
                };

                let position = transform.translation();
                let direction = transform.forward_vector();

                lights_data.push(LightData {
                    position: [position.x, position.y, position.z, 1.0],
                    direction: [direction.x, direction.y, direction.z, 0.0],
                    color: [
                        light.color.x * light.intensity,
                        light.color.y * light.intensity,
                        light.color.z * light.intensity,
                        1.0,
                    ],
                    light_type,
                    range: light.range,
                    inner_cone: light.inner_cone_angle.cos(),
                    outer_cone: light.outer_cone_angle.cos(),
                });

                if lights_data.len() >= MAX_LIGHTS {
                    break;
                }
            }
        }

        let directional_light = world
            .query_entities(crate::ecs::world::LIGHT | crate::ecs::world::GLOBAL_TRANSFORM)
            .find_map(|entity| {
                if let (Some(light), Some(transform)) =
                    (world.get_light(entity), world.get_global_transform(entity))
                {
                    if matches!(
                        light.light_type,
                        crate::ecs::light::components::LightType::Directional
                    ) && light.cast_shadows
                    {
                        Some((light.clone(), *transform))
                    } else {
                        None
                    }
                } else {
                    None
                }
            });

        let (light_view_projection, shadow_bias, shadows_enabled) =
            if let Some((light, light_transform)) = directional_light {
                let light_direction = light_transform.forward_vector();
                let scene_center = nalgebra_glm::vec3(0.0, 0.0, 0.0);
                let light_distance = 100.0;
                let light_position = scene_center - light_direction * light_distance;

                let up = if light_direction.y.abs() > 0.99 {
                    nalgebra_glm::vec3(1.0, 0.0, 0.0)
                } else {
                    nalgebra_glm::vec3(0.0, 1.0, 0.0)
                };

                let light_view = nalgebra_glm::look_at(&light_position, &scene_center, &up);

                let light_projection = reverse_z_ortho_light(-50.0, 50.0, -50.0, 50.0, 0.1, 200.0);

                let light_view_projection = light_projection * light_view;

                (light_view_projection.into(), light.shadow_bias, 1.0)
            } else {
                (nalgebra_glm::Mat4::identity().into(), 0.007, 0.0)
            };

        if let Some(camera_matrices) =
            crate::ecs::camera::queries::query_active_camera_matrices(world)
        {
            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,
                _padding: [0.0; 2],
                _padding2: [[0.0; 4]; 16],
            };
            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,
                _padding: [0.0; 2],
                _padding2: [[0.0; 4]; 16],
            };
            queue.write_buffer(
                &self.overlay_uniform_buffer,
                0,
                bytemuck::cast_slice(&[overlay_uniforms]),
            );
        }

        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.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(),
                        },
                    ],
                });
            }

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

        let mesh_entities: Vec<_> = world
            .query_entities(crate::ecs::world::RENDER_MESH)
            .collect();

        let instanced_mesh_entities_for_materials: Vec<_> = world
            .query_entities(crate::ecs::world::INSTANCED_MESH)
            .collect();

        let mut material_map: HashMap<crate::ecs::world::Entity, u32> = HashMap::new();
        let mut material_texture_map: HashMap<u32, (Option<String>, Option<String>)> =
            HashMap::new();
        let mut cache_id_to_material_id: HashMap<String, u32> = HashMap::new();
        let mut materials_data = vec![MaterialData {
            base_color: [0.7, 0.7, 0.7, 1.0],
            emissive_factor: [0.0, 0.0, 0.0],
            alpha_mode: 0,
            alpha_cutoff: 0.5,
            has_base_texture: 0,
            has_emissive_texture: 0,
            roughness: 0.5,
            metallic: 0.0,
            unlit: 0,
            _padding: [0.0, 0.0],
        }];

        for &entity in &mesh_entities {
            if let Some(material) = world.get_material(entity) {
                let material_id = if let Some(cache_id) = &material.cache_id {
                    if let Some(&existing_id) = cache_id_to_material_id.get(cache_id) {
                        existing_id
                    } else {
                        let new_id = materials_data.len() as u32;
                        let alpha_mode = match material.alpha_mode {
                            crate::ecs::material::components::AlphaMode::Opaque => 0,
                            crate::ecs::material::components::AlphaMode::Mask => 1,
                            crate::ecs::material::components::AlphaMode::Blend => 2,
                        };
                        materials_data.push(MaterialData {
                            base_color: material.base_color,
                            emissive_factor: material.emissive_factor,
                            alpha_mode,
                            alpha_cutoff: material.alpha_cutoff,
                            has_base_texture: material.base_texture.is_some() as u32,
                            has_emissive_texture: material.emissive_texture.is_some() as u32,
                            roughness: material.roughness,
                            metallic: material.metallic,
                            unlit: material.unlit as u32,
                            _padding: [0.0, 0.0],
                        });
                        material_texture_map.insert(
                            new_id,
                            (
                                material.base_texture.clone(),
                                material.emissive_texture.clone(),
                            ),
                        );
                        cache_id_to_material_id.insert(cache_id.clone(), new_id);
                        new_id
                    }
                } else {
                    let new_id = materials_data.len() as u32;
                    let alpha_mode = match material.alpha_mode {
                        crate::ecs::material::components::AlphaMode::Opaque => 0,
                        crate::ecs::material::components::AlphaMode::Mask => 1,
                        crate::ecs::material::components::AlphaMode::Blend => 2,
                    };
                    materials_data.push(MaterialData {
                        base_color: material.base_color,
                        emissive_factor: material.emissive_factor,
                        alpha_mode,
                        alpha_cutoff: material.alpha_cutoff,
                        has_base_texture: material.base_texture.is_some() as u32,
                        has_emissive_texture: material.emissive_texture.is_some() as u32,
                        roughness: material.roughness,
                        metallic: material.metallic,
                        unlit: material.unlit as u32,
                        _padding: [0.0, 0.0],
                    });
                    material_texture_map.insert(
                        new_id,
                        (
                            material.base_texture.clone(),
                            material.emissive_texture.clone(),
                        ),
                    );
                    new_id
                };
                material_map.insert(entity, material_id);
            }
        }

        for &entity in &instanced_mesh_entities_for_materials {
            if let Some(material) = world.get_material(entity) {
                let material_id = if let Some(cache_id) = &material.cache_id {
                    if let Some(&existing_id) = cache_id_to_material_id.get(cache_id) {
                        existing_id
                    } else {
                        let new_id = materials_data.len() as u32;
                        let alpha_mode = match material.alpha_mode {
                            crate::ecs::material::components::AlphaMode::Opaque => 0,
                            crate::ecs::material::components::AlphaMode::Mask => 1,
                            crate::ecs::material::components::AlphaMode::Blend => 2,
                        };
                        materials_data.push(MaterialData {
                            base_color: material.base_color,
                            emissive_factor: material.emissive_factor,
                            alpha_mode,
                            alpha_cutoff: material.alpha_cutoff,
                            has_base_texture: material.base_texture.is_some() as u32,
                            has_emissive_texture: material.emissive_texture.is_some() as u32,
                            roughness: material.roughness,
                            metallic: material.metallic,
                            unlit: material.unlit as u32,
                            _padding: [0.0, 0.0],
                        });
                        material_texture_map.insert(
                            new_id,
                            (
                                material.base_texture.clone(),
                                material.emissive_texture.clone(),
                            ),
                        );
                        cache_id_to_material_id.insert(cache_id.clone(), new_id);
                        new_id
                    }
                } else {
                    let new_id = materials_data.len() as u32;
                    let alpha_mode = match material.alpha_mode {
                        crate::ecs::material::components::AlphaMode::Opaque => 0,
                        crate::ecs::material::components::AlphaMode::Mask => 1,
                        crate::ecs::material::components::AlphaMode::Blend => 2,
                    };
                    materials_data.push(MaterialData {
                        base_color: material.base_color,
                        emissive_factor: material.emissive_factor,
                        alpha_mode,
                        alpha_cutoff: material.alpha_cutoff,
                        has_base_texture: material.base_texture.is_some() as u32,
                        has_emissive_texture: material.emissive_texture.is_some() as u32,
                        roughness: material.roughness,
                        metallic: material.metallic,
                        unlit: material.unlit as u32,
                        _padding: [0.0, 0.0],
                    });
                    material_texture_map.insert(
                        new_id,
                        (
                            material.base_texture.clone(),
                            material.emissive_texture.clone(),
                        ),
                    );
                    new_id
                };
                material_map.insert(entity, material_id);
            }
        }

        if materials_data.len() > self.materials_buffer_size {
            let new_size = (materials_data.len() as f32 * BUFFER_GROWTH_FACTOR).ceil() as usize;

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

            self.materials_buffer = new_buffer;
            self.materials_buffer_size = new_size;

            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(),
                    },
                ],
            });
        }

        queue.write_buffer(
            &self.materials_buffer,
            0,
            bytemuck::cast_slice(&materials_data),
        );

        let mut world_opaque_entities_by_mesh: HashMap<String, Vec<crate::ecs::world::Entity>> =
            HashMap::new();
        let mut world_transparent_entities_by_mesh: HashMap<
            String,
            Vec<crate::ecs::world::Entity>,
        > = HashMap::new();
        let mut overlay_opaque_entities_by_mesh: HashMap<String, Vec<crate::ecs::world::Entity>> =
            HashMap::new();
        let mut overlay_transparent_entities_by_mesh: HashMap<
            String,
            Vec<crate::ecs::world::Entity>,
        > = HashMap::new();

        for &entity in &mesh_entities {
            if let Some(visibility) = world.get_visibility(entity)
                && !visibility.visible
            {
                continue;
            }

            let render_layer = world
                .get_render_layer(entity)
                .map(|layer| layer.0)
                .unwrap_or(crate::ecs::render_layer::components::RenderLayer::WORLD);

            let should_render = match render_layer {
                crate::ecs::render_layer::components::RenderLayer::WORLD => {
                    world.resources.graphics.render_layer_world_enabled
                }
                crate::ecs::render_layer::components::RenderLayer::OVERLAY => {
                    world.resources.graphics.render_layer_overlay_enabled
                }
                _ => true,
            };

            if !should_render {
                continue;
            }

            if let Some(mesh) = world.get_render_mesh(entity)
                && world.get_global_transform(entity).is_some()
                && self.meshes.contains_key(&mesh.name)
            {
                let is_transparent = if let Some(material) = world.get_material(entity) {
                    material.alpha_mode == crate::ecs::material::components::AlphaMode::Blend
                } else {
                    false
                };

                let is_overlay =
                    render_layer == crate::ecs::render_layer::components::RenderLayer::OVERLAY;

                if is_overlay {
                    if is_transparent {
                        overlay_transparent_entities_by_mesh
                            .entry(mesh.name.clone())
                            .or_default()
                            .push(entity);
                    } else {
                        overlay_opaque_entities_by_mesh
                            .entry(mesh.name.clone())
                            .or_default()
                            .push(entity);
                    }
                } else if is_transparent {
                    world_transparent_entities_by_mesh
                        .entry(mesh.name.clone())
                        .or_default()
                        .push(entity);
                } else {
                    world_opaque_entities_by_mesh
                        .entry(mesh.name.clone())
                        .or_default()
                        .push(entity);
                }
            }
        }

        let instanced_mesh_entities: Vec<_> = world
            .query_entities(crate::ecs::world::INSTANCED_MESH)
            .collect();

        let mut all_transforms = Vec::new();
        let mut all_objects = Vec::new();
        let mut opaque_instances = Vec::new();
        let mut transparent_instances = Vec::new();
        let mut overlay_opaque_instances = Vec::new();
        let mut overlay_transparent_instances = Vec::new();

        let mut world_opaque_by_mesh_material: HashMap<(u32, u32), Vec<crate::ecs::world::Entity>> =
            HashMap::new();
        for (mesh_name, entities) in world_opaque_entities_by_mesh {
            if let Some(&mesh_id) = self.meshes.get(&mesh_name) {
                for entity in entities {
                    let material_id = *material_map.get(&entity).unwrap_or(&0);
                    world_opaque_by_mesh_material
                        .entry((mesh_id, material_id))
                        .or_default()
                        .push(entity);
                }
            }
        }

        for ((mesh_id, material_id), entities) in world_opaque_by_mesh_material {
            let start = all_objects.len() as u32;
            let batch_id = opaque_instances.len() as u32;

            for entity in entities {
                if let Some(transform) = world.get_global_transform(entity) {
                    let transform_index = all_transforms.len() as u32;
                    all_transforms.push(ModelMatrix {
                        model: transform.0.into(),
                        normal_matrix: compute_normal_matrix(&transform.0),
                    });

                    all_objects.push(ObjectData {
                        transform_index,
                        mesh_id,
                        material_id,
                        batch_id,
                    });
                }
            }

            let end = all_objects.len() as u32;
            if start < end {
                opaque_instances.push((mesh_id, material_id, start, end));
            }
        }

        for &entity in &instanced_mesh_entities {
            if let Some(visibility) = world.get_visibility(entity)
                && !visibility.visible
            {
                continue;
            }

            let Some(instanced_mesh) = world.get_instanced_mesh(entity) else {
                continue;
            };

            if instanced_mesh.instances.is_empty() {
                continue;
            }

            let Some(&mesh_id) = self.meshes.get(&instanced_mesh.mesh_name) else {
                continue;
            };

            let material_id = *material_map.get(&entity).unwrap_or(&0);

            let is_transparent = if let Some(material) = world.get_material(entity) {
                material.alpha_mode == crate::ecs::material::components::AlphaMode::Blend
            } else {
                false
            };

            let start = all_objects.len() as u32;
            let base_transform_index = all_transforms.len() as u32;
            let cached_model_matrices = instanced_mesh.cached_model_matrices();
            let instance_count = cached_model_matrices.len();

            all_transforms.extend(cached_model_matrices.iter().map(|instance_matrix| {
                ModelMatrix {
                    model: instance_matrix.model,
                    normal_matrix: instance_matrix.normal_matrix,
                }
            }));

            let batch_id = if is_transparent {
                (opaque_instances.len() + transparent_instances.len()) as u32
            } else {
                opaque_instances.len() as u32
            };

            all_objects.extend((0..instance_count as u32).map(|_index| ObjectData {
                transform_index: base_transform_index + _index,
                mesh_id,
                material_id,
                batch_id,
            }));

            let end = all_objects.len() as u32;
            if start < end {
                if is_transparent {
                    transparent_instances.push((mesh_id, material_id, start, end));
                } else {
                    opaque_instances.push((mesh_id, material_id, start, end));
                }
            }
        }

        let mut world_transparent_by_mesh_material: HashMap<
            (u32, u32),
            Vec<crate::ecs::world::Entity>,
        > = HashMap::new();
        for (mesh_name, entities) in world_transparent_entities_by_mesh {
            if let Some(&mesh_id) = self.meshes.get(&mesh_name) {
                for entity in entities {
                    let material_id = *material_map.get(&entity).unwrap_or(&0);
                    world_transparent_by_mesh_material
                        .entry((mesh_id, material_id))
                        .or_default()
                        .push(entity);
                }
            }
        }

        for ((mesh_id, material_id), entities) in world_transparent_by_mesh_material {
            let start = all_objects.len() as u32;
            let batch_id = (opaque_instances.len() + transparent_instances.len()) as u32;

            for entity in entities {
                if let Some(transform) = world.get_global_transform(entity) {
                    let transform_index = all_transforms.len() as u32;
                    all_transforms.push(ModelMatrix {
                        model: transform.0.into(),
                        normal_matrix: compute_normal_matrix(&transform.0),
                    });

                    all_objects.push(ObjectData {
                        transform_index,
                        mesh_id,
                        material_id,
                        batch_id,
                    });
                }
            }

            let end = all_objects.len() as u32;
            if start < end {
                transparent_instances.push((mesh_id, material_id, start, end));
            }
        }

        let mut overlay_opaque_by_mesh_material: HashMap<
            (u32, u32),
            Vec<crate::ecs::world::Entity>,
        > = HashMap::new();
        for (mesh_name, entities) in overlay_opaque_entities_by_mesh {
            if let Some(&mesh_id) = self.meshes.get(&mesh_name) {
                for entity in entities {
                    let material_id = *material_map.get(&entity).unwrap_or(&0);
                    overlay_opaque_by_mesh_material
                        .entry((mesh_id, material_id))
                        .or_default()
                        .push(entity);
                }
            }
        }

        for ((mesh_id, material_id), entities) in overlay_opaque_by_mesh_material {
            let start = all_objects.len() as u32;
            let batch_id = (opaque_instances.len()
                + transparent_instances.len()
                + overlay_opaque_instances.len()) as u32;

            for entity in entities {
                if let Some(transform) = world.get_global_transform(entity) {
                    let transform_index = all_transforms.len() as u32;
                    all_transforms.push(ModelMatrix {
                        model: transform.0.into(),
                        normal_matrix: compute_normal_matrix(&transform.0),
                    });

                    all_objects.push(ObjectData {
                        transform_index,
                        mesh_id,
                        material_id,
                        batch_id,
                    });
                }
            }

            let end = all_objects.len() as u32;
            if start < end {
                overlay_opaque_instances.push((mesh_id, material_id, start, end));
            }
        }

        let mut overlay_transparent_by_mesh_material: HashMap<
            (u32, u32),
            Vec<crate::ecs::world::Entity>,
        > = HashMap::new();
        for (mesh_name, entities) in overlay_transparent_entities_by_mesh {
            if let Some(&mesh_id) = self.meshes.get(&mesh_name) {
                for entity in entities {
                    let material_id = *material_map.get(&entity).unwrap_or(&0);
                    overlay_transparent_by_mesh_material
                        .entry((mesh_id, material_id))
                        .or_default()
                        .push(entity);
                }
            }
        }

        for ((mesh_id, material_id), entities) in overlay_transparent_by_mesh_material {
            let start = all_objects.len() as u32;
            let batch_id = (opaque_instances.len()
                + transparent_instances.len()
                + overlay_opaque_instances.len()
                + overlay_transparent_instances.len()) as u32;

            for entity in entities {
                if let Some(transform) = world.get_global_transform(entity) {
                    let transform_index = all_transforms.len() as u32;
                    all_transforms.push(ModelMatrix {
                        model: transform.0.into(),
                        normal_matrix: compute_normal_matrix(&transform.0),
                    });

                    all_objects.push(ObjectData {
                        transform_index,
                        mesh_id,
                        material_id,
                        batch_id,
                    });
                }
            }

            let end = all_objects.len() as u32;
            if start < end {
                overlay_transparent_instances.push((mesh_id, material_id, start, end));
            }
        }

        self.regular_object_count = all_transforms.len() as u32;

        if !all_objects.is_empty() {
            if all_transforms.len() > self.transform_buffer_size {
                let new_size = std::cmp::min(
                    (all_transforms.len() as f32 * BUFFER_GROWTH_FACTOR).ceil() as usize,
                    MAX_INSTANCES,
                );

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

                    self.transform_buffer = new_buffer;
                    self.transform_buffer_size = new_size;

                    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(),
                                },
                            ],
                        });
                }
            }

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

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

                    self.object_buffer = new_buffer;
                    self.object_buffer_size = new_size;

                    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(),
                                },
                            ],
                        });
                }
            }

            if all_transforms.len() <= self.transform_buffer_size {
                queue.write_buffer(
                    &self.transform_buffer,
                    0,
                    bytemuck::cast_slice(&all_transforms),
                );
            }

            if all_objects.len() <= self.object_buffer_size {
                queue.write_buffer(&self.object_buffer, 0, bytemuck::cast_slice(&all_objects));
            }

            self.object_count = all_objects.len() as u32;

            let hiz_full_view = self
                .hiz_texture
                .create_view(&wgpu::TextureViewDescriptor::default());

            self.culling_bind_group = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Mesh Culling Bind Group"),
                layout: &self.culling_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: self.transform_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: self.object_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: self.culling_uniform_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 3,
                        resource: self.mesh_bounds_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 4,
                        resource: wgpu::BindingResource::TextureView(&hiz_full_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 5,
                        resource: wgpu::BindingResource::Sampler(&self.hiz_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 6,
                        resource: self.indirect_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 7,
                        resource: self.visible_indices_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 8,
                        resource: self.visibility_buffer.as_entire_binding(),
                    },
                ],
            }));

            if let Some(camera_matrices) =
                crate::ecs::camera::queries::query_active_camera_matrices(world)
            {
                let view_proj = camera_matrices.projection * camera_matrices.view;
                let frustum_planes = extract_frustum_planes(&view_proj);

                let hiz_size = self.hiz_texture.size();
                let hiz_mip_count = self.hiz_views.len() as u32;

                let culling_uniforms = CullingUniforms {
                    frustum_planes: frustum_planes.map(|v| [v.x, v.y, v.z, v.w]),
                    view_projection: [
                        [
                            view_proj[(0, 0)],
                            view_proj[(1, 0)],
                            view_proj[(2, 0)],
                            view_proj[(3, 0)],
                        ],
                        [
                            view_proj[(0, 1)],
                            view_proj[(1, 1)],
                            view_proj[(2, 1)],
                            view_proj[(3, 1)],
                        ],
                        [
                            view_proj[(0, 2)],
                            view_proj[(1, 2)],
                            view_proj[(2, 2)],
                            view_proj[(3, 2)],
                        ],
                        [
                            view_proj[(0, 3)],
                            view_proj[(1, 3)],
                            view_proj[(2, 3)],
                            view_proj[(3, 3)],
                        ],
                    ],
                    hiz_size: [hiz_size.width as f32, hiz_size.height as f32],
                    hiz_mip_count,
                    object_count: self.object_count,
                };
                queue.write_buffer(
                    &self.culling_uniform_buffer,
                    0,
                    bytemuck::cast_slice(&[culling_uniforms]),
                );
            }
        }

        self.opaque_instances = opaque_instances;
        self.transparent_instances = transparent_instances;
        self.overlay_opaque_instances = overlay_opaque_instances;
        self.overlay_transparent_instances = overlay_transparent_instances;

        let all_instances: Vec<_> = self
            .opaque_instances
            .iter()
            .chain(self.transparent_instances.iter())
            .chain(self.overlay_opaque_instances.iter())
            .chain(self.overlay_transparent_instances.iter())
            .cloned()
            .collect();

        let total_batch_count = all_instances.len();

        if total_batch_count > self.indirect_buffer_size {
            let new_size = std::cmp::min(
                (total_batch_count as f32 * BUFFER_GROWTH_FACTOR).ceil() as usize,
                MAX_INSTANCES,
            );

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

            self.culling_bind_group = None;
        }

        let total_object_count = all_objects.len();
        let visible_indices_buffer_size =
            (self.visible_indices_buffer.size() / std::mem::size_of::<u32>() as u64) as usize;

        if total_object_count > visible_indices_buffer_size {
            let new_size = std::cmp::min(
                (total_object_count as f32 * BUFFER_GROWTH_FACTOR).ceil() as usize,
                MAX_INSTANCES,
            );

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

            self.visibility_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Visibility Buffer (Resized)"),
                size: (std::mem::size_of::<u32>() * new_size) as u64,
                usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });

            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(),
                    },
                ],
            });

            self.culling_bind_group = None;
        }

        if !all_instances.is_empty() {
            let mut indirect_commands = Vec::with_capacity(total_batch_count);
            let mut indirect_reset_commands = Vec::with_capacity(total_batch_count);
            let mut visible_indices = Vec::with_capacity(total_object_count);

            for &(mesh_id, _material_id, start, end) in all_instances.iter() {
                let mesh_data = &self.mesh_data[mesh_id as usize];
                let instance_count = end - start;

                indirect_commands.push(DrawIndexedIndirect {
                    index_count: mesh_data.index_count,
                    instance_count,
                    first_index: mesh_data.index_offset,
                    base_vertex: mesh_data.vertex_offset as i32,
                    first_instance: start,
                });

                indirect_reset_commands.push(DrawIndexedIndirect {
                    index_count: mesh_data.index_count,
                    instance_count: 0,
                    first_index: mesh_data.index_offset,
                    base_vertex: mesh_data.vertex_offset as i32,
                    first_instance: start,
                });

                for object_index in start..end {
                    visible_indices.push(object_index);
                }
            }

            queue.write_buffer(
                &self.indirect_buffer,
                0,
                bytemuck::cast_slice(&indirect_commands),
            );
            queue.write_buffer(
                &self.visible_indices_buffer,
                0,
                bytemuck::cast_slice(&visible_indices),
            );

            if total_batch_count > 0 {
                let reset_buffer_size = (self.indirect_reset_buffer.size()
                    / std::mem::size_of::<DrawIndexedIndirect>() as u64)
                    as usize;
                if total_batch_count > reset_buffer_size {
                    self.indirect_reset_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                        label: Some("Indirect Reset Buffer (Resized)"),
                        size: (std::mem::size_of::<DrawIndexedIndirect>()
                            * self.indirect_buffer_size) as u64,
                        usage: wgpu::BufferUsages::COPY_SRC | wgpu::BufferUsages::COPY_DST,
                        mapped_at_creation: false,
                    });
                }
                queue.write_buffer(
                    &self.indirect_reset_buffer,
                    0,
                    bytemuck::cast_slice(&indirect_reset_commands),
                );
                self.indirect_reset_count = total_batch_count;
            }
        }

        let max_material_id = materials_data.len() as u32;
        self.material_bind_groups
            .retain(|&id, _| id < max_material_id);
        self.material_bind_group_cache_key
            .retain(|&id, _| id < max_material_id);

        for material_id in 0..materials_data.len() as u32 {
            let cache_key = material_texture_map
                .get(&material_id)
                .cloned()
                .unwrap_or((None, None));

            if self.material_bind_groups.contains_key(&material_id)
                && self.material_bind_group_cache_key.get(&material_id) == Some(&cache_key)
            {
                continue;
            }

            let base_texture_view;
            let base_sampler;
            let emissive_texture_view;
            let emissive_sampler;

            if let Some(ref base_texture_name) = cache_key.0 {
                if let Some((view, sampler)) = self.registered_textures.get(base_texture_name) {
                    base_texture_view = view;
                    base_sampler = sampler;
                } else {
                    base_texture_view = &self.dummy_white_view;
                    base_sampler = &self.dummy_sampler;
                }
            } else {
                base_texture_view = &self.dummy_white_view;
                base_sampler = &self.dummy_sampler;
            }

            if let Some(ref emissive_texture_name) = cache_key.1 {
                if let Some((view, sampler)) = self.registered_textures.get(emissive_texture_name) {
                    emissive_texture_view = view;
                    emissive_sampler = sampler;
                } else {
                    emissive_texture_view = &self.dummy_black_view;
                    emissive_sampler = &self.dummy_sampler;
                }
            } else {
                emissive_texture_view = &self.dummy_black_view;
                emissive_sampler = &self.dummy_sampler;
            }

            let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some(&format!("Material {} Texture Bind Group", material_id)),
                layout: &self.texture_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(base_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(base_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: wgpu::BindingResource::TextureView(emissive_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 3,
                        resource: wgpu::BindingResource::Sampler(emissive_sampler),
                    },
                ],
            });
            self.material_bind_groups.insert(material_id, bind_group);
            self.material_bind_group_cache_key
                .insert(material_id, cache_key);
        }
    }

    fn execute<'r, 'e>(
        &mut self,
        context: PassExecutionContext<'r, 'e, crate::ecs::world::World>,
    ) -> crate::render::wgpu::rendergraph::Result<
        Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
    > {
        let (shadow_depth_view, _, _) = context.get_depth_attachment("shadow_depth")?;

        self.shadow_bind_group = context
            .device
            .create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Shadow Bind Group"),
                layout: &self.shadow_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(shadow_depth_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
                    },
                ],
            });

        let (color_view, color_load, color_store) = context.get_color_attachment("color")?;
        let (depth_view, depth_load, _depth_store) = context.get_depth_attachment("depth")?;

        if self.object_count > 0
            && let Some(ref culling_bind_group) = self.culling_bind_group
        {
            if self.indirect_reset_count > 0 {
                let copy_size =
                    (self.indirect_reset_count * std::mem::size_of::<DrawIndexedIndirect>()) as u64;
                context.encoder.copy_buffer_to_buffer(
                    &self.indirect_reset_buffer,
                    0,
                    &self.indirect_buffer,
                    0,
                    copy_size,
                );
            }

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

            compute_pass.set_pipeline(&self.culling_pipeline);
            compute_pass.set_bind_group(0, culling_bind_group, &[]);

            let workgroup_size = 256;
            let dispatch_size = self.object_count.div_ceil(workgroup_size);
            compute_pass.dispatch_workgroups(dispatch_size, 1, 1);
        }

        if !self.opaque_instances.is_empty() {
            let mut opaque_pass = context
                .encoder
                .begin_render_pass(&wgpu::RenderPassDescriptor {
                    label: Some("Opaque Mesh Pass"),
                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                        view: color_view,
                        resolve_target: None,
                        ops: wgpu::Operations {
                            load: color_load,
                            store: wgpu::StoreOp::Store,
                        },
                        depth_slice: None,
                    })],
                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                        view: depth_view,
                        depth_ops: Some(wgpu::Operations {
                            load: depth_load,
                            store: wgpu::StoreOp::Store,
                        }),
                        stencil_ops: None,
                    }),
                    timestamp_writes: None,
                    occlusion_query_set: None,
                });

            opaque_pass.set_pipeline(&self.opaque_pipeline);
            opaque_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
            opaque_pass.set_bind_group(1, &self.instance_bind_group, &[]);
            opaque_pass.set_bind_group(3, &self.shadow_bind_group, &[]);
            opaque_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            opaque_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);

            let mut current_material: Option<u32> = None;
            let mut batch_start_index: usize = 0;

            for (batch_index, &(_mesh_id, material_id, _start, _end)) in
                self.opaque_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) = self.material_bind_groups.get(&prev_material)
                            {
                                opaque_pass.set_bind_group(2, bind_group, &[]);
                            }
                            let indirect_offset = (batch_start_index
                                * std::mem::size_of::<DrawIndexedIndirect>())
                                as u64;
                            opaque_pass.multi_draw_indexed_indirect(
                                &self.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 = self.opaque_instances.len() - batch_start_index;
                if count > 0 {
                    if let Some(bind_group) = self.material_bind_groups.get(&material_id) {
                        opaque_pass.set_bind_group(2, bind_group, &[]);
                    }
                    let indirect_offset =
                        (batch_start_index * std::mem::size_of::<DrawIndexedIndirect>()) as u64;
                    opaque_pass.multi_draw_indexed_indirect(
                        &self.indirect_buffer,
                        indirect_offset,
                        count as u32,
                    );
                }
            }

            drop(opaque_pass);
        }

        if !self.transparent_instances.is_empty() {
            let mut oit_pass = context
                .encoder
                .begin_render_pass(&wgpu::RenderPassDescriptor {
                    label: Some("OIT Mesh Pass"),
                    color_attachments: &[
                        Some(wgpu::RenderPassColorAttachment {
                            view: &self.oit_accum_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
                                store: wgpu::StoreOp::Store,
                            },
                            depth_slice: None,
                        }),
                        Some(wgpu::RenderPassColorAttachment {
                            view: &self.oit_reveal_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
                                store: wgpu::StoreOp::Store,
                            },
                            depth_slice: None,
                        }),
                    ],
                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                        view: depth_view,
                        depth_ops: Some(wgpu::Operations {
                            load: wgpu::LoadOp::Load,
                            store: wgpu::StoreOp::Store,
                        }),
                        stencil_ops: None,
                    }),
                    timestamp_writes: None,
                    occlusion_query_set: None,
                });

            oit_pass.set_pipeline(&self.oit_pipeline);
            oit_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
            oit_pass.set_bind_group(1, &self.instance_bind_group, &[]);
            oit_pass.set_bind_group(3, &self.shadow_bind_group, &[]);
            oit_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            oit_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);

            let transparent_batch_offset = self.opaque_instances.len();
            let mut current_material: Option<u32> = None;
            let mut batch_start_index: usize = 0;

            for (batch_index, &(_mesh_id, material_id, _start, _end)) in
                self.transparent_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) = self.material_bind_groups.get(&prev_material)
                            {
                                oit_pass.set_bind_group(2, bind_group, &[]);
                            }
                            let indirect_offset = ((transparent_batch_offset + batch_start_index)
                                * std::mem::size_of::<DrawIndexedIndirect>())
                                as u64;
                            oit_pass.multi_draw_indexed_indirect(
                                &self.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 = self.transparent_instances.len() - batch_start_index;
                if count > 0 {
                    if let Some(bind_group) = self.material_bind_groups.get(&material_id) {
                        oit_pass.set_bind_group(2, bind_group, &[]);
                    }
                    let indirect_offset = ((transparent_batch_offset + batch_start_index)
                        * std::mem::size_of::<DrawIndexedIndirect>())
                        as u64;
                    oit_pass.multi_draw_indexed_indirect(
                        &self.indirect_buffer,
                        indirect_offset,
                        count as u32,
                    );
                }
            }

            drop(oit_pass);

            let mut composite_pass =
                context
                    .encoder
                    .begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: Some("OIT Composite Pass"),
                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                            view: color_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: wgpu::LoadOp::Load,
                                store: color_store,
                            },
                            depth_slice: None,
                        })],
                        depth_stencil_attachment: None,
                        timestamp_writes: None,
                        occlusion_query_set: None,
                    });

            composite_pass.set_pipeline(&self.oit_composite_pipeline);
            composite_pass.set_bind_group(0, &self.oit_composite_bind_group, &[]);
            composite_pass.draw(0..3, 0..1);

            drop(composite_pass);
        }

        if !self.overlay_opaque_instances.is_empty() {
            let mut overlay_opaque_pass =
                context
                    .encoder
                    .begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: Some("Overlay Opaque Mesh Pass"),
                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                            view: color_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: wgpu::LoadOp::Load,
                                store: wgpu::StoreOp::Store,
                            },
                            depth_slice: None,
                        })],
                        depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                            view: &self.overlay_depth_view,
                            depth_ops: Some(wgpu::Operations {
                                load: wgpu::LoadOp::Clear(0.0),
                                store: wgpu::StoreOp::Store,
                            }),
                            stencil_ops: None,
                        }),
                        timestamp_writes: None,
                        occlusion_query_set: None,
                    });

            overlay_opaque_pass.set_pipeline(&self.overlay_opaque_pipeline);
            overlay_opaque_pass.set_bind_group(0, &self.overlay_uniform_bind_group, &[]);
            overlay_opaque_pass.set_bind_group(1, &self.instance_bind_group, &[]);
            overlay_opaque_pass.set_bind_group(3, &self.shadow_bind_group, &[]);
            overlay_opaque_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            overlay_opaque_pass
                .set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);

            let overlay_opaque_batch_offset =
                self.opaque_instances.len() + self.transparent_instances.len();
            let mut current_material: Option<u32> = None;
            let mut batch_start_index: usize = 0;

            for (batch_index, &(_mesh_id, material_id, _start, _end)) in
                self.overlay_opaque_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) = self.material_bind_groups.get(&prev_material)
                            {
                                overlay_opaque_pass.set_bind_group(2, bind_group, &[]);
                            }
                            let indirect_offset = ((overlay_opaque_batch_offset
                                + batch_start_index)
                                * std::mem::size_of::<DrawIndexedIndirect>())
                                as u64;
                            overlay_opaque_pass.multi_draw_indexed_indirect(
                                &self.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 = self.overlay_opaque_instances.len() - batch_start_index;
                if count > 0 {
                    if let Some(bind_group) = self.material_bind_groups.get(&material_id) {
                        overlay_opaque_pass.set_bind_group(2, bind_group, &[]);
                    }
                    let indirect_offset = ((overlay_opaque_batch_offset + batch_start_index)
                        * std::mem::size_of::<DrawIndexedIndirect>())
                        as u64;
                    overlay_opaque_pass.multi_draw_indexed_indirect(
                        &self.indirect_buffer,
                        indirect_offset,
                        count as u32,
                    );
                }
            }

            drop(overlay_opaque_pass);
        }

        if !self.overlay_transparent_instances.is_empty() {
            let mut overlay_oit_pass =
                context
                    .encoder
                    .begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: Some("Overlay OIT Mesh Pass"),
                        color_attachments: &[
                            Some(wgpu::RenderPassColorAttachment {
                                view: &self.oit_accum_view,
                                resolve_target: None,
                                ops: wgpu::Operations {
                                    load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
                                    store: wgpu::StoreOp::Store,
                                },
                                depth_slice: None,
                            }),
                            Some(wgpu::RenderPassColorAttachment {
                                view: &self.oit_reveal_view,
                                resolve_target: None,
                                ops: wgpu::Operations {
                                    load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
                                    store: wgpu::StoreOp::Store,
                                },
                                depth_slice: None,
                            }),
                        ],
                        depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
                            view: &self.overlay_depth_view,
                            depth_ops: Some(wgpu::Operations {
                                load: wgpu::LoadOp::Load,
                                store: wgpu::StoreOp::Store,
                            }),
                            stencil_ops: None,
                        }),
                        timestamp_writes: None,
                        occlusion_query_set: None,
                    });

            overlay_oit_pass.set_pipeline(&self.oit_pipeline);
            overlay_oit_pass.set_bind_group(0, &self.overlay_uniform_bind_group, &[]);
            overlay_oit_pass.set_bind_group(1, &self.instance_bind_group, &[]);
            overlay_oit_pass.set_bind_group(3, &self.shadow_bind_group, &[]);
            overlay_oit_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            overlay_oit_pass
                .set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);

            let overlay_transparent_batch_offset = self.opaque_instances.len()
                + self.transparent_instances.len()
                + self.overlay_opaque_instances.len();
            let mut current_material: Option<u32> = None;
            let mut batch_start_index: usize = 0;

            for (batch_index, &(_mesh_id, material_id, _start, _end)) in
                self.overlay_transparent_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) = self.material_bind_groups.get(&prev_material)
                            {
                                overlay_oit_pass.set_bind_group(2, bind_group, &[]);
                            }
                            let indirect_offset = ((overlay_transparent_batch_offset
                                + batch_start_index)
                                * std::mem::size_of::<DrawIndexedIndirect>())
                                as u64;
                            overlay_oit_pass.multi_draw_indexed_indirect(
                                &self.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 = self.overlay_transparent_instances.len() - batch_start_index;
                if count > 0 {
                    if let Some(bind_group) = self.material_bind_groups.get(&material_id) {
                        overlay_oit_pass.set_bind_group(2, bind_group, &[]);
                    }
                    let indirect_offset = ((overlay_transparent_batch_offset + batch_start_index)
                        * std::mem::size_of::<DrawIndexedIndirect>())
                        as u64;
                    overlay_oit_pass.multi_draw_indexed_indirect(
                        &self.indirect_buffer,
                        indirect_offset,
                        count as u32,
                    );
                }
            }

            drop(overlay_oit_pass);

            let mut overlay_composite_pass =
                context
                    .encoder
                    .begin_render_pass(&wgpu::RenderPassDescriptor {
                        label: Some("Overlay OIT Composite Pass"),
                        color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                            view: color_view,
                            resolve_target: None,
                            ops: wgpu::Operations {
                                load: wgpu::LoadOp::Load,
                                store: color_store,
                            },
                            depth_slice: None,
                        })],
                        depth_stencil_attachment: None,
                        timestamp_writes: None,
                        occlusion_query_set: None,
                    });

            overlay_composite_pass.set_pipeline(&self.oit_composite_pipeline);
            overlay_composite_pass.set_bind_group(0, &self.oit_composite_bind_group, &[]);
            overlay_composite_pass.draw(0..3, 0..1);

            drop(overlay_composite_pass);
        }

        if !self.hiz_views.is_empty() {
            let hiz_size = self.hiz_texture.size();

            let depth_copy_bind_group =
                context
                    .device
                    .create_bind_group(&wgpu::BindGroupDescriptor {
                        label: Some("Depth Copy Bind Group"),
                        layout: &self.depth_copy_bind_group_layout,
                        entries: &[
                            wgpu::BindGroupEntry {
                                binding: 0,
                                resource: wgpu::BindingResource::TextureView(depth_view),
                            },
                            wgpu::BindGroupEntry {
                                binding: 1,
                                resource: wgpu::BindingResource::TextureView(&self.hiz_views[0]),
                            },
                        ],
                    });

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

                compute_pass.set_pipeline(&self.depth_copy_pipeline);
                compute_pass.set_bind_group(0, &depth_copy_bind_group, &[]);

                let dispatch_x = hiz_size.width.div_ceil(8);
                let dispatch_y = hiz_size.height.div_ceil(8);
                compute_pass.dispatch_workgroups(dispatch_x, dispatch_y, 1);
            }

            for mip_level in 1..self.hiz_views.len() {
                let src_mip = mip_level - 1;

                let src_view = self.hiz_texture.create_view(&wgpu::TextureViewDescriptor {
                    label: Some(&format!("Hi-Z Src View Mip {}", src_mip)),
                    base_mip_level: src_mip as u32,
                    mip_level_count: Some(1),
                    ..Default::default()
                });

                let hiz_bind_group = context
                    .device
                    .create_bind_group(&wgpu::BindGroupDescriptor {
                        label: Some(&format!("Hi-Z Bind Group Mip {}", mip_level)),
                        layout: &self.hiz_bind_group_layout,
                        entries: &[
                            wgpu::BindGroupEntry {
                                binding: 0,
                                resource: wgpu::BindingResource::TextureView(&src_view),
                            },
                            wgpu::BindGroupEntry {
                                binding: 1,
                                resource: wgpu::BindingResource::TextureView(
                                    &self.hiz_views[mip_level],
                                ),
                            },
                        ],
                    });

                let dst_width = (hiz_size.width >> mip_level).max(1);
                let dst_height = (hiz_size.height >> mip_level).max(1);

                let mut compute_pass =
                    context
                        .encoder
                        .begin_compute_pass(&wgpu::ComputePassDescriptor {
                            label: Some(&format!("Hi-Z Generate Mip {} Pass", mip_level)),
                            timestamp_writes: None,
                        });

                compute_pass.set_pipeline(&self.hiz_generate_pipeline);
                compute_pass.set_bind_group(0, &hiz_bind_group, &[]);

                let dispatch_x = dst_width.div_ceil(8);
                let dispatch_y = dst_height.div_ceil(8);
                compute_pass.dispatch_workgroups(dispatch_x, dispatch_y, 1);
            }
        }

        Ok(context.into_sub_graph_commands())
    }
}

fn reverse_z_ortho_light(
    left: f32,
    right: f32,
    bottom: f32,
    top: f32,
    near: f32,
    far: f32,
) -> nalgebra_glm::Mat4 {
    let width = right - left;
    let height = top - bottom;
    let depth = far - near;

    nalgebra_glm::Mat4::new(
        2.0 / width,
        0.0,
        0.0,
        -(right + left) / width,
        0.0,
        2.0 / height,
        0.0,
        -(top + bottom) / height,
        0.0,
        0.0,
        1.0 / depth,
        far / depth,
        0.0,
        0.0,
        0.0,
        1.0,
    )
}

fn extract_frustum_planes(view_proj: &crate::ecs::world::Mat4) -> [crate::ecs::world::Vec4; 6] {
    let mut planes = [crate::ecs::world::Vec4::zeros(); 6];

    let row0 = crate::ecs::world::Vec4::new(
        view_proj[(0, 0)],
        view_proj[(0, 1)],
        view_proj[(0, 2)],
        view_proj[(0, 3)],
    );
    let row1 = crate::ecs::world::Vec4::new(
        view_proj[(1, 0)],
        view_proj[(1, 1)],
        view_proj[(1, 2)],
        view_proj[(1, 3)],
    );
    let row2 = crate::ecs::world::Vec4::new(
        view_proj[(2, 0)],
        view_proj[(2, 1)],
        view_proj[(2, 2)],
        view_proj[(2, 3)],
    );
    let row3 = crate::ecs::world::Vec4::new(
        view_proj[(3, 0)],
        view_proj[(3, 1)],
        view_proj[(3, 2)],
        view_proj[(3, 3)],
    );

    planes[0] = row3 + row0;
    planes[1] = row3 - row0;
    planes[2] = row3 + row1;
    planes[3] = row3 - row1;
    planes[4] = row3 + row2;
    planes[5] = row3 - row2;

    for plane in &mut planes {
        let normal_length = (plane.x * plane.x + plane.y * plane.y + plane.z * plane.z).sqrt();
        if normal_length > 1e-6 {
            *plane /= normal_length;
        }
    }

    planes
}
