//! wgpu rendering backend.
//!
//! Implements the [`Render`](crate::ecs::world::Render) trait using wgpu, supporting
//! DirectX 12, Metal, Vulkan, and WebGPU backends.
//!
//! Key types:
//!
//! - [`WgpuRenderer`]: Main renderer managing the GPU device, surface, and render graph
//! - [`CameraViewport`]: Off-screen render target for camera output (used by egui viewports)
//! - [`SecondarySurface`]: Surface state for multi-window rendering
//!
//! Submodules:
//!
//! - [`rendergraph`]: Declarative pass-based render graph with automatic resource management
//! - [`passes`]: Built-in geometry, shadow, and post-processing passes
//! - [`texture_cache`]: GPU texture caching and lifecycle management
//! - [`font_atlas`]: SDF font atlas for text rendering
//! - [`sprite_texture_atlas`]: Texture atlas for 2D sprite batching

pub mod brdf_lut;
#[cfg(feature = "egui")]
mod egui_integration;
#[cfg(feature = "assets")]
pub mod envmap_filter;
pub mod font_atlas;
#[cfg(feature = "assets")]
pub mod hdr;
pub mod passes;
pub mod rendergraph;
pub mod sprite_font_atlas;
pub mod sprite_shapes;
pub mod sprite_texture_atlas;
pub mod text_mesh;
pub mod texture_cache;

use crate::ecs::generational_registry::registry_entry_by_name;
use crate::ecs::world::components::Projection;
use texture_cache::texture_cache_load_from_raw_rgba;

#[cfg(feature = "egui")]
pub struct CameraViewport {
    pub texture: wgpu::Texture,
    pub view: wgpu::TextureView,
    pub texture_id: egui::TextureId,
    pub size: (u32, u32),
}

#[cfg(not(feature = "egui"))]
pub struct CameraViewport {
    pub texture: wgpu::Texture,
    pub view: wgpu::TextureView,
    pub size: (u32, u32),
}

#[cfg(feature = "egui")]
type SecondaryCameraViewportCache = std::collections::HashMap<
    usize,
    std::collections::HashMap<freecs::Entity, (egui::TextureId, (u32, u32))>,
>;

const DEPTH_PICK_SAMPLE_SIZE: u32 = 5;

pub struct SecondarySurface {
    pub surface: wgpu::Surface<'static>,
    pub config: wgpu::SurfaceConfiguration,
    pub format: wgpu::TextureFormat,
    pub width: u32,
    pub height: u32,
    #[cfg(feature = "egui")]
    pub egui_renderer: Option<egui_wgpu::Renderer>,
    #[cfg(feature = "egui")]
    pub egui_depth_texture: Option<wgpu::Texture>,
    #[cfg(feature = "egui")]
    pub egui_depth_view: Option<wgpu::TextureView>,
    #[cfg(feature = "egui")]
    pub egui_depth_size: (u32, u32),
}

pub struct WgpuRenderer {
    instance: wgpu::Instance,
    adapter: wgpu::Adapter,
    secondary_surfaces: std::collections::HashMap<usize, SecondarySurface>,
    surface: wgpu::Surface<'static>,
    device: wgpu::Device,
    queue: wgpu::Queue,
    surface_config: wgpu::SurfaceConfiguration,
    surface_format: wgpu::TextureFormat,
    graph: rendergraph::RenderGraph<crate::ecs::world::World>,
    depth_id: rendergraph::ResourceId,
    scene_color_id: rendergraph::ResourceId,
    compute_output_id: rendergraph::ResourceId,
    swapchain_id: rendergraph::ResourceId,
    viewport_resource_id: rendergraph::ResourceId,
    ui_depth_id: rendergraph::ResourceId,
    entity_id_id: rendergraph::ResourceId,
    view_normals_id: rendergraph::ResourceId,
    ssao_raw_id: rendergraph::ResourceId,
    ssao_id: rendergraph::ResourceId,
    ssgi_raw_id: rendergraph::ResourceId,
    ssgi_id: rendergraph::ResourceId,
    ssr_raw_id: rendergraph::ResourceId,
    ssr_id: rendergraph::ResourceId,
    ui_image_pass: Option<Box<passes::UiImagePass>>,
    #[cfg(feature = "egui")]
    egui_pass: Option<Box<passes::EguiPass>>,
    msdf_font_textures: Vec<(wgpu::Texture, wgpu::TextureView)>,
    bitmap_font_textures: Vec<(wgpu::Texture, wgpu::TextureView)>,
    font_data: Vec<crate::ecs::text::resources::FontAtlasData>,
    bitmap_font_data: Vec<crate::ecs::text::resources::FontAtlasData>,
    font_initialized: bool,
    bitmap_atlas_scale: f32,
    bitmap_atlas_subpixel: bool,
    bitmap_font_sources: Vec<(Vec<u8>, f32)>,
    camera_viewports: std::collections::HashMap<freecs::Entity, CameraViewport>,
    _brdf_lut_texture: wgpu::Texture,
    brdf_lut_view: wgpu::TextureView,
    depth_pick_compute_pipeline: wgpu::ComputePipeline,
    depth_pick_bind_group_layout: wgpu::BindGroupLayout,
    depth_pick_storage_buffer: wgpu::Buffer,
    depth_pick_uniform_buffer: wgpu::Buffer,
    depth_pick_staging_buffer: wgpu::Buffer,
    depth_pick_bind_group: Option<wgpu::BindGroup>,
    depth_pick_pending: bool,
    depth_pick_map_complete: std::sync::Arc<std::sync::atomic::AtomicBool>,
    depth_pick_center: (u32, u32),
    #[cfg(not(target_arch = "wasm32"))]
    screenshot_staging_buffer: wgpu::Buffer,
    #[cfg(not(target_arch = "wasm32"))]
    screenshot_pending: bool,
    #[cfg(not(target_arch = "wasm32"))]
    screenshot_map_complete: std::sync::Arc<std::sync::atomic::AtomicBool>,
    #[cfg(not(target_arch = "wasm32"))]
    screenshot_path: Option<std::path::PathBuf>,
    #[cfg(not(target_arch = "wasm32"))]
    screenshot_width: u32,
    #[cfg(not(target_arch = "wasm32"))]
    screenshot_height: u32,
    render_buffer_size: (u32, u32),
    #[cfg(feature = "egui")]
    secondary_camera_viewport_cache: SecondaryCameraViewportCache,
}

type AcquiredSurface = (wgpu::SurfaceTexture, wgpu::TextureView, u32, u32);

enum PassMode {
    World { enable_ui: bool },
    UiOnly,
}

struct SavedUiState {
    did_swap: bool,
    frame_rects: Vec<passes::UiRect>,
    frame_ui_images: Vec<passes::UiImage>,
    frame_text_meshes: Vec<crate::ecs::ui::UiTextInstance>,
    cached_viewport_size: Option<(u32, u32)>,
    render_buffer_size: (u32, u32),
}

impl WgpuRenderer {
    fn cleanup_unused_camera_viewports(&mut self, required_cameras: &[freecs::Entity]) {
        self.camera_viewports
            .retain(|entity, _| required_cameras.contains(entity));
    }

    fn extract_frame_dirty_state(&mut self, world: &mut crate::ecs::world::World) {
        use crate::ecs::prefab::resources::{mesh_cache_take_dirty, mesh_cache_take_dirty_skinned};

        let frame_state = world.resources.mesh_render_state.take_frame_state();
        let dirty_meshes = mesh_cache_take_dirty(&mut world.resources.mesh_cache);
        let dirty_skinned = mesh_cache_take_dirty_skinned(&mut world.resources.mesh_cache);

        if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
            && let Some(pass) =
                (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
        {
            pass.frame_dirty = Some(frame_state);
            pass.dirty_mesh_names = dirty_meshes;
        }

        if let Some(skinned_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
            && let Some(pass) = (skinned_pass as &mut dyn std::any::Any)
                .downcast_mut::<passes::geometry::SkinnedMeshPass>()
        {
            pass.dirty_skinned_mesh_names = dirty_skinned;
        }
    }

    pub async fn new_async<W>(
        window_handle: W,
        initial_width: u32,
        initial_height: u32,
    ) -> Result<Self, Box<dyn std::error::Error>>
    where
        W: Into<wgpu::SurfaceTarget<'static>>,
    {
        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
        let surface = instance.create_surface(window_handle)?;

        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: wgpu::PowerPreference::default(),
                compatible_surface: Some(&surface),
                force_fallback_adapter: false,
            })
            .await
            .expect("Failed to request adapter!");

        let mut required_features = wgpu::Features::INDIRECT_FIRST_INSTANCE;

        if !cfg!(target_os = "macos") {
            required_features |= wgpu::Features::MULTI_DRAW_INDIRECT_COUNT;
        }

        if !cfg!(target_arch = "wasm32") {
            required_features |= wgpu::Features::POLYGON_MODE_LINE;
        }

        let mut required_limits = wgpu::Limits::default().using_resolution(adapter.limits());
        required_limits.max_bind_groups = if cfg!(target_arch = "wasm32") { 4 } else { 8 };

        let (device, queue) = adapter
            .request_device(&wgpu::DeviceDescriptor {
                label: Some("WGPU Device"),
                memory_hints: wgpu::MemoryHints::default(),
                required_features,
                required_limits,
                experimental_features: wgpu::ExperimentalFeatures::disabled(),
                trace: wgpu::Trace::Off,
            })
            .await?;

        let surface_capabilities = surface.get_capabilities(&adapter);

        let surface_format = surface_capabilities
            .formats
            .iter()
            .copied()
            .find(|f| !f.is_srgb())
            .unwrap_or(surface_capabilities.formats[0]);

        let surface_config = wgpu::SurfaceConfiguration {
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            format: surface_format,
            width: initial_width,
            height: initial_height,
            present_mode: surface_capabilities.present_modes[0],
            alpha_mode: surface_capabilities.alpha_modes[0],
            view_formats: vec![],
            desired_maximum_frame_latency: 2,
        };

        surface.configure(&device, &surface_config);

        let depth_format = wgpu::TextureFormat::Depth32Float;
        let hdr_format = wgpu::TextureFormat::Rgba16Float;
        let clear_pass = passes::ClearPass::new();
        let sky_pass = passes::SkyPass::new(&device, hdr_format, depth_format);
        let grid_pass = passes::GridPass::new(&device, hdr_format, depth_format);
        let scene_pass = passes::ScenePass::new(&device, hdr_format, depth_format);

        let shadow_depth_pass = passes::ShadowDepthPass::new(&device, &queue);

        let mesh_pass = passes::MeshPass::new(
            &device,
            &queue,
            hdr_format,
            depth_format,
            (initial_width, initial_height),
        );

        let skinned_mesh_pass =
            passes::SkinnedMeshPass::new(&device, &queue, hdr_format, depth_format);

        let water_pass = passes::WaterPass::new(&device, hdr_format, depth_format);

        let water_mesh_pass = passes::WaterMeshPass::new(&device, hdr_format, depth_format);

        let grass_pass = passes::GrassPass::new(&device, hdr_format, depth_format);

        let lines_pass = passes::LinesPass::new(&device, hdr_format);

        let particle_pass = passes::ParticlePass::new(&device, hdr_format);

        let sprite_pass = passes::SpritePass::new(&device, hdr_format, &queue);

        let mut sprite_particle_pass = passes::SpriteParticlePass::new(&device, hdr_format);
        sprite_particle_pass.set_atlas_bind_group(
            &device,
            sprite_pass.atlas_view(),
            sprite_pass.atlas_sampler(),
        );

        let mut ui_image_pass = passes::UiImagePass::new(&device, surface_format);
        ui_image_pass.set_atlas_bind_group(
            &device,
            sprite_pass.atlas_view(),
            sprite_pass.atlas_sampler(),
        );
        ui_image_pass.set_atlas_slots_per_row(sprite_pass.atlas_slots_per_row());

        let selection_mask_pass = passes::SelectionMaskPass::new(&device, depth_format);

        #[cfg(feature = "egui")]
        let egui_pass = passes::EguiPass::new(&device, surface_format);

        let mut graph = rendergraph::RenderGraph::new();

        let depth_id = graph
            .add_depth_texture("depth")
            .size(initial_width.max(1), initial_height.max(1))
            .usage(
                wgpu::TextureUsages::RENDER_ATTACHMENT
                    | wgpu::TextureUsages::TEXTURE_BINDING
                    | wgpu::TextureUsages::COPY_SRC,
            )
            .clear_depth(0.0)
            .transient();

        let scene_color_id = graph
            .add_color_texture("scene_color")
            .format(wgpu::TextureFormat::Rgba16Float)
            .size(initial_width.max(1), initial_height.max(1))
            .usage(
                wgpu::TextureUsages::RENDER_ATTACHMENT
                    | wgpu::TextureUsages::TEXTURE_BINDING
                    | wgpu::TextureUsages::COPY_SRC,
            )
            .clear_color(wgpu::Color {
                r: 0.0,
                g: 0.0,
                b: 0.0,
                a: 1.0,
            })
            .transient();

        let compute_output_id = graph
            .add_color_texture("compute_output")
            .format(surface_format)
            .size(initial_width.max(1), initial_height.max(1))
            .usage(
                wgpu::TextureUsages::TEXTURE_BINDING
                    | wgpu::TextureUsages::RENDER_ATTACHMENT
                    | wgpu::TextureUsages::COPY_SRC,
            )
            .transient();

        let swapchain_id = graph
            .add_color_texture("swapchain")
            .format(surface_format)
            .external();

        let viewport_resource_id = graph
            .add_color_texture("viewport_output")
            .format(surface_format)
            .external();

        let shadow_map_size = if cfg!(target_arch = "wasm32") {
            4096
        } else {
            8192
        };
        let shadow_depth_id = graph
            .add_depth_texture("shadow_depth")
            .size(shadow_map_size, shadow_map_size)
            .format(wgpu::TextureFormat::Depth32Float)
            .clear_depth(0.0)
            .fixed_size()
            .transient();

        let spotlight_shadow_atlas_size = if cfg!(target_arch = "wasm32") {
            1024
        } else {
            4096
        };
        let spotlight_shadow_atlas_id = graph
            .add_depth_texture("spotlight_shadow_atlas")
            .size(spotlight_shadow_atlas_size, spotlight_shadow_atlas_size)
            .format(wgpu::TextureFormat::Depth32Float)
            .clear_depth(0.0)
            .fixed_size()
            .transient();

        let selection_mask_id = graph
            .add_color_texture("selection_mask")
            .format(wgpu::TextureFormat::R8Unorm)
            .size(initial_width.max(1), initial_height.max(1))
            .clear_color(wgpu::Color::TRANSPARENT)
            .transient();

        let entity_id_id = graph
            .add_color_texture("entity_id")
            .format(wgpu::TextureFormat::R32Float)
            .size(initial_width.max(1), initial_height.max(1))
            .usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
            .clear_color(wgpu::Color::TRANSPARENT)
            .transient();

        let view_normals_id = graph
            .add_color_texture("view_normals")
            .format(wgpu::TextureFormat::Rgba16Float)
            .size(initial_width.max(1), initial_height.max(1))
            .usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
            .clear_color(wgpu::Color::TRANSPARENT)
            .transient();

        let ssao_raw_id = graph
            .add_color_texture("ssao_raw")
            .format(wgpu::TextureFormat::R8Unorm)
            .size(initial_width.max(1), initial_height.max(1))
            .usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
            .clear_color(wgpu::Color::WHITE)
            .transient();

        let ssao_id = graph
            .add_color_texture("ssao")
            .format(wgpu::TextureFormat::R8Unorm)
            .size(initial_width.max(1), initial_height.max(1))
            .usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
            .clear_color(wgpu::Color::WHITE)
            .transient();

        let ssgi_half_width = (initial_width / 2).max(1);
        let ssgi_half_height = (initial_height / 2).max(1);

        let ssgi_raw_id = graph
            .add_color_texture("ssgi_raw")
            .format(wgpu::TextureFormat::Rgba16Float)
            .size(ssgi_half_width, ssgi_half_height)
            .usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
            .clear_color(wgpu::Color::TRANSPARENT)
            .transient();

        let ssgi_id = graph
            .add_color_texture("ssgi")
            .format(wgpu::TextureFormat::Rgba16Float)
            .size(ssgi_half_width, ssgi_half_height)
            .usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
            .clear_color(wgpu::Color::TRANSPARENT)
            .transient();

        let ssr_raw_id = graph
            .add_color_texture("ssr_raw")
            .format(wgpu::TextureFormat::Rgba16Float)
            .size(initial_width.max(1), initial_height.max(1))
            .usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
            .clear_color(wgpu::Color::TRANSPARENT)
            .transient();

        let ssr_id = graph
            .add_color_texture("ssr")
            .format(wgpu::TextureFormat::Rgba16Float)
            .size(initial_width.max(1), initial_height.max(1))
            .usage(wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING)
            .clear_color(wgpu::Color::TRANSPARENT)
            .transient();

        let ui_depth_id = graph
            .add_depth_texture("ui_depth")
            .size(initial_width.max(1), initial_height.max(1))
            .format(wgpu::TextureFormat::Depth32Float)
            .clear_depth(0.0)
            .transient();

        graph.add_pass(
            Box::new(clear_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(Box::new(sky_pass), &[("color", scene_color_id)])?;

        graph.add_pass(
            Box::new(scene_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(
            Box::new(shadow_depth_pass),
            &[
                ("shadow_depth", shadow_depth_id),
                ("spotlight_shadow_atlas", spotlight_shadow_atlas_id),
            ],
        )?;

        graph.add_pass(
            Box::new(mesh_pass),
            &[
                ("color", scene_color_id),
                ("depth", depth_id),
                ("shadow_depth", shadow_depth_id),
                ("spotlight_shadow_atlas", spotlight_shadow_atlas_id),
                ("entity_id", entity_id_id),
                ("view_normals", view_normals_id),
            ],
        )?;

        graph.add_pass(
            Box::new(skinned_mesh_pass),
            &[
                ("color", scene_color_id),
                ("depth", depth_id),
                ("shadow_depth", shadow_depth_id),
                ("spotlight_shadow_atlas", spotlight_shadow_atlas_id),
            ],
        )?;

        graph.add_pass(
            Box::new(water_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(
            Box::new(water_mesh_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(
            Box::new(grass_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(
            Box::new(grid_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(
            Box::new(lines_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(
            Box::new(particle_pass),
            &[("color", scene_color_id), ("depth", depth_id)],
        )?;

        graph.add_pass(Box::new(sprite_pass), &[("color", scene_color_id)])?;

        graph.add_pass(Box::new(sprite_particle_pass), &[("color", scene_color_id)])?;

        graph.add_pass(
            Box::new(selection_mask_pass),
            &[("selection_mask", selection_mask_id), ("depth", depth_id)],
        )?;

        let outline_pass = passes::OutlinePass::new(&device, hdr_format);
        graph.add_pass(
            Box::new(outline_pass),
            &[
                ("selection_mask", selection_mask_id),
                ("color", scene_color_id),
            ],
        )?;

        let (brdf_lut_texture, brdf_lut_view) = brdf_lut::generate_brdf_lut(&device, &queue);

        let depth_pick_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("Depth Pick Shader"),
            source: wgpu::ShaderSource::Wgsl(include_str!("wgpu/shaders/depth_pick.wgsl").into()),
        });

        let depth_pick_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("Depth Pick 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::Buffer {
                            ty: wgpu::BufferBindingType::Storage { read_only: false },
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 2,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Buffer {
                            ty: wgpu::BufferBindingType::Uniform,
                            has_dynamic_offset: false,
                            min_binding_size: None,
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 3,
                        visibility: wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    },
                ],
            });

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

        let depth_pick_compute_pipeline =
            device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
                label: Some("Depth Pick Compute Pipeline"),
                layout: Some(&depth_pick_pipeline_layout),
                module: &depth_pick_shader,
                entry_point: Some("main"),
                compilation_options: Default::default(),
                cache: None,
            });

        let depth_pick_buffer_size = (DEPTH_PICK_SAMPLE_SIZE * DEPTH_PICK_SAMPLE_SIZE * 8) as u64;
        let depth_pick_storage_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Depth Pick Storage Buffer"),
            size: depth_pick_buffer_size,
            usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
            mapped_at_creation: false,
        });

        let depth_pick_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Depth Pick Uniform Buffer"),
            size: 16,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let depth_pick_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Depth Pick Staging Buffer"),
            size: depth_pick_buffer_size,
            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
            mapped_at_creation: false,
        });

        #[cfg(not(target_arch = "wasm32"))]
        let screenshot_staging_buffer = {
            let bytes_per_pixel = 4u32;
            let unpadded_bytes_per_row = initial_width.max(1) * bytes_per_pixel;
            let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
            let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;
            let buffer_size = (padded_bytes_per_row * initial_height.max(1)) as u64;
            device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Screenshot Staging Buffer"),
                size: buffer_size,
                usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
                mapped_at_creation: false,
            })
        };

        Ok(Self {
            instance,
            adapter,
            secondary_surfaces: std::collections::HashMap::new(),
            surface,
            device,
            queue,
            surface_config,
            surface_format,
            graph,
            depth_id,
            scene_color_id,
            compute_output_id,
            swapchain_id,
            viewport_resource_id,
            ui_depth_id,
            entity_id_id,
            view_normals_id,
            ssao_raw_id,
            ssao_id,
            ssgi_raw_id,
            ssgi_id,
            ssr_raw_id,
            ssr_id,
            ui_image_pass: Some(Box::new(ui_image_pass)),
            #[cfg(feature = "egui")]
            egui_pass: Some(Box::new(egui_pass)),
            msdf_font_textures: Vec::new(),
            bitmap_font_textures: Vec::new(),
            font_data: Vec::new(),
            bitmap_font_data: Vec::new(),
            font_initialized: false,
            bitmap_atlas_scale: 1.0,
            bitmap_atlas_subpixel: !cfg!(target_arch = "wasm32"),
            bitmap_font_sources: Vec::new(),
            camera_viewports: std::collections::HashMap::new(),
            _brdf_lut_texture: brdf_lut_texture,
            brdf_lut_view,
            depth_pick_compute_pipeline,
            depth_pick_bind_group_layout,
            depth_pick_storage_buffer,
            depth_pick_uniform_buffer,
            depth_pick_staging_buffer,
            depth_pick_bind_group: None,
            depth_pick_pending: false,
            depth_pick_map_complete: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
            depth_pick_center: (0, 0),
            #[cfg(not(target_arch = "wasm32"))]
            screenshot_staging_buffer,
            #[cfg(not(target_arch = "wasm32"))]
            screenshot_pending: false,
            #[cfg(not(target_arch = "wasm32"))]
            screenshot_map_complete: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
            #[cfg(not(target_arch = "wasm32"))]
            screenshot_path: None,
            #[cfg(not(target_arch = "wasm32"))]
            screenshot_width: initial_width.max(1),
            #[cfg(not(target_arch = "wasm32"))]
            screenshot_height: initial_height.max(1),
            render_buffer_size: (initial_width.max(1), initial_height.max(1)),
            #[cfg(feature = "egui")]
            secondary_camera_viewport_cache: std::collections::HashMap::new(),
        })
    }

    fn resize_render_buffers(&mut self, width: u32, height: u32) {
        if self.render_buffer_size == (width, height) {
            return;
        }

        let width = width.max(1);
        let height = height.max(1);

        let (old_width, old_height) = self.render_buffer_size;
        let _ = self
            .graph
            .resize_all_transient_proportional(old_width, old_height, width, height);

        if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
            && let Some(mesh_pass) =
                (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
        {
            mesh_pass.resize(&self.device, width, height);
        }

        if let Some(bloom_pass) = self.graph.get_pass_mut("bloom_pass")
            && let Some(bloom_pass) =
                (bloom_pass as &mut dyn std::any::Any).downcast_mut::<passes::BloomPass>()
        {
            bloom_pass.resize(&self.device, &self.queue, width, height);
        }

        if let Some(outline_pass) = self.graph.get_pass_mut("outline_pass")
            && let Some(outline_pass) =
                (outline_pass as &mut dyn std::any::Any).downcast_mut::<passes::OutlinePass>()
        {
            outline_pass.resize(width, height);
        }

        self.render_buffer_size = (width, height);
    }

    fn acquire_secondary_surface_texture(
        &self,
        id: usize,
    ) -> Result<Option<AcquiredSurface>, Box<dyn std::error::Error>> {
        let Some(secondary) = self.secondary_surfaces.get(&id) else {
            return Ok(None);
        };
        let width = secondary.width;
        let height = secondary.height;

        let surface_texture = match secondary.surface.get_current_texture() {
            Ok(frame) => frame,
            Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => {
                secondary.surface.configure(&self.device, &secondary.config);
                secondary.surface.get_current_texture()?
            }
            Err(wgpu::SurfaceError::Timeout | wgpu::SurfaceError::Other) => {
                return Ok(None);
            }
            Err(error) => return Err(Box::new(error)),
        };

        let view = surface_texture
            .texture
            .create_view(&wgpu::TextureViewDescriptor::default());

        Ok(Some((surface_texture, view, width, height)))
    }

    fn swap_secondary_ui_buffers(
        &self,
        world: &mut crate::ecs::world::World,
        id: usize,
        width: u32,
        height: u32,
    ) -> SavedUiState {
        let cached_viewport_size = world.resources.window.cached_viewport_size;
        let render_buffer_size = self.render_buffer_size;
        world.resources.window.cached_viewport_size = Some((width, height));

        if let Some(buffers) = world.resources.retained_ui.secondary_buffers.get_mut(&id) {
            let frame_rects = std::mem::replace(
                &mut world.resources.retained_ui.frame_rects,
                std::mem::take(&mut buffers.frame_rects),
            );
            let frame_ui_images = std::mem::replace(
                &mut world.resources.retained_ui.frame_ui_images,
                std::mem::take(&mut buffers.frame_ui_images),
            );
            let frame_text_meshes = std::mem::replace(
                &mut world.resources.retained_ui.frame_text_meshes,
                std::mem::take(&mut buffers.frame_text_meshes),
            );
            SavedUiState {
                did_swap: true,
                frame_rects,
                frame_ui_images,
                frame_text_meshes,
                cached_viewport_size,
                render_buffer_size,
            }
        } else {
            SavedUiState {
                did_swap: false,
                frame_rects: Vec::new(),
                frame_ui_images: Vec::new(),
                frame_text_meshes: Vec::new(),
                cached_viewport_size,
                render_buffer_size,
            }
        }
    }

    fn restore_secondary_ui_buffers(
        &mut self,
        world: &mut crate::ecs::world::World,
        saved: SavedUiState,
    ) {
        if saved.did_swap {
            world.resources.retained_ui.frame_rects = saved.frame_rects;
            world.resources.retained_ui.frame_ui_images = saved.frame_ui_images;
            world.resources.retained_ui.frame_text_meshes = saved.frame_text_meshes;
        }
        world.resources.window.cached_viewport_size = saved.cached_viewport_size;
        self.resize_render_buffers(saved.render_buffer_size.0, saved.render_buffer_size.1);
    }

    fn execute_to_external_target(
        &mut self,
        world: &mut crate::ecs::world::World,
        target_texture: Option<&wgpu::Texture>,
        target_view: &wgpu::TextureView,
        width: u32,
        height: u32,
        pass_mode: PassMode,
    ) -> Result<(), rendergraph::RenderGraphError> {
        let saved_states = self.graph.save_pass_enabled_states();

        let has_culling_override = matches!(pass_mode, PassMode::World { .. })
            && world.resources.graphics.culling_camera_override.is_some();

        if has_culling_override
            && let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
            && let Some(mesh_pass) =
                (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
        {
            mesh_pass.preserve_hiz = true;
        }

        self.resize_render_buffers(width, height);

        if matches!(pass_mode, PassMode::World { .. })
            && let Some(lines_pass) = self.graph.get_pass_mut("lines_pass")
            && let Some(lines_pass) =
                (lines_pass as &mut dyn std::any::Any).downcast_mut::<passes::LinesPass>()
        {
            passes::sync_lines_data(lines_pass, &self.device, &self.queue, world);
        }

        match pass_mode {
            PassMode::World { enable_ui } => {
                let _ = self.graph.set_pass_enabled("egui_pass", false);
                let _ = self.graph.set_pass_enabled("viewport_blit_pass", false);
                if !enable_ui {
                    let _ = self.graph.set_pass_enabled("ui_pass", false);
                    let _ = self.graph.set_pass_enabled("ui_image_pass", false);
                }

                let graphics = &world.resources.graphics;
                let _ = self
                    .graph
                    .set_pass_enabled("bloom_pass", graphics.bloom_enabled);
                let _ = self
                    .graph
                    .set_pass_enabled("ssao_pass", graphics.ssao_enabled);
                let _ = self
                    .graph
                    .set_pass_enabled("ssao_blur_pass", graphics.ssao_enabled);
                let _ = self
                    .graph
                    .set_pass_enabled("ssr_pass", graphics.ssr_enabled);
                let _ = self
                    .graph
                    .set_pass_enabled("ssr_blur_pass", graphics.ssr_enabled);
                let _ = self
                    .graph
                    .set_pass_enabled("ssgi_pass", graphics.ssgi_enabled);
                let _ = self
                    .graph
                    .set_pass_enabled("ssgi_blur_pass", graphics.ssgi_enabled);
            }
            PassMode::UiOnly => {
                for name in saved_states.keys() {
                    let _ = self.graph.set_pass_enabled(name, false);
                }
                let _ = self.graph.set_pass_enabled("ui_pass", true);
                let _ = self.graph.set_pass_enabled("ui_image_pass", true);
            }
        }

        self.graph.set_external_texture(
            self.swapchain_id,
            target_texture.cloned(),
            target_view.clone(),
            width,
            height,
        );

        if matches!(pass_mode, PassMode::World { .. }) {
            self.extract_frame_dirty_state(world);
        }

        let execute_result = self.graph.execute(&self.device, &self.queue, world);
        let execute_error = match execute_result {
            Ok(command_buffers) => {
                self.queue.submit(command_buffers);
                None
            }
            Err(error) => Some(error),
        };

        self.graph.restore_pass_enabled_states(&saved_states);

        if has_culling_override
            && let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
            && let Some(mesh_pass) =
                (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
        {
            mesh_pass.preserve_hiz = false;
        }

        match execute_error {
            Some(error) => Err(error),
            None => Ok(()),
        }
    }

    pub fn render_world_to_texture(
        &mut self,
        world: &mut crate::ecs::world::World,
        target_texture: Option<&wgpu::Texture>,
        target_view: &wgpu::TextureView,
        width: u32,
        height: u32,
    ) -> Result<(), Box<dyn std::error::Error>> {
        self.execute_to_external_target(
            world,
            target_texture,
            target_view,
            width,
            height,
            PassMode::World { enable_ui: false },
        )?;
        Ok(())
    }

    pub fn device(&self) -> &wgpu::Device {
        &self.device
    }

    pub fn queue(&self) -> &wgpu::Queue {
        &self.queue
    }

    pub fn surface_format(&self) -> wgpu::TextureFormat {
        self.surface_format
    }

    pub fn surface_size(&self) -> (u32, u32) {
        (self.surface_config.width, self.surface_config.height)
    }

    fn render_to_secondary_surface(
        &mut self,
        id: usize,
        world: &mut crate::ecs::world::World,
        pass_mode: PassMode,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let Some((surface_texture, view, width, height)) =
            self.acquire_secondary_surface_texture(id)?
        else {
            return Ok(());
        };

        let saved = self.swap_secondary_ui_buffers(world, id, width, height);

        let result = self.execute_to_external_target(
            world,
            Some(&surface_texture.texture),
            &view,
            width,
            height,
            pass_mode,
        );

        self.restore_secondary_ui_buffers(world, saved);

        result?;
        surface_texture.present();
        Ok(())
    }

    pub fn cleanup_world(&mut self, world_id: u64) {
        if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
            && let Some(mesh_pass) =
                (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
        {
            mesh_pass.cleanup_world_state(world_id);
        }
    }

    pub fn cleanup_stale_worlds(&mut self, max_age_frames: u64) {
        if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
            && let Some(mesh_pass) =
                (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
        {
            mesh_pass.cleanup_stale_world_states(max_age_frames);
        }
    }

    fn regenerate_bitmap_atlases(
        &mut self,
        new_scale: f32,
        new_subpixel: bool,
        world: &mut crate::ecs::world::World,
    ) {
        self.bitmap_font_textures.clear();
        self.bitmap_font_data.clear();
        world.resources.text_cache.font_manager.clear_bitmap_fonts();

        for (source_data, base_size) in &self.bitmap_font_sources {
            let atlas = match font_atlas::generate_font_atlas_bitmap(
                &self.device,
                &self.queue,
                source_data,
                *base_size,
                new_scale,
                new_subpixel,
            ) {
                Ok(atlas) => atlas,
                Err(error) => {
                    tracing::error!("Failed to regenerate bitmap font atlas: {}", error);
                    continue;
                }
            };

            self.bitmap_font_textures
                .push((atlas.texture, atlas.texture_view));
            let texture_index = (self.bitmap_font_textures.len() - 1) as u32;

            let atlas_data = crate::ecs::text::resources::FontAtlasData {
                texture_index,
                glyphs: atlas.glyphs,
                kerning: atlas.kerning,
                width: atlas.width,
                height: atlas.height,
                font_size: atlas.font_size,
                sdf_range: atlas.sdf_range,
            };

            self.bitmap_font_data.push(atlas_data.clone());
            world
                .resources
                .text_cache
                .font_manager
                .add_bitmap_font(atlas_data);
        }

        if let Some(ui_pass) = self.graph.get_pass_mut("ui_pass")
            && let Some(ui_pass) =
                (ui_pass as &mut dyn std::any::Any).downcast_mut::<passes::UiPass>()
        {
            ui_pass.update_texture_bind_groups(&self.device, &self.bitmap_font_textures);
        }

        world.resources.retained_ui.text_mesh_cache.clear();

        self.bitmap_atlas_scale = new_scale;
        self.bitmap_atlas_subpixel = new_subpixel;
    }
}

impl crate::ecs::world::Render for WgpuRenderer {
    fn render_frame(
        &mut self,
        world: &mut crate::ecs::world::World,
        ui_output: Option<crate::ecs::world::UiOutput>,
        ui_primitives: Option<crate::ecs::world::UiPrimitives>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        if self.depth_pick_pending {
            let _ = self.device.poll(wgpu::PollType::Poll);

            if self
                .depth_pick_map_complete
                .load(std::sync::atomic::Ordering::Relaxed)
            {
                let buffer_slice = self.depth_pick_staging_buffer.slice(..);
                let data = buffer_slice.get_mapped_range();
                let mut depth_values = Vec::new();
                let mut entity_id_values = Vec::new();
                for chunk in data.chunks_exact(8) {
                    let depth = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
                    let entity_id = u32::from_le_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]);
                    depth_values.push(depth);
                    entity_id_values.push(entity_id);
                }
                drop(data);
                self.depth_pick_staging_buffer.unmap();

                world.resources.gpu_picking.set_depth_samples(
                    depth_values,
                    entity_id_values,
                    DEPTH_PICK_SAMPLE_SIZE,
                    DEPTH_PICK_SAMPLE_SIZE,
                    self.depth_pick_center.0,
                    self.depth_pick_center.1,
                );

                if let Some(camera_entity) = world.resources.active_camera
                    && let Some(camera) = world.get_camera(camera_entity)
                    && let Some(global_transform) = world.get_global_transform(camera_entity)
                {
                    let (viewport_width, viewport_height) = (
                        self.surface_config.width as f32,
                        self.surface_config.height as f32,
                    );
                    let aspect_ratio = viewport_width / viewport_height;
                    let view_matrix = global_transform
                        .0
                        .try_inverse()
                        .unwrap_or_else(nalgebra_glm::Mat4::identity);
                    let projection_matrix = camera.projection.matrix_with_aspect(aspect_ratio);
                    let inverse_view_proj = (projection_matrix * view_matrix)
                        .try_inverse()
                        .unwrap_or_else(nalgebra_glm::Mat4::identity);

                    world.resources.gpu_picking.compute_result(
                        &inverse_view_proj,
                        viewport_width,
                        viewport_height,
                    );
                }

                self.depth_pick_pending = false;
                self.depth_pick_map_complete
                    .store(false, std::sync::atomic::Ordering::Relaxed);
            }
        }

        #[cfg(all(not(target_arch = "wasm32"), feature = "screenshot"))]
        if self.screenshot_pending {
            let _ = self.device.poll(wgpu::PollType::Poll);

            if self
                .screenshot_map_complete
                .load(std::sync::atomic::Ordering::Relaxed)
            {
                let buffer_slice = self.screenshot_staging_buffer.slice(..);
                let data = buffer_slice.get_mapped_range();

                let width = self.screenshot_width;
                let height = self.screenshot_height;
                let bytes_per_pixel = 4u32;
                let unpadded_bytes_per_row = width * bytes_per_pixel;
                let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
                let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;

                let mut pixels = Vec::with_capacity((width * height * bytes_per_pixel) as usize);
                for row in 0..height {
                    let start = (row * padded_bytes_per_row) as usize;
                    let end = start + (unpadded_bytes_per_row) as usize;
                    pixels.extend_from_slice(&data[start..end]);
                }
                drop(data);
                self.screenshot_staging_buffer.unmap();

                let path = self.screenshot_path.take().unwrap_or_else(|| {
                    let screenshots_dir = std::path::PathBuf::from("screenshots");
                    if !screenshots_dir.exists() {
                        let _ = std::fs::create_dir_all(&screenshots_dir);
                    }
                    let timestamp = std::time::SystemTime::now()
                        .duration_since(std::time::UNIX_EPOCH)
                        .map(|d| d.as_secs())
                        .unwrap_or(0);
                    screenshots_dir.join(format!("screenshot_{}.png", timestamp))
                });

                let save_width = width;
                let save_height = height;
                std::thread::spawn(move || {
                    match image::RgbaImage::from_raw(save_width, save_height, pixels) {
                        Some(image) => {
                            if let Err(error) = image.save(&path) {
                                tracing::error!("Failed to save screenshot: {}", error);
                            } else {
                                tracing::info!("Screenshot saved to: {}", path.display());
                            }
                        }
                        None => {
                            tracing::error!("Failed to create image from screenshot data");
                        }
                    }
                });

                self.screenshot_pending = false;
                self.screenshot_map_complete
                    .store(false, std::sync::atomic::Ordering::Relaxed);
            }
        }

        self.resize_render_buffers(self.surface_config.width, self.surface_config.height);

        let mut sprite_commands = Vec::new();
        let mut texture_load_commands = Vec::new();
        let mut texture_reload_commands = Vec::new();
        let mut hdr_skybox_commands = Vec::new();
        let mut procedural_ibl_commands = Vec::new();
        let mut ibl_snapshot_commands = Vec::new();

        for cmd in world.resources.command_queue.iter() {
            match cmd {
                crate::ecs::world::WorldCommand::UploadSpriteTexture {
                    slot,
                    rgba_data,
                    width,
                    height,
                } => {
                    sprite_commands.push((*slot, rgba_data.clone(), *width, *height));
                }
                crate::ecs::world::WorldCommand::LoadTexture {
                    name,
                    rgba_data,
                    width,
                    height,
                } => {
                    texture_load_commands.push((name.clone(), rgba_data.clone(), *width, *height));
                }
                crate::ecs::world::WorldCommand::ReloadTexture {
                    name,
                    rgba_data,
                    width,
                    height,
                } => {
                    texture_reload_commands.push((
                        name.clone(),
                        rgba_data.clone(),
                        *width,
                        *height,
                    ));
                }
                crate::ecs::world::WorldCommand::LoadHdrSkybox { hdr_data } => {
                    hdr_skybox_commands.push(hdr_data.clone());
                }
                crate::ecs::world::WorldCommand::LoadHdrSkyboxFromPath { path } => {
                    match std::fs::read(path) {
                        Ok(bytes) => {
                            tracing::info!("Successfully read HDR file: {}", path.display());
                            hdr_skybox_commands.push(bytes);
                        }
                        Err(e) => {
                            tracing::error!("Failed to read HDR file {}: {}", path.display(), e);
                        }
                    }
                }
                crate::ecs::world::WorldCommand::CaptureProceduralAtmosphereIBL {
                    atmosphere,
                    time,
                } => {
                    procedural_ibl_commands.push((*atmosphere, *time));
                }
                crate::ecs::world::WorldCommand::CaptureIblSnapshots { atmosphere, hours } => {
                    ibl_snapshot_commands.push((*atmosphere, hours.clone()));
                }
                _ => {}
            }
        }

        if !sprite_commands.is_empty() {
            if let Some(sprite_pass) = self.graph.get_pass_mut("sprite_pass")
                && let Some(sprite_pass) =
                    (sprite_pass as &mut dyn std::any::Any).downcast_mut::<passes::SpritePass>()
            {
                for (slot, rgba_data, width, height) in sprite_commands {
                    sprite_pass.upload_texture_to_atlas(
                        &self.queue,
                        slot,
                        &rgba_data,
                        width,
                        height,
                    );
                }
            }

            world.resources.command_queue.retain(|cmd| {
                !matches!(
                    cmd,
                    crate::ecs::world::WorldCommand::UploadSpriteTexture { .. }
                )
            });
        }

        if !texture_load_commands.is_empty() {
            for (name, rgba_data, width, height) in texture_load_commands {
                if let Err(e) = texture_cache_load_from_raw_rgba(
                    &mut world.resources.texture_cache,
                    &self.device,
                    &self.queue,
                    name.clone(),
                    &rgba_data,
                    (width, height),
                ) {
                    tracing::error!("Failed to load texture: {}", e);
                    continue;
                }

                if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
                    && let Some(mesh_pass) =
                        (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
                    && let Some(texture_entry) =
                        registry_entry_by_name(&world.resources.texture_cache.registry, &name)
                {
                    mesh_pass.register_texture_with_data(
                        name.clone(),
                        texture_entry.view.clone(),
                        texture_entry.sampler.clone(),
                        rgba_data.clone(),
                        width,
                        height,
                    );
                }

                if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
                    && let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
                        .downcast_mut::<passes::geometry::SkinnedMeshPass>(
                    )
                    && let Some(texture_entry) =
                        registry_entry_by_name(&world.resources.texture_cache.registry, &name)
                {
                    skinned_mesh_pass.register_texture(
                        name.clone(),
                        texture_entry.view.clone(),
                        texture_entry.sampler.clone(),
                    );
                }

                if let Some(decal_pass) = self.graph.get_pass_mut("decal_pass")
                    && let Some(decal_pass) =
                        (decal_pass as &mut dyn std::any::Any).downcast_mut::<passes::DecalPass>()
                    && let Some(texture_entry) =
                        registry_entry_by_name(&world.resources.texture_cache.registry, &name)
                {
                    decal_pass.register_texture(
                        name,
                        texture_entry.view.clone(),
                        texture_entry.sampler.clone(),
                    );
                }
            }

            world
                .resources
                .command_queue
                .retain(|cmd| !matches!(cmd, crate::ecs::world::WorldCommand::LoadTexture { .. }));
        }

        if !texture_reload_commands.is_empty() {
            use crate::render::wgpu::texture_cache::{TextureReloadResult, texture_cache_reload};

            for (name, rgba_data, width, height) in texture_reload_commands {
                let result = texture_cache_reload(
                    &mut world.resources.texture_cache,
                    &self.device,
                    &self.queue,
                    &name,
                    &rgba_data,
                    width,
                    height,
                );
                if matches!(result, TextureReloadResult::Recreated) {
                    if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
                        && let Some(mesh_pass) =
                            (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
                        && let Some(texture_entry) =
                            registry_entry_by_name(&world.resources.texture_cache.registry, &name)
                    {
                        mesh_pass.register_texture_with_data(
                            name.clone(),
                            texture_entry.view.clone(),
                            texture_entry.sampler.clone(),
                            rgba_data.clone(),
                            width,
                            height,
                        );
                    }

                    if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
                        && let Some(skinned_mesh_pass) = (skinned_mesh_pass
                            as &mut dyn std::any::Any)
                            .downcast_mut::<passes::geometry::SkinnedMeshPass>()
                        && let Some(texture_entry) =
                            registry_entry_by_name(&world.resources.texture_cache.registry, &name)
                    {
                        skinned_mesh_pass.register_texture(
                            name.clone(),
                            texture_entry.view.clone(),
                            texture_entry.sampler.clone(),
                        );
                    }

                    if let Some(decal_pass) = self.graph.get_pass_mut("decal_pass")
                        && let Some(decal_pass) = (decal_pass as &mut dyn std::any::Any)
                            .downcast_mut::<passes::DecalPass>()
                        && let Some(texture_entry) =
                            registry_entry_by_name(&world.resources.texture_cache.registry, &name)
                    {
                        decal_pass.register_texture(
                            name,
                            texture_entry.view.clone(),
                            texture_entry.sampler.clone(),
                        );
                    }
                }
            }

            world.resources.command_queue.retain(|cmd| {
                !matches!(cmd, crate::ecs::world::WorldCommand::ReloadTexture { .. })
            });
        }

        #[cfg(feature = "assets")]
        if !hdr_skybox_commands.is_empty() {
            let mut ibl_views = None;

            if let Some(sky_pass) = self.graph.get_pass_mut("sky_pass")
                && let Some(sky_pass) =
                    (sky_pass as &mut dyn std::any::Any).downcast_mut::<passes::geometry::SkyPass>()
            {
                for hdr_data in hdr_skybox_commands {
                    ibl_views =
                        Some(sky_pass.load_hdr_skybox(&self.device, &self.queue, &hdr_data));
                }
            }

            if let Some((irradiance_view, prefiltered_view)) = ibl_views {
                if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
                    && let Some(mesh_pass) =
                        (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
                {
                    mesh_pass.update_ibl_textures(
                        self.brdf_lut_view.clone(),
                        irradiance_view.clone(),
                        prefiltered_view.clone(),
                    );
                }

                if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
                    && let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
                        .downcast_mut::<passes::geometry::SkinnedMeshPass>(
                    )
                {
                    skinned_mesh_pass.set_ibl_textures(
                        self.brdf_lut_view.clone(),
                        irradiance_view.clone(),
                        prefiltered_view.clone(),
                    );
                }

                world.resources.ibl_views.brdf_lut_view = Some(self.brdf_lut_view.clone());
                world.resources.ibl_views.irradiance_view = Some(irradiance_view);
                world.resources.ibl_views.prefiltered_view = Some(prefiltered_view);
            }

            world.resources.command_queue.retain(|cmd| {
                !matches!(
                    cmd,
                    crate::ecs::world::WorldCommand::LoadHdrSkybox { .. }
                        | crate::ecs::world::WorldCommand::LoadHdrSkyboxFromPath { .. }
                )
            });
        }

        #[cfg(feature = "assets")]
        if !procedural_ibl_commands.is_empty() {
            let mut ibl_views = None;

            if let Some(sky_pass) = self.graph.get_pass_mut("sky_pass")
                && let Some(sky_pass) =
                    (sky_pass as &mut dyn std::any::Any).downcast_mut::<passes::geometry::SkyPass>()
            {
                for (atmosphere, time) in procedural_ibl_commands {
                    ibl_views = sky_pass.capture_procedural_atmosphere(
                        &self.device,
                        &self.queue,
                        atmosphere,
                        time,
                    );
                }
            }

            if let Some((irradiance_view, prefiltered_view)) = ibl_views {
                if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
                    && let Some(mesh_pass) =
                        (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
                {
                    mesh_pass.update_ibl_textures(
                        self.brdf_lut_view.clone(),
                        irradiance_view.clone(),
                        prefiltered_view.clone(),
                    );
                }

                if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
                    && let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
                        .downcast_mut::<passes::geometry::SkinnedMeshPass>(
                    )
                {
                    skinned_mesh_pass.set_ibl_textures(
                        self.brdf_lut_view.clone(),
                        irradiance_view.clone(),
                        prefiltered_view.clone(),
                    );
                }

                world.resources.ibl_views.brdf_lut_view = Some(self.brdf_lut_view.clone());
                world.resources.ibl_views.irradiance_view = Some(irradiance_view);
                world.resources.ibl_views.prefiltered_view = Some(prefiltered_view);
            }

            world.resources.command_queue.retain(|cmd| {
                !matches!(
                    cmd,
                    crate::ecs::world::WorldCommand::CaptureProceduralAtmosphereIBL { .. }
                )
            });
        }

        #[cfg(feature = "assets")]
        if !ibl_snapshot_commands.is_empty() {
            if let Some(sky_pass) = self.graph.get_pass_mut("sky_pass")
                && let Some(sky_pass) =
                    (sky_pass as &mut dyn std::any::Any).downcast_mut::<passes::geometry::SkyPass>()
            {
                for (atmosphere, hours) in ibl_snapshot_commands {
                    sky_pass.capture_ibl_snapshots(&self.device, &self.queue, atmosphere, &hours);
                }
            }

            world.resources.command_queue.retain(|cmd| {
                !matches!(
                    cmd,
                    crate::ecs::world::WorldCommand::CaptureIblSnapshots { .. }
                )
            });
        }

        if let Some(lines_pass) = self.graph.get_pass_mut("lines_pass")
            && let Some(lines_pass) =
                (lines_pass as &mut dyn std::any::Any).downcast_mut::<passes::LinesPass>()
        {
            passes::sync_lines_data(lines_pass, &self.device, &self.queue, world);
            passes::sync_bounding_volume_data(
                lines_pass,
                &self.device,
                &self.queue,
                world,
                world.resources.graphics.show_bounding_volumes,
                world.resources.graphics.show_selected_bounding_volume,
                world.resources.graphics.bounding_volume_selected_entity,
            );
            passes::sync_normal_data(
                lines_pass,
                &self.device,
                &self.queue,
                world,
                world.resources.graphics.show_normals,
                world.resources.graphics.normal_line_length,
                world.resources.graphics.normal_line_color,
            );
        }

        #[cfg(feature = "egui")]
        if let Some(ref ui_output) = ui_output
            && let Some(ref ui_primitives) = ui_primitives
        {
            self.prepare_egui_pass(ui_output, ui_primitives);
        }
        #[cfg(not(feature = "egui"))]
        let _ = (ui_output, ui_primitives);

        let point_shadow_view = self
            .graph
            .get_pass_mut("shadow_depth_pass")
            .and_then(|pass| {
                (pass as &dyn std::any::Any)
                    .downcast_ref::<passes::ShadowDepthPass>()
                    .map(|shadow_pass| shadow_pass.point_light_cubemap_view().clone())
            });

        if let Some(view) = point_shadow_view {
            if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
                && let Some(mesh_pass) =
                    (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
            {
                mesh_pass.update_point_shadow_cubemap(view.clone());
            }
            if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
                && let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
                    .downcast_mut::<passes::SkinnedMeshPass>()
            {
                skinned_mesh_pass.update_point_shadow_cubemap(view);
            }
        }

        {
            let raw_hour = world.resources.graphics.day_night_hour;
            let hour = raw_hour - (raw_hour / 24.0).floor() * 24.0;
            let world_id = world.resources.world_id;
            let mut bracket_views: Option<(
                wgpu::TextureView,
                wgpu::TextureView,
                wgpu::TextureView,
                wgpu::TextureView,
                f32,
            )> = None;

            if let Some(sky_pass) = self.graph.get_pass_mut("sky_pass")
                && let Some(sky_pass) =
                    (sky_pass as &mut dyn std::any::Any).downcast_mut::<passes::geometry::SkyPass>()
                && let Some((index_a, index_b, blend)) =
                    sky_pass.select_ibl_bracket(world.resources.graphics.atmosphere, hour)
            {
                let irr_a =
                    sky_pass.ibl_snapshots[index_a]
                        .1
                        .create_view(&wgpu::TextureViewDescriptor {
                            dimension: Some(wgpu::TextureViewDimension::Cube),
                            ..Default::default()
                        });
                let pref_a =
                    sky_pass.ibl_snapshots[index_a]
                        .2
                        .create_view(&wgpu::TextureViewDescriptor {
                            dimension: Some(wgpu::TextureViewDimension::Cube),
                            ..Default::default()
                        });
                let irr_b =
                    sky_pass.ibl_snapshots[index_b]
                        .1
                        .create_view(&wgpu::TextureViewDescriptor {
                            dimension: Some(wgpu::TextureViewDimension::Cube),
                            ..Default::default()
                        });
                let pref_b =
                    sky_pass.ibl_snapshots[index_b]
                        .2
                        .create_view(&wgpu::TextureViewDescriptor {
                            dimension: Some(wgpu::TextureViewDimension::Cube),
                            ..Default::default()
                        });
                bracket_views = Some((irr_a, pref_a, irr_b, pref_b, blend));
            }

            if let Some((irr_a, pref_a, irr_b, pref_b, blend)) = bracket_views {
                world.resources.graphics.ibl_blend_factor = blend;

                if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
                    && let Some(mesh_pass) =
                        (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
                {
                    mesh_pass.update_ibl_textures_blended(
                        self.brdf_lut_view.clone(),
                        irr_a.clone(),
                        pref_a.clone(),
                        irr_b.clone(),
                        pref_b.clone(),
                        blend,
                    );
                }

                if let Some(skinned_mesh_pass) = self.graph.get_pass_mut("skinned_mesh_pass")
                    && let Some(skinned_mesh_pass) = (skinned_mesh_pass as &mut dyn std::any::Any)
                        .downcast_mut::<passes::geometry::SkinnedMeshPass>(
                    )
                {
                    skinned_mesh_pass.update_ibl_textures_blended_for_world(
                        world_id,
                        passes::geometry::BlendedIblViews {
                            brdf_lut: self.brdf_lut_view.clone(),
                            irradiance_a: irr_a.clone(),
                            prefiltered_a: pref_a.clone(),
                            irradiance_b: irr_b,
                            prefiltered_b: pref_b,
                            blend_factor: blend,
                        },
                    );
                }

                world.resources.ibl_views.brdf_lut_view = Some(self.brdf_lut_view.clone());
                world.resources.ibl_views.irradiance_view = Some(irr_a);
                world.resources.ibl_views.prefiltered_view = Some(pref_a);
            }
        }

        let surface_texture = match self.surface.get_current_texture() {
            Ok(frame) => frame,
            Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => {
                self.surface.configure(&self.device, &self.surface_config);
                self.surface.get_current_texture()?
            }
            Err(wgpu::SurfaceError::Timeout | wgpu::SurfaceError::Other) => {
                return Ok(());
            }
            Err(e) => return Err(Box::new(e)),
        };

        let surface_texture_view =
            surface_texture
                .texture
                .create_view(&wgpu::TextureViewDescriptor {
                    label: wgpu::Label::default(),
                    aspect: wgpu::TextureAspect::default(),
                    format: Some(self.surface_format),
                    dimension: None,
                    base_mip_level: 0,
                    mip_level_count: None,
                    base_array_layer: 0,
                    array_layer_count: None,
                    usage: None,
                });

        self.graph.set_external_texture(
            self.swapchain_id,
            Some(surface_texture.texture.clone()),
            surface_texture_view,
            self.surface_config.width,
            self.surface_config.height,
        );

        let required_cameras = world.resources.user_interface.required_cameras.clone();
        let required_camera_sizes = world.resources.user_interface.required_camera_sizes.clone();
        self.cleanup_unused_camera_viewports(&required_cameras);

        #[cfg(feature = "egui")]
        {
            world.resources.user_interface.viewport_textures.clear();
            world
                .resources
                .user_interface
                .viewport_texture_sizes
                .clear();
        }

        let has_viewports = !required_cameras.is_empty();
        let _ = self
            .graph
            .set_pass_enabled("viewport_blit_pass", has_viewports);

        if !has_viewports {
            self.resize_render_buffers(self.surface_config.width, self.surface_config.height);
        }

        let graphics = &world.resources.graphics;
        let _ = self
            .graph
            .set_pass_enabled("bloom_pass", graphics.bloom_enabled);
        let _ = self
            .graph
            .set_pass_enabled("ssao_pass", graphics.ssao_enabled);
        let _ = self
            .graph
            .set_pass_enabled("ssao_blur_pass", graphics.ssao_enabled);
        let _ = self
            .graph
            .set_pass_enabled("ssr_pass", graphics.ssr_enabled);
        let _ = self
            .graph
            .set_pass_enabled("ssr_blur_pass", graphics.ssr_enabled);
        let _ = self
            .graph
            .set_pass_enabled("ssgi_pass", graphics.ssgi_enabled);
        let _ = self
            .graph
            .set_pass_enabled("ssgi_blur_pass", graphics.ssgi_enabled);

        let original_active_camera = world.resources.active_camera;

        if has_viewports {
            let _ = self.graph.set_pass_enabled("egui_pass", false);
            let _ = self.graph.set_pass_enabled("ui_pass", false);
            let _ = self.graph.set_pass_enabled("ui_image_pass", false);
            let _ = self.graph.set_pass_enabled("swapchain_blit_pass", false);
        }

        for (camera_index, camera_entity) in required_cameras.iter().enumerate() {
            let (viewport_width, viewport_height) = required_camera_sizes
                .get(camera_index)
                .copied()
                .filter(|&(width, height)| width > 0 && height > 0)
                .unwrap_or((self.surface_config.width, self.surface_config.height));

            #[cfg(feature = "egui")]
            self.ensure_camera_viewport(*camera_entity, viewport_width, viewport_height);

            if let Some(camera) = world.get_camera_mut(*camera_entity)
                && let Projection::Perspective(perspective) = &mut camera.projection
            {
                perspective.aspect_ratio =
                    Some(viewport_width as f32 / viewport_height.max(1) as f32);
            }

            let viewport_cloned = self
                .camera_viewports
                .get(camera_entity)
                .map(|viewport| (viewport.texture.clone(), viewport.view.clone()));

            if let Some((texture, view)) = viewport_cloned {
                self.graph.set_external_texture(
                    self.viewport_resource_id,
                    Some(texture),
                    view,
                    viewport_width,
                    viewport_height,
                );

                world.resources.active_camera = Some(*camera_entity);
                self.resize_render_buffers(viewport_width, viewport_height);
                let original_cached_size = world.resources.window.cached_viewport_size;
                world.resources.window.cached_viewport_size =
                    Some((viewport_width, viewport_height));

                self.extract_frame_dirty_state(world);
                let command_buffers = self.graph.execute(&self.device, &self.queue, world)?;
                self.queue.submit(command_buffers);

                world.resources.window.cached_viewport_size = original_cached_size;

                #[cfg(feature = "egui")]
                if let Some(viewport) = self.camera_viewports.get(camera_entity) {
                    world
                        .resources
                        .user_interface
                        .viewport_textures
                        .push(viewport.texture_id);
                    world
                        .resources
                        .user_interface
                        .viewport_texture_sizes
                        .push(viewport.size);
                }
            }
        }

        world.resources.active_camera = original_active_camera;

        let saved_pass_states = if has_viewports {
            let saved = self.graph.save_pass_enabled_states();
            for name in saved.keys() {
                if name != "egui_pass" && name != "ui_pass" && name != "ui_image_pass" {
                    let _ = self.graph.set_pass_enabled(name, false);
                }
            }
            let _ = self.graph.set_pass_enabled("egui_pass", true);
            let _ = self.graph.set_pass_enabled("ui_pass", true);
            let _ = self.graph.set_pass_enabled("ui_image_pass", true);
            Some(saved)
        } else {
            None
        };

        self.extract_frame_dirty_state(world);
        let command_buffers = self.graph.execute(&self.device, &self.queue, world)?;
        self.queue.submit(command_buffers);

        if let Some(saved) = &saved_pass_states {
            self.graph.restore_pass_enabled_states(saved);
        }

        #[cfg(not(target_arch = "wasm32"))]
        {
            let screenshot_command = world.resources.command_queue.iter().find_map(|cmd| {
                if let crate::ecs::world::WorldCommand::CaptureScreenshot { path } = cmd {
                    Some(path.clone())
                } else {
                    None
                }
            });

            if !self.screenshot_pending
                && let Some(path) = screenshot_command
                && let Some(compute_output_texture) = self.graph.get_texture(self.compute_output_id)
            {
                world.resources.command_queue.retain(|cmd| {
                    !matches!(
                        cmd,
                        crate::ecs::world::WorldCommand::CaptureScreenshot { .. }
                    )
                });

                let width = self.surface_config.width;
                let height = self.surface_config.height;
                let bytes_per_pixel = 4u32;
                let unpadded_bytes_per_row = width * bytes_per_pixel;
                let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
                let padded_bytes_per_row = unpadded_bytes_per_row.div_ceil(align) * align;

                let required_buffer_size = (padded_bytes_per_row * height) as u64;
                if self.screenshot_staging_buffer.size() < required_buffer_size {
                    self.screenshot_staging_buffer =
                        self.device.create_buffer(&wgpu::BufferDescriptor {
                            label: Some("Screenshot Staging Buffer"),
                            size: required_buffer_size,
                            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
                            mapped_at_creation: false,
                        });
                }

                let mut encoder =
                    self.device
                        .create_command_encoder(&wgpu::CommandEncoderDescriptor {
                            label: Some("Screenshot Encoder"),
                        });

                encoder.copy_texture_to_buffer(
                    wgpu::TexelCopyTextureInfo {
                        texture: compute_output_texture,
                        mip_level: 0,
                        origin: wgpu::Origin3d::ZERO,
                        aspect: wgpu::TextureAspect::All,
                    },
                    wgpu::TexelCopyBufferInfo {
                        buffer: &self.screenshot_staging_buffer,
                        layout: wgpu::TexelCopyBufferLayout {
                            offset: 0,
                            bytes_per_row: Some(padded_bytes_per_row),
                            rows_per_image: Some(height),
                        },
                    },
                    wgpu::Extent3d {
                        width,
                        height,
                        depth_or_array_layers: 1,
                    },
                );

                self.queue.submit(std::iter::once(encoder.finish()));

                self.screenshot_path = path;
                self.screenshot_width = width;
                self.screenshot_height = height;
                self.screenshot_pending = true;

                let map_complete = self.screenshot_map_complete.clone();
                self.screenshot_staging_buffer.slice(..).map_async(
                    wgpu::MapMode::Read,
                    move |_| {
                        map_complete.store(true, std::sync::atomic::Ordering::Relaxed);
                    },
                );
            }
        }

        if !self.depth_pick_pending
            && let Some(request) = world.resources.gpu_picking.take_pending_request()
            && let Some(depth_texture_view) = self.graph.get_texture_view(self.depth_id)
            && let Some(entity_id_texture_view) = self.graph.get_texture_view(self.entity_id_id)
        {
            let uniform_data: [u32; 4] = [
                request.screen_x,
                request.screen_y,
                DEPTH_PICK_SAMPLE_SIZE,
                0,
            ];
            self.queue.write_buffer(
                &self.depth_pick_uniform_buffer,
                0,
                bytemuck::cast_slice(&uniform_data),
            );

            let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Depth Pick Bind Group"),
                layout: &self.depth_pick_bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(depth_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: self.depth_pick_storage_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 2,
                        resource: self.depth_pick_uniform_buffer.as_entire_binding(),
                    },
                    wgpu::BindGroupEntry {
                        binding: 3,
                        resource: wgpu::BindingResource::TextureView(entity_id_texture_view),
                    },
                ],
            });

            let mut encoder = self
                .device
                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
                    label: Some("Depth Pick Encoder"),
                });

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

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

            encoder.copy_buffer_to_buffer(
                &self.depth_pick_storage_buffer,
                0,
                &self.depth_pick_staging_buffer,
                0,
                (DEPTH_PICK_SAMPLE_SIZE * DEPTH_PICK_SAMPLE_SIZE * 8) as u64,
            );

            self.queue.submit(std::iter::once(encoder.finish()));
            self.depth_pick_bind_group = Some(bind_group);
            self.depth_pick_pending = true;
            self.depth_pick_center = (request.screen_x, request.screen_y);

            let map_complete = self.depth_pick_map_complete.clone();
            self.depth_pick_staging_buffer
                .slice(..)
                .map_async(wgpu::MapMode::Read, move |_| {
                    map_complete.store(true, std::sync::atomic::Ordering::Relaxed);
                });
        }

        surface_texture.present();

        Ok(())
    }

    fn resize_surface(
        &mut self,
        width: u32,
        height: u32,
    ) -> Result<(), Box<dyn std::error::Error>> {
        if width == 0 || height == 0 {
            return Ok(());
        }

        self.surface_config.width = width;
        self.surface_config.height = height;
        self.surface.configure(&self.device, &self.surface_config);
        self.resize_render_buffers(width, height);

        Ok(())
    }

    fn surface_size(&self) -> (u32, u32) {
        (self.surface_config.width, self.surface_config.height)
    }

    fn configure_with_state(
        &mut self,
        state: &mut dyn crate::run::State,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let msdf_atlas = font_atlas::generate_font_atlas_msdf(
            &self.device,
            &self.queue,
            font_atlas::DEFAULT_FONT_DATA,
            32.0,
        )?;

        self.msdf_font_textures
            .push((msdf_atlas.texture, msdf_atlas.texture_view));
        let msdf_texture_index = (self.msdf_font_textures.len() - 1) as u32;

        let msdf_font_data = crate::ecs::text::resources::FontAtlasData {
            texture_index: msdf_texture_index,
            glyphs: msdf_atlas.glyphs,
            kerning: msdf_atlas.kerning.clone(),
            width: msdf_atlas.width,
            height: msdf_atlas.height,
            font_size: msdf_atlas.font_size,
            sdf_range: msdf_atlas.sdf_range,
        };
        self.font_data.push(msdf_font_data);

        for base_size in [18.0_f32, 28.0] {
            let bitmap_atlas = font_atlas::generate_font_atlas_bitmap(
                &self.device,
                &self.queue,
                font_atlas::DEFAULT_FONT_DATA,
                base_size,
                1.0,
                self.bitmap_atlas_subpixel,
            )?;

            self.bitmap_font_sources
                .push((font_atlas::DEFAULT_FONT_DATA.to_vec(), base_size));

            self.bitmap_font_textures
                .push((bitmap_atlas.texture, bitmap_atlas.texture_view));
            let bitmap_texture_index = (self.bitmap_font_textures.len() - 1) as u32;

            let bitmap_font_data = crate::ecs::text::resources::FontAtlasData {
                texture_index: bitmap_texture_index,
                glyphs: bitmap_atlas.glyphs,
                kerning: bitmap_atlas.kerning,
                width: bitmap_atlas.width,
                height: bitmap_atlas.height,
                font_size: bitmap_atlas.font_size,
                sdf_range: bitmap_atlas.sdf_range,
            };
            self.bitmap_font_data.push(bitmap_font_data);
        }

        let mut text_pass = passes::TextPass::new(
            &self.device,
            wgpu::TextureFormat::Rgba16Float,
            wgpu::TextureFormat::Depth32Float,
        );

        text_pass.update_texture_bind_groups(&self.device, &self.msdf_font_textures);

        self.graph.add_pass(
            Box::new(text_pass),
            &[("color", self.scene_color_id), ("depth", self.depth_id)],
        )?;

        state.configure_render_graph(
            &mut self.graph,
            &self.device,
            self.surface_format,
            crate::run::RenderResources {
                scene_color: self.scene_color_id,
                depth: self.depth_id,
                compute_output: self.compute_output_id,
                swapchain: self.swapchain_id,
                view_normals: self.view_normals_id,
                ssao_raw: self.ssao_raw_id,
                ssao: self.ssao_id,
                ssgi_raw: self.ssgi_raw_id,
                ssgi: self.ssgi_id,
                ssr_raw: self.ssr_raw_id,
                ssr: self.ssr_id,
                surface_width: self.surface_config.width,
                surface_height: self.surface_config.height,
            },
        );

        let viewport_blit_pass = passes::BlitPass::new(&self.device, self.surface_format)
            .with_name("viewport_blit_pass");
        self.graph.add_pass(
            Box::new(viewport_blit_pass),
            &[
                ("input", self.compute_output_id),
                ("output", self.viewport_resource_id),
            ],
        )?;

        if let Some(ui_image_pass) = self.ui_image_pass.take() {
            self.graph.add_pass(
                ui_image_pass,
                &[("color", self.swapchain_id), ("depth", self.ui_depth_id)],
            )?;
        }

        let mut ui_pass = passes::UiPass::new(&self.device, self.surface_format);
        ui_pass.update_texture_bind_groups(&self.device, &self.bitmap_font_textures);
        self.graph.add_pass(
            Box::new(ui_pass),
            &[("color", self.swapchain_id), ("depth", self.ui_depth_id)],
        )?;

        #[cfg(feature = "egui")]
        if let Some(egui_pass) = self.egui_pass.take() {
            self.graph
                .add_pass(egui_pass, &[("swapchain", self.swapchain_id)])?;
        }

        self.graph.compile()?;
        Ok(())
    }

    fn initialize_fonts(&mut self, world: &mut crate::ecs::world::World) {
        if !self.font_initialized {
            world.resources.retained_ui.subpixel_text_rendering = !cfg!(target_arch = "wasm32");

            for font_data in &self.font_data {
                let font_index = world
                    .resources
                    .text_cache
                    .font_manager
                    .add_font(font_data.clone());
                if world.resources.text_cache.font_manager.default_font == 0 {
                    world.resources.text_cache.font_manager.default_font = font_index;
                }
                tracing::info!("Initialized MSDF font at index {}", font_index);
            }
            for bitmap_data in &self.bitmap_font_data {
                let bitmap_index = world
                    .resources
                    .text_cache
                    .font_manager
                    .add_bitmap_font(bitmap_data.clone());
                tracing::info!("Initialized bitmap font at index {}", bitmap_index);
            }
            self.font_initialized = true;
        }
    }

    fn update_with_state(
        &mut self,
        state: &mut dyn crate::run::State,
        world: &mut crate::ecs::world::World,
    ) -> Result<(), Box<dyn std::error::Error>> {
        self.initialize_fonts(world);

        for pending_load in world.resources.pending_font_loads.drain(..) {
            let msdf_atlas = match font_atlas::generate_font_atlas_msdf(
                &self.device,
                &self.queue,
                &pending_load.font_data,
                pending_load.font_size,
            ) {
                Ok(atlas) => atlas,
                Err(e) => {
                    tracing::error!("Failed to load MSDF font: {}", e);
                    continue;
                }
            };

            self.msdf_font_textures
                .push((msdf_atlas.texture, msdf_atlas.texture_view));
            let msdf_texture_index = (self.msdf_font_textures.len() - 1) as u32;

            let msdf_font_data = crate::ecs::text::resources::FontAtlasData {
                texture_index: msdf_texture_index,
                glyphs: msdf_atlas.glyphs,
                kerning: msdf_atlas.kerning.clone(),
                width: msdf_atlas.width,
                height: msdf_atlas.height,
                font_size: msdf_atlas.font_size,
                sdf_range: msdf_atlas.sdf_range,
            };

            let font_index = world
                .resources
                .text_cache
                .font_manager
                .add_font(msdf_font_data);

            let mut bitmap_load_failed = false;
            for base_size in [pending_load.font_size, 28.0_f32] {
                let bitmap_atlas = match font_atlas::generate_font_atlas_bitmap(
                    &self.device,
                    &self.queue,
                    &pending_load.font_data,
                    base_size,
                    self.bitmap_atlas_scale,
                    self.bitmap_atlas_subpixel,
                ) {
                    Ok(atlas) => atlas,
                    Err(e) => {
                        tracing::error!("Failed to load bitmap font: {}", e);
                        bitmap_load_failed = true;
                        break;
                    }
                };

                self.bitmap_font_sources
                    .push((pending_load.font_data.clone(), base_size));

                self.bitmap_font_textures
                    .push((bitmap_atlas.texture, bitmap_atlas.texture_view));
                let bitmap_texture_index = (self.bitmap_font_textures.len() - 1) as u32;

                let bitmap_font_data = crate::ecs::text::resources::FontAtlasData {
                    texture_index: bitmap_texture_index,
                    glyphs: bitmap_atlas.glyphs,
                    kerning: bitmap_atlas.kerning,
                    width: bitmap_atlas.width,
                    height: bitmap_atlas.height,
                    font_size: bitmap_atlas.font_size,
                    sdf_range: bitmap_atlas.sdf_range,
                };

                world
                    .resources
                    .text_cache
                    .font_manager
                    .add_bitmap_font(bitmap_font_data);
            }
            if bitmap_load_failed {
                continue;
            }

            if let Some(text_pass) = self.graph.get_pass_mut("text_pass")
                && let Some(text_pass) =
                    (text_pass as &mut dyn std::any::Any).downcast_mut::<passes::TextPass>()
            {
                text_pass.update_texture_bind_groups(&self.device, &self.msdf_font_textures);
            }

            if let Some(ui_pass) = self.graph.get_pass_mut("ui_pass")
                && let Some(ui_pass) =
                    (ui_pass as &mut dyn std::any::Any).downcast_mut::<passes::UiPass>()
            {
                ui_pass.update_texture_bind_groups(&self.device, &self.bitmap_font_textures);
            }

            tracing::info!("Loaded custom font at index {}", font_index);
        }

        let new_scale = world.resources.window.cached_scale_factor;
        let new_subpixel = world.resources.retained_ui.subpixel_text_rendering;
        if (new_scale - self.bitmap_atlas_scale).abs() > f32::EPSILON
            || new_subpixel != self.bitmap_atlas_subpixel
        {
            self.regenerate_bitmap_atlases(new_scale, new_subpixel, world);
        }

        state.update_render_graph(&mut self.graph, world);
        Ok(())
    }

    fn copy_fonts_to_world(&self, world: &mut crate::ecs::world::World) {
        if world.resources.text_cache.font_manager.font_count() == 0 {
            for font_data in &self.font_data {
                let font_index = world
                    .resources
                    .text_cache
                    .font_manager
                    .add_font(font_data.clone());
                if world.resources.text_cache.font_manager.default_font == 0 {
                    world.resources.text_cache.font_manager.default_font = font_index;
                }
            }
            for bitmap_data in &self.bitmap_font_data {
                world
                    .resources
                    .text_cache
                    .font_manager
                    .add_bitmap_font(bitmap_data.clone());
            }
        }
    }

    fn render_world_to_texture(
        &mut self,
        world: &mut crate::ecs::world::World,
        target_texture: Option<&wgpu::Texture>,
        target_view: &wgpu::TextureView,
        width: u32,
        height: u32,
    ) -> Result<(), Box<dyn std::error::Error>> {
        WgpuRenderer::render_world_to_texture(
            self,
            world,
            target_texture,
            target_view,
            width,
            height,
        )
    }

    fn device(&self) -> &wgpu::Device {
        &self.device
    }

    fn queue(&self) -> &wgpu::Queue {
        &self.queue
    }

    fn surface_format(&self) -> wgpu::TextureFormat {
        self.surface_format
    }

    fn register_render_texture(&mut self, name: &str, view: wgpu::TextureView) {
        let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some(&format!("{}_sampler", name)),
            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()
        });

        if let Some(mesh_pass) = self.graph.get_pass_mut("mesh_pass")
            && let Some(mesh_pass) =
                (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
        {
            mesh_pass.register_texture(name.to_string(), view, sampler);
        }
    }

    fn create_secondary_surface(
        &mut self,
        id: usize,
        window: std::sync::Arc<winit::window::Window>,
        width: u32,
        height: u32,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let surface = self.instance.create_surface(window)?;
        let capabilities = surface.get_capabilities(&self.adapter);
        let format = capabilities
            .formats
            .iter()
            .copied()
            .find(|f| !f.is_srgb())
            .unwrap_or(capabilities.formats[0]);
        let config = wgpu::SurfaceConfiguration {
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            format,
            width: width.max(1),
            height: height.max(1),
            present_mode: capabilities.present_modes[0],
            alpha_mode: capabilities.alpha_modes[0],
            view_formats: vec![],
            desired_maximum_frame_latency: 2,
        };
        surface.configure(&self.device, &config);
        self.secondary_surfaces.insert(
            id,
            SecondarySurface {
                surface,
                config,
                format,
                width: width.max(1),
                height: height.max(1),
                #[cfg(feature = "egui")]
                egui_renderer: None,
                #[cfg(feature = "egui")]
                egui_depth_texture: None,
                #[cfg(feature = "egui")]
                egui_depth_view: None,
                #[cfg(feature = "egui")]
                egui_depth_size: (0, 0),
            },
        );
        Ok(())
    }

    fn remove_secondary_surface(&mut self, id: usize) {
        self.secondary_surfaces.remove(&id);
        #[cfg(feature = "egui")]
        self.secondary_camera_viewport_cache.remove(&id);
    }

    fn resize_secondary_surface(
        &mut self,
        id: usize,
        width: u32,
        height: u32,
    ) -> Result<(), Box<dyn std::error::Error>> {
        if width == 0 || height == 0 {
            return Ok(());
        }
        if let Some(secondary) = self.secondary_surfaces.get_mut(&id) {
            secondary.config.width = width;
            secondary.config.height = height;
            secondary.width = width;
            secondary.height = height;
            secondary.surface.configure(&self.device, &secondary.config);
        }
        Ok(())
    }

    fn render_world_to_secondary_surface(
        &mut self,
        id: usize,
        world: &mut crate::ecs::world::World,
    ) -> Result<(), Box<dyn std::error::Error>> {
        WgpuRenderer::render_to_secondary_surface(
            self,
            id,
            world,
            PassMode::World { enable_ui: true },
        )
    }

    fn render_retained_ui_to_secondary_surface(
        &mut self,
        id: usize,
        world: &mut crate::ecs::world::World,
    ) -> Result<(), Box<dyn std::error::Error>> {
        WgpuRenderer::render_to_secondary_surface(self, id, world, PassMode::UiOnly)
    }

    fn secondary_surface_size(&self, id: usize) -> Option<(u32, u32)> {
        self.secondary_surfaces
            .get(&id)
            .map(|secondary| (secondary.width, secondary.height))
    }

    fn initialize_secondary_egui(&mut self, id: usize) {
        #[cfg(not(feature = "egui"))]
        let _ = id;
        #[cfg(feature = "egui")]
        self.initialize_secondary_egui_impl(id);
    }

    fn render_egui_to_secondary_surface(
        &mut self,
        id: usize,
        ui_output: Option<crate::ecs::world::UiOutput>,
        ui_primitives: Option<crate::ecs::world::UiPrimitives>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        #[cfg(feature = "egui")]
        return self.render_egui_to_secondary_surface_impl(id, ui_output, ui_primitives);
        #[cfg(not(feature = "egui"))]
        {
            let _ = (id, ui_output, ui_primitives);
            Ok(())
        }
    }
}

pub async fn create_wgpu_renderer<W>(
    window_handle: W,
    initial_width: u32,
    initial_height: u32,
) -> Result<WgpuRenderer, Box<dyn std::error::Error>>
where
    W: Into<wgpu::SurfaceTarget<'static>>,
{
    WgpuRenderer::new_async(window_handle, initial_width, initial_height).await
}
