nightshade 0.36.1

A cross-platform data-oriented game engine.
Documentation
//! wgpu rendering backend.
//!
//! The [`WgpuRenderer`] supports 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
//!
//! 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
//! - [`glyph_atlas`]: dynamic glyph atlas backed by cosmic-text + swash

pub mod brdf_lut;
mod camera_viewport;
#[cfg(feature = "assets")]
pub mod envmap_filter;
mod execution;
mod frame;
pub mod glyph_atlas;
#[cfg(feature = "assets")]
pub mod hdr;
mod initialization;
pub mod material_texture_arrays;
pub mod mip_generator;
pub mod passes;
pub mod rendergraph;
pub mod shader_compose;
mod stagger;
pub mod text_mesh;
pub mod texture_cache;
pub mod ui_texture_array;

#[derive(Default, Clone)]
pub struct WindowRenderState {
    /// Every distinct buffer size this window has dispatched at this
    /// frame (or recently). The cache lets multi-camera-per-window
    /// dispatches early-return when the shared graph is already at the
    /// requested size for *any* of this window's cameras, instead of
    /// always falling through to the per-pass texture pool.
    pub recent_buffer_sizes: Vec<(u32, u32)>,
    pub last_settings_version: Option<u64>,
}

const RECENT_BUFFER_SIZE_CAPACITY: usize = 8;

pub struct CameraViewport {
    pub texture: wgpu::Texture,
    pub view: wgpu::TextureView,
    pub size: (u32, u32),
    pub has_rendered_at_least_once: bool,
    pub last_active_view: Option<crate::ecs::camera::components::EffectiveShading>,
    pub last_settings_version: u64,
    pub last_render_frame: u64,
    pub last_camera_world_transform: Option<nalgebra_glm::Mat4>,
}

const DEPTH_PICK_SAMPLE_SIZE: u32 = 5;

pub struct WgpuRenderer {
    surface: wgpu::Surface<'static>,
    device: wgpu::Device,
    queue: wgpu::Queue,
    surface_config: wgpu::SurfaceConfiguration,
    surface_format: wgpu::TextureFormat,
    supported_present_modes: Vec<wgpu::PresentMode>,
    graph: rendergraph::RenderGraph<crate::ecs::world::World>,
    /// Render-graph resource handles for the built-in pipeline targets.
    targets: RenderTargets,
    /// Persistent spotlight shadow atlas, kept across frames so cached shadow
    /// slots survive. Fed to the render graph as an external resource each frame.
    spotlight_shadow_atlas_texture: wgpu::Texture,
    spotlight_shadow_atlas_view: wgpu::TextureView,
    ui_image_pass: Option<Box<passes::UiImagePass>>,
    pub(super) glyph_atlas: glyph_atlas::GlyphAtlas,
    /// Per-camera viewport texture pool.
    camera_viewports: std::collections::HashMap<freecs::Entity, CameraViewport>,
    _brdf_lut_texture: wgpu::Texture,
    brdf_lut_view: wgpu::TextureView,
    material_texture_arrays: material_texture_arrays::MaterialTextureArrays,
    pub(super) ui_texture_array: ui_texture_array::UiTextureArray,
    mip_generator: mip_generator::MipGenerator,
    /// GPU depth-pick readback state.
    depth_pick: DepthPickState,
    /// Screenshot readback state.
    #[cfg(not(target_arch = "wasm32"))]
    screenshot: ScreenshotState,
    render_buffer_size: (u32, u32),
    /// Render-graph state. Tracks the last buffer size used by the
    /// renderer's dispatch so consecutive dispatches don't
    /// pessimistically resize when the size is already current.
    window_render_state: WindowRenderState,
    /// Per-frame and dedup bookkeeping.
    frame_state: FrameState,
    /// The adapter the renderer selected, published into `RendererState` on the
    /// first frame so game code can adapt quality and controls to the device.
    gpu_profile: crate::ecs::graphics::resources::GpuProfile,
}

/// Render-graph resource handles for the built-in pipeline targets.
struct RenderTargets {
    depth: rendergraph::ResourceId,
    scene_color: rendergraph::ResourceId,
    compute_output: rendergraph::ResourceId,
    fxaa_output: rendergraph::ResourceId,
    swapchain: rendergraph::ResourceId,
    viewport_resource: rendergraph::ResourceId,
    ui_depth: rendergraph::ResourceId,
    entity_id: rendergraph::ResourceId,
    view_normals: rendergraph::ResourceId,
    ssao_raw: rendergraph::ResourceId,
    ssao: rendergraph::ResourceId,
    ssgi_raw: rendergraph::ResourceId,
    ssgi: rendergraph::ResourceId,
    ssr_raw: rendergraph::ResourceId,
    ssr: rendergraph::ResourceId,
    spotlight_shadow_atlas: rendergraph::ResourceId,
}

/// GPU depth-pick compute and readback resources.
struct DepthPickState {
    compute_pipeline: wgpu::ComputePipeline,
    bind_group_layout: wgpu::BindGroupLayout,
    storage_buffer: wgpu::Buffer,
    uniform_buffer: wgpu::Buffer,
    staging_buffer: wgpu::Buffer,
    bind_group: Option<wgpu::BindGroup>,
    pending: bool,
    map_complete: std::sync::Arc<std::sync::atomic::AtomicBool>,
    center: (u32, u32),
    texture_size: (u32, u32),
    camera: Option<freecs::Entity>,
}

/// Screenshot capture and readback resources.
#[cfg(not(target_arch = "wasm32"))]
struct ScreenshotState {
    staging_buffer: wgpu::Buffer,
    pending: bool,
    map_complete: std::sync::Arc<std::sync::atomic::AtomicBool>,
    path: Option<std::path::PathBuf>,
    width: u32,
    height: u32,
    max_dimension: Option<u32>,
}

/// Per-frame and dedup bookkeeping that the renderer carries between frames.
struct FrameState {
    index: u64,
    last_settings_signature: Option<u64>,
    text_meshes_prepared_for_frame: u64,
    extract_dirty_done_for_frame: u64,
    text_mesh_signatures: std::collections::HashMap<freecs::Entity, u64>,
    glyph_atlas_initialized: bool,
}

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
}