nightshade 0.13.3

A cross-platform data-oriented game engine.
Documentation
use std::collections::HashMap;

pub const SPRITE_ATLAS_TOTAL_SLOTS: u32 = 128;
pub const SPRITE_ATLAS_SLOT_SIZE: (u32, u32) = (512, 512);

pub struct SpriteTextureAtlas {
    pub texture: wgpu::Texture,
    pub view: wgpu::TextureView,
    pub sampler: wgpu::Sampler,
    pub atlas_size: (u32, u32),
    pub slot_size: (u32, u32),
    pub slots_per_row: u32,
    pub total_slots: u32,
    occupied_slots: HashMap<u32, bool>,
}

impl SpriteTextureAtlas {
    pub fn new(device: &wgpu::Device, total_slots: u32, slot_size: (u32, u32)) -> Self {
        let slots_per_row = (total_slots as f32).sqrt().ceil() as u32;
        let atlas_size = (slots_per_row * slot_size.0, slots_per_row * slot_size.1);

        let texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Sprite Texture Atlas"),
            size: wgpu::Extent3d {
                width: atlas_size.0,
                height: atlas_size.1,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba8UnormSrgb,
            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
            view_formats: &[],
        });

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

        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            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::Linear,
            ..Default::default()
        });

        Self {
            texture,
            view,
            sampler,
            atlas_size,
            slot_size,
            slots_per_row,
            total_slots,
            occupied_slots: HashMap::new(),
        }
    }

    pub fn upload_texture(
        &mut self,
        queue: &wgpu::Queue,
        slot: u32,
        rgba_data: &[u8],
        width: u32,
        height: u32,
    ) {
        if slot >= self.total_slots {
            return;
        }

        let row = slot / self.slots_per_row;
        let col = slot % self.slots_per_row;

        let x_offset = col * self.slot_size.0;
        let y_offset = row * self.slot_size.1;

        queue.write_texture(
            wgpu::TexelCopyTextureInfo {
                texture: &self.texture,
                mip_level: 0,
                origin: wgpu::Origin3d {
                    x: x_offset,
                    y: y_offset,
                    z: 0,
                },
                aspect: wgpu::TextureAspect::All,
            },
            rgba_data,
            wgpu::TexelCopyBufferLayout {
                offset: 0,
                bytes_per_row: Some(width * 4),
                rows_per_image: Some(height),
            },
            wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
        );

        self.occupied_slots.insert(slot, true);
    }

    pub fn get_uv_transform(&self, slot: u32) -> (f32, f32, f32, f32) {
        let row = slot / self.slots_per_row;
        let col = slot % self.slots_per_row;

        let u_min = (col * self.slot_size.0) as f32 / self.atlas_size.0 as f32;
        let v_min = (row * self.slot_size.1) as f32 / self.atlas_size.1 as f32;
        let u_max = ((col + 1) * self.slot_size.0) as f32 / self.atlas_size.0 as f32;
        let v_max = ((row + 1) * self.slot_size.1) as f32 / self.atlas_size.1 as f32;

        (u_min, v_min, u_max, v_max)
    }
}