use crate::ecs::generational_registry::{registry_entry, registry_entry_by_name};
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use std::collections::HashMap;

use super::super::projection::*;
use super::pass::MeshPass;
use super::types::*;

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

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

    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 light_result = collect_lights(world, MAX_LIGHTS);
        let mut lights_data = light_result.lights_data;
        let directional_light = light_result.directional_light;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        let mesh_entities: Vec<_> = world
            .query_entities(crate::ecs::world::RENDER_MESH)
            .filter(|&entity| {
                !world.entity_has_components(
                    entity,
                    crate::ecs::world::SKIN | crate::ecs::world::WATER,
                )
            })
            .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, MaterialTextures> = HashMap::new();
        let mut name_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,
            has_normal_texture: 0,
            has_metallic_roughness_texture: 0,
            has_occlusion_texture: 0,
            normal_scale: 1.0,
            occlusion_strength: 1.0,
            roughness: 0.5,
            metallic: 0.0,
            unlit: 0,
            normal_map_flags: 0,
            uv_scale: [1.0, 1.0],
            transmission_factor: 0.0,
            has_transmission_texture: 0,
            thickness: 0.0,
            has_thickness_texture: 0,
            _align_attenuation: [0, 0],
            attenuation_color: [1.0, 1.0, 1.0],
            attenuation_distance: f32::INFINITY,
            ior: 1.5,
            specular_factor: 1.0,
            _align_specular: [0, 0],
            specular_color_factor: [1.0, 1.0, 1.0],
            has_specular_texture: 0,
            has_specular_color_texture: 0,
            emissive_strength: 1.0,
            _padding: [0, 0],
        }];

        for &entity in &mesh_entities {
            if let Some(material_ref) = world.get_material_ref(entity) {
                let material_opt = if let Some(id) = material_ref.id {
                    registry_entry(
                        &world.resources.material_registry.registry,
                        id.index,
                        id.generation,
                    )
                } else {
                    registry_entry_by_name(
                        &world.resources.material_registry.registry,
                        &material_ref.name,
                    )
                };
                let material_name = &material_ref.name;
                let material_id = if let Some(&existing_id) = name_to_material_id.get(material_name)
                {
                    existing_id
                } else if let Some(material) = material_opt {
                    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,
                    };
                    let normal_map_flags = if material.normal_map_flip_y {
                        NORMAL_MAP_FLIP_Y
                    } else {
                        0
                    } | if material.normal_map_two_component {
                        NORMAL_MAP_TWO_COMPONENT
                    } else {
                        0
                    };
                    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,
                        has_normal_texture: material.normal_texture.is_some() as u32,
                        has_metallic_roughness_texture: material
                            .metallic_roughness_texture
                            .is_some()
                            as u32,
                        has_occlusion_texture: material.occlusion_texture.is_some() as u32,
                        normal_scale: material.normal_scale,
                        occlusion_strength: material.occlusion_strength,
                        roughness: material.roughness,
                        metallic: material.metallic,
                        unlit: material.unlit as u32,
                        normal_map_flags,
                        uv_scale: material.uv_scale,
                        transmission_factor: material.transmission_factor,
                        has_transmission_texture: material.transmission_texture.is_some() as u32,
                        thickness: material.thickness,
                        has_thickness_texture: material.thickness_texture.is_some() as u32,
                        _align_attenuation: [0, 0],
                        attenuation_color: material.attenuation_color,
                        attenuation_distance: material.attenuation_distance,
                        ior: material.ior,
                        specular_factor: material.specular_factor,
                        _align_specular: [0, 0],
                        specular_color_factor: material.specular_color_factor,
                        has_specular_texture: material.specular_texture.is_some() as u32,
                        has_specular_color_texture: material.specular_color_texture.is_some()
                            as u32,
                        emissive_strength: material.emissive_strength,
                        _padding: [0, 0],
                    });
                    material_texture_map.insert(
                        new_id,
                        MaterialTextures {
                            base_texture: material.base_texture.clone(),
                            emissive_texture: material.emissive_texture.clone(),
                            normal_texture: material.normal_texture.clone(),
                            metallic_roughness_texture: material.metallic_roughness_texture.clone(),
                            occlusion_texture: material.occlusion_texture.clone(),
                            transmission_texture: material.transmission_texture.clone(),
                            thickness_texture: material.thickness_texture.clone(),
                            specular_texture: material.specular_texture.clone(),
                            specular_color_texture: material.specular_color_texture.clone(),
                        },
                    );
                    name_to_material_id.insert(material_name.clone(), new_id);
                    new_id
                } else {
                    0
                };
                material_map.insert(entity, material_id);
            }
        }

        for &entity in &instanced_mesh_entities_for_materials {
            if let Some(material_ref) = world.get_material_ref(entity) {
                let material_opt = if let Some(id) = material_ref.id {
                    registry_entry(
                        &world.resources.material_registry.registry,
                        id.index,
                        id.generation,
                    )
                } else {
                    registry_entry_by_name(
                        &world.resources.material_registry.registry,
                        &material_ref.name,
                    )
                };
                let material_name = &material_ref.name;
                let material_id = if let Some(&existing_id) = name_to_material_id.get(material_name)
                {
                    existing_id
                } else if let Some(material) = material_opt {
                    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,
                    };
                    let normal_map_flags = if material.normal_map_flip_y {
                        NORMAL_MAP_FLIP_Y
                    } else {
                        0
                    } | if material.normal_map_two_component {
                        NORMAL_MAP_TWO_COMPONENT
                    } else {
                        0
                    };
                    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,
                        has_normal_texture: material.normal_texture.is_some() as u32,
                        has_metallic_roughness_texture: material
                            .metallic_roughness_texture
                            .is_some()
                            as u32,
                        has_occlusion_texture: material.occlusion_texture.is_some() as u32,
                        normal_scale: material.normal_scale,
                        occlusion_strength: material.occlusion_strength,
                        roughness: material.roughness,
                        metallic: material.metallic,
                        unlit: material.unlit as u32,
                        normal_map_flags,
                        uv_scale: material.uv_scale,
                        transmission_factor: material.transmission_factor,
                        has_transmission_texture: material.transmission_texture.is_some() as u32,
                        thickness: material.thickness,
                        has_thickness_texture: material.thickness_texture.is_some() as u32,
                        _align_attenuation: [0, 0],
                        attenuation_color: material.attenuation_color,
                        attenuation_distance: material.attenuation_distance,
                        ior: material.ior,
                        specular_factor: material.specular_factor,
                        _align_specular: [0, 0],
                        specular_color_factor: material.specular_color_factor,
                        has_specular_texture: material.specular_texture.is_some() as u32,
                        has_specular_color_texture: material.specular_color_texture.is_some()
                            as u32,
                        emissive_strength: material.emissive_strength,
                        _padding: [0, 0],
                    });
                    material_texture_map.insert(
                        new_id,
                        MaterialTextures {
                            base_texture: material.base_texture.clone(),
                            emissive_texture: material.emissive_texture.clone(),
                            normal_texture: material.normal_texture.clone(),
                            metallic_roughness_texture: material.metallic_roughness_texture.clone(),
                            occlusion_texture: material.occlusion_texture.clone(),
                            transmission_texture: material.transmission_texture.clone(),
                            thickness_texture: material.thickness_texture.clone(),
                            specular_texture: material.specular_texture.clone(),
                            specular_color_texture: material.specular_color_texture.clone(),
                        },
                    );
                    name_to_material_id.insert(material_name.clone(), new_id);
                    new_id
                } else {
                    0
                };
                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.rebuild_instance_bind_group(device);
        }

        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 = world
                    .get_material_ref(entity)
                    .and_then(|mat_ref| {
                        if let Some(id) = mat_ref.id {
                            registry_entry(
                                &world.resources.material_registry.registry,
                                id.index,
                                id.generation,
                            )
                        } else {
                            registry_entry_by_name(
                                &world.resources.material_registry.registry,
                                &mat_ref.name,
                            )
                        }
                    })
                    .is_some_and(|material| {
                        material.alpha_mode == crate::ecs::material::components::AlphaMode::Blend
                    });

                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_custom_data: Vec<[f32; 4]> = 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 partial_custom_data_updates: Vec<(usize, [f32; 4])> = 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);
                }
            }
        }

        let mut world_opaque_batches: Vec<_> = world_opaque_by_mesh_material.into_iter().collect();
        world_opaque_batches.sort_by_key(|((mesh_id, material_id), _)| (*mesh_id, *material_id));

        for ((mesh_id, material_id), mut entities) in world_opaque_batches {
            entities.sort_by_key(|e| (e.id, e.generation));
            let start = all_objects.len() as u32;
            let batch_id = opaque_instances.len() as u32;

            let mesh_data = &self.mesh_data[mesh_id as usize];

            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_custom_data.push([1.0, 1.0, 1.0, 1.0]);

                    let morph_weights = if let Some(mw) = world.get_morph_weights(entity) {
                        let mut weights = [0.0f32; 8];
                        for (index, weight) in mw.weights.iter().take(8).enumerate() {
                            weights[index] = *weight;
                        }
                        weights
                    } else {
                        [0.0f32; 8]
                    };

                    all_objects.push(ObjectData {
                        transform_index,
                        mesh_id,
                        material_id,
                        batch_id,
                        morph_weights,
                        morph_target_count: mesh_data.morph_target_count,
                        morph_displacement_offset: mesh_data.morph_displacement_offset,
                        mesh_vertex_offset: mesh_data.vertex_offset,
                        mesh_vertex_count: mesh_data.vertex_count,
                    });
                }
            }

            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 = world
                .get_material_ref(entity)
                .and_then(|mat_ref| {
                    if let Some(id) = mat_ref.id {
                        registry_entry(
                            &world.resources.material_registry.registry,
                            id.index,
                            id.generation,
                        )
                    } else {
                        registry_entry_by_name(
                            &world.resources.material_registry.registry,
                            &mat_ref.name,
                        )
                    }
                })
                .is_some_and(|material| {
                    material.alpha_mode == crate::ecs::material::components::AlphaMode::Blend
                });

            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 custom_data_slice = instanced_mesh.custom_data_slice();
            let dirty_indices = instanced_mesh.take_dirty_custom_data_indices();
            let instance_count = cached_model_matrices.len();

            for local_index in dirty_indices {
                if local_index < custom_data_slice.len() {
                    let global_index = base_transform_index as usize + local_index;
                    partial_custom_data_updates
                        .push((global_index, custom_data_slice[local_index].tint));
                }
            }

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

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

            let instanced_mesh_data = &self.mesh_data[mesh_id as usize];
            all_objects.extend((0..instance_count as u32).map(|_index| ObjectData {
                transform_index: base_transform_index + _index,
                mesh_id,
                material_id,
                batch_id,
                morph_weights: [0.0f32; 8],
                morph_target_count: instanced_mesh_data.morph_target_count,
                morph_displacement_offset: instanced_mesh_data.morph_displacement_offset,
                mesh_vertex_offset: instanced_mesh_data.vertex_offset,
                mesh_vertex_count: instanced_mesh_data.vertex_count,
            }));

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

        let mut world_transparent_batches: Vec<_> =
            world_transparent_by_mesh_material.into_iter().collect();
        world_transparent_batches
            .sort_by_key(|((mesh_id, material_id), _)| (*mesh_id, *material_id));

        for ((mesh_id, material_id), mut entities) in world_transparent_batches {
            entities.sort_by_key(|e| (e.id, e.generation));
            let start = all_objects.len() as u32;
            let batch_id = (opaque_instances.len() + transparent_instances.len()) as u32;

            let mesh_data = &self.mesh_data[mesh_id as usize];

            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_custom_data.push([1.0, 1.0, 1.0, 1.0]);

                    let morph_weights = if let Some(mw) = world.get_morph_weights(entity) {
                        let mut weights = [0.0f32; 8];
                        for (index, weight) in mw.weights.iter().take(8).enumerate() {
                            weights[index] = *weight;
                        }
                        weights
                    } else {
                        [0.0f32; 8]
                    };

                    all_objects.push(ObjectData {
                        transform_index,
                        mesh_id,
                        material_id,
                        batch_id,
                        morph_weights,
                        morph_target_count: mesh_data.morph_target_count,
                        morph_displacement_offset: mesh_data.morph_displacement_offset,
                        mesh_vertex_offset: mesh_data.vertex_offset,
                        mesh_vertex_count: mesh_data.vertex_count,
                    });
                }
            }

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

        let mut overlay_opaque_batches: Vec<_> =
            overlay_opaque_by_mesh_material.into_iter().collect();
        overlay_opaque_batches.sort_by_key(|((mesh_id, material_id), _)| (*mesh_id, *material_id));

        for ((mesh_id, material_id), mut entities) in overlay_opaque_batches {
            entities.sort_by_key(|e| (e.id, e.generation));
            let start = all_objects.len() as u32;
            let batch_id = (opaque_instances.len()
                + transparent_instances.len()
                + overlay_opaque_instances.len()) as u32;

            let mesh_data = &self.mesh_data[mesh_id as usize];

            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_custom_data.push([1.0, 1.0, 1.0, 1.0]);

                    let morph_weights = if let Some(mw) = world.get_morph_weights(entity) {
                        let mut weights = [0.0f32; 8];
                        for (index, weight) in mw.weights.iter().take(8).enumerate() {
                            weights[index] = *weight;
                        }
                        weights
                    } else {
                        [0.0f32; 8]
                    };

                    all_objects.push(ObjectData {
                        transform_index,
                        mesh_id,
                        material_id,
                        batch_id,
                        morph_weights,
                        morph_target_count: mesh_data.morph_target_count,
                        morph_displacement_offset: mesh_data.morph_displacement_offset,
                        mesh_vertex_offset: mesh_data.vertex_offset,
                        mesh_vertex_count: mesh_data.vertex_count,
                    });
                }
            }

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

        let mut overlay_transparent_batches: Vec<_> =
            overlay_transparent_by_mesh_material.into_iter().collect();
        overlay_transparent_batches
            .sort_by_key(|((mesh_id, material_id), _)| (*mesh_id, *material_id));

        for ((mesh_id, material_id), mut entities) in overlay_transparent_batches {
            entities.sort_by_key(|e| (e.id, e.generation));
            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;

            let mesh_data = &self.mesh_data[mesh_id as usize];

            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_custom_data.push([1.0, 1.0, 1.0, 1.0]);

                    let morph_weights = if let Some(mw) = world.get_morph_weights(entity) {
                        let mut weights = [0.0f32; 8];
                        for (index, weight) in mw.weights.iter().take(8).enumerate() {
                            weights[index] = *weight;
                        }
                        weights
                    } else {
                        [0.0f32; 8]
                    };

                    all_objects.push(ObjectData {
                        transform_index,
                        mesh_id,
                        material_id,
                        batch_id,
                        morph_weights,
                        morph_target_count: mesh_data.morph_target_count,
                        morph_displacement_offset: mesh_data.morph_displacement_offset,
                        mesh_vertex_offset: mesh_data.vertex_offset,
                        mesh_vertex_count: mesh_data.vertex_count,
                    });
                }
            }

            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.custom_data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                        label: Some("Mesh Custom Data Buffer (Resized)"),
                        size: (std::mem::size_of::<[f32; 4]>() * new_size) as u64,
                        usage: wgpu::BufferUsages::STORAGE
                            | wgpu::BufferUsages::COPY_DST
                            | wgpu::BufferUsages::COPY_SRC,
                        mapped_at_creation: false,
                    });
                    self.custom_data_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(),
                                },
                                wgpu::BindGroupEntry {
                                    binding: 5,
                                    resource: self.custom_data_buffer.as_entire_binding(),
                                },
                                wgpu::BindGroupEntry {
                                    binding: 6,
                                    resource: self.morph_displacement_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(),
                                },
                                wgpu::BindGroupEntry {
                                    binding: 5,
                                    resource: self.custom_data_buffer.as_entire_binding(),
                                },
                                wgpu::BindGroupEntry {
                                    binding: 6,
                                    resource: self.morph_displacement_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_custom_data.len() <= self.custom_data_buffer_size {
                let use_partial_updates = !partial_custom_data_updates.is_empty()
                    && partial_custom_data_updates.len() < 100
                    && all_custom_data.len() == self.custom_data_buffer_size;

                if use_partial_updates {
                    let element_size = std::mem::size_of::<[f32; 4]>() as u64;
                    for (global_index, tint) in &partial_custom_data_updates {
                        let offset = (*global_index as u64) * element_size;
                        queue.write_buffer(
                            &self.custom_data_buffer,
                            offset,
                            bytemuck::cast_slice(&[*tint]),
                        );
                    }
                } else if !partial_custom_data_updates.is_empty()
                    || all_custom_data.len() != self.custom_data_buffer_size
                {
                    queue.write_buffer(
                        &self.custom_data_buffer,
                        0,
                        bytemuck::cast_slice(&all_custom_data),
                    );
                }
            }

            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;

            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: self.indirect_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 5,
                        resource: self.visible_indices_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 culling_uniforms = CullingUniforms {
                    frustum_planes: frustum_planes.map(|v| [v.x, v.y, v.z, v.w]),
                    object_count: self.object_count,
                    _padding: [0; 3],
                };
                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.rebuild_instance_bind_group(device);
            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_default();

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

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

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

            let (normal_texture_view, normal_sampler) = if let Some(ref name) =
                cache_key.normal_texture
                && let Some((view, sampler)) = self.registered_textures.get(name)
            {
                (view, sampler)
            } else {
                (&self.dummy_normal_view, &self.dummy_sampler)
            };

            let (metallic_roughness_texture_view, metallic_roughness_sampler) =
                if let Some(ref name) = cache_key.metallic_roughness_texture
                    && let Some((view, sampler)) = self.registered_textures.get(name)
                {
                    (view, sampler)
                } else {
                    (&self.dummy_white_view, &self.dummy_sampler)
                };

            let (occlusion_texture_view, occlusion_sampler) = if let Some(ref name) =
                cache_key.occlusion_texture
                && let Some((view, sampler)) = self.registered_textures.get(name)
            {
                (view, sampler)
            } else {
                (&self.dummy_white_view, &self.dummy_sampler)
            };

            let (transmission_texture_view, transmission_sampler) = if let Some(ref name) =
                cache_key.transmission_texture
                && let Some((view, sampler)) = self.registered_textures.get(name)
            {
                (view, sampler)
            } else {
                (&self.dummy_white_view, &self.dummy_sampler)
            };

            let (thickness_texture_view, thickness_sampler) = if let Some(ref name) =
                cache_key.thickness_texture
                && let Some((view, sampler)) = self.registered_textures.get(name)
            {
                (view, sampler)
            } else {
                (&self.dummy_white_view, &self.dummy_sampler)
            };

            let (specular_texture_view, specular_sampler) = if let Some(ref name) =
                cache_key.specular_texture
                && let Some((view, sampler)) = self.registered_textures.get(name)
            {
                (view, sampler)
            } else {
                (&self.dummy_white_view, &self.dummy_sampler)
            };

            let (specular_color_texture_view, specular_color_sampler) = if let Some(ref name) =
                cache_key.specular_color_texture
                && let Some((view, sampler)) = self.registered_textures.get(name)
            {
                (view, sampler)
            } else {
                (&self.dummy_white_view, &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),
                    },
                    wgpu::BindGroupEntry {
                        binding: 4,
                        resource: wgpu::BindingResource::TextureView(normal_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 5,
                        resource: wgpu::BindingResource::Sampler(normal_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 6,
                        resource: wgpu::BindingResource::TextureView(
                            metallic_roughness_texture_view,
                        ),
                    },
                    wgpu::BindGroupEntry {
                        binding: 7,
                        resource: wgpu::BindingResource::Sampler(metallic_roughness_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 8,
                        resource: wgpu::BindingResource::TextureView(occlusion_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 9,
                        resource: wgpu::BindingResource::Sampler(occlusion_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 10,
                        resource: wgpu::BindingResource::TextureView(transmission_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 11,
                        resource: wgpu::BindingResource::Sampler(transmission_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 12,
                        resource: wgpu::BindingResource::TextureView(thickness_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 13,
                        resource: wgpu::BindingResource::Sampler(thickness_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 14,
                        resource: wgpu::BindingResource::TextureView(specular_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 15,
                        resource: wgpu::BindingResource::Sampler(specular_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 16,
                        resource: wgpu::BindingResource::TextureView(specular_color_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 17,
                        resource: wgpu::BindingResource::Sampler(specular_color_sampler),
                    },
                ],
            });
            self.material_bind_groups.insert(material_id, bind_group);
            self.material_bind_group_cache_key
                .insert(material_id, cache_key);
        }

        self.newly_registered_textures.clear();
    }

    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")?;
        let (spotlight_shadow_atlas_view, _, _) =
            context.get_depth_attachment("spotlight_shadow_atlas")?;

        self.scene_bind_group = context
            .device
            .create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Scene Bind Group"),
                layout: &self.scene_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),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: wgpu::BindingResource::TextureView(spotlight_shadow_atlas_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 3,
                        resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 4,
                        resource: self.spotlight_shadow_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 5,
                        resource: wgpu::BindingResource::TextureView(&self.brdf_lut_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 6,
                        resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 7,
                        resource: wgpu::BindingResource::TextureView(&self.irradiance_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 8,
                        resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 9,
                        resource: wgpu::BindingResource::TextureView(&self.prefiltered_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 10,
                        resource: wgpu::BindingResource::Sampler(&self.ibl_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 11,
                        resource: wgpu::BindingResource::TextureView(
                            &self.point_shadow_cubemap_view,
                        ),
                    },
                    wgpu::BindGroupEntry {
                        binding: 12,
                        resource: wgpu::BindingResource::Sampler(&self.point_shadow_sampler),
                    },
                    wgpu::BindGroupEntry {
                        binding: 13,
                        resource: self.point_shadow_buffer.as_entire_binding(),
                    },
                ],
            });

        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.scene_bind_group, &[]);
            opaque_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            opaque_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);

            MeshPass::draw_batches(
                &mut opaque_pass,
                &self.opaque_instances,
                0,
                &self.material_bind_groups,
                &self.indirect_buffer,
            );

            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.scene_bind_group, &[]);
            oit_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
            oit_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);

            MeshPass::draw_batches(
                &mut oit_pass,
                &self.transparent_instances,
                self.opaque_instances.len(),
                &self.material_bind_groups,
                &self.indirect_buffer,
            );

            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.scene_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);

            MeshPass::draw_batches(
                &mut overlay_opaque_pass,
                &self.overlay_opaque_instances,
                self.opaque_instances.len() + self.transparent_instances.len(),
                &self.material_bind_groups,
                &self.indirect_buffer,
            );

            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.scene_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);

            MeshPass::draw_batches(
                &mut overlay_oit_pass,
                &self.overlay_transparent_instances,
                self.opaque_instances.len()
                    + self.transparent_instances.len()
                    + self.overlay_opaque_instances.len(),
                &self.material_bind_groups,
                &self.indirect_buffer,
            );

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

        Ok(context.into_sub_graph_commands())
    }
}
