nightshade 0.14.0

A cross-platform data-oriented game engine.
Documentation
//! D2-array texture pool for UI images (thumbnails, icons, asset previews).
//!
//! UI images are loaded asset textures sampled from a dedicated D2 array, so
//! each UI image is just a layer index and adding more is O(1) up to the
//! array's layer count.
//!
//! WebGPU guarantees at least `max_texture_array_layers >= 256` and
//! `TextureViewDimension::D2Array` is core, so this works on every backend
//! the engine targets, including wasm.

pub const UI_TEXTURE_LAYER_SIZE: u32 = 256;
pub const UI_TEXTURE_MAX_LAYERS: u32 = 256;
pub const UI_TEXTURE_NONE_LAYER: u32 = 0xFFFF_FFFF;

pub struct UiTextureArray {
    pub texture: wgpu::Texture,
    pub view: wgpu::TextureView,
    pub sampler: wgpu::Sampler,
    pub next_layer: u32,
}

impl UiTextureArray {
    pub fn new(device: &wgpu::Device) -> Self {
        let texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("UI Image Texture Array"),
            size: wgpu::Extent3d {
                width: UI_TEXTURE_LAYER_SIZE,
                height: UI_TEXTURE_LAYER_SIZE,
                depth_or_array_layers: UI_TEXTURE_MAX_LAYERS,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba8Unorm,
            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
            view_formats: &[],
        });

        let view = texture.create_view(&wgpu::TextureViewDescriptor {
            label: Some("UI Image Texture Array View"),
            format: Some(wgpu::TextureFormat::Rgba8Unorm),
            dimension: Some(wgpu::TextureViewDimension::D2Array),
            usage: None,
            aspect: wgpu::TextureAspect::All,
            base_mip_level: 0,
            mip_level_count: Some(1),
            base_array_layer: 0,
            array_layer_count: Some(UI_TEXTURE_MAX_LAYERS),
        });

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

        Self {
            texture,
            view,
            sampler,
            next_layer: 0,
        }
    }

    pub fn allocate_layer(&mut self) -> Option<u32> {
        if self.next_layer >= UI_TEXTURE_MAX_LAYERS {
            return None;
        }
        let layer = self.next_layer;
        self.next_layer += 1;
        Some(layer)
    }

    pub fn upload_layer(
        &self,
        queue: &wgpu::Queue,
        layer: u32,
        rgba: &[u8],
        width: u32,
        height: u32,
    ) {
        if layer >= UI_TEXTURE_MAX_LAYERS
            || width > UI_TEXTURE_LAYER_SIZE
            || height > UI_TEXTURE_LAYER_SIZE
        {
            return;
        }
        queue.write_texture(
            wgpu::TexelCopyTextureInfo {
                texture: &self.texture,
                mip_level: 0,
                origin: wgpu::Origin3d {
                    x: 0,
                    y: 0,
                    z: layer,
                },
                aspect: wgpu::TextureAspect::All,
            },
            rgba,
            wgpu::TexelCopyBufferLayout {
                offset: 0,
                bytes_per_row: Some(width * 4),
                rows_per_image: Some(height),
            },
            wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
        );
    }
}