nvgx-wgpu 0.1.0

WGPU backend for Pure-rust implementation of NanoVG
Documentation
use nvgx::{ImageFlags, TextureType};
use slab::Slab;

#[allow(unused)]
pub struct StencilTexture {
    pub texture: wgpu::Texture,
    pub view: wgpu::TextureView,
}

impl StencilTexture {
    pub fn new(device: &wgpu::Device, width: u32, height: u32) -> Self {
        let size = wgpu::Extent3d {
            width,
            height,
            depth_or_array_layers: 1,
        };

        let desc = wgpu::TextureDescriptor {
            label: Some("NVG Stencil Texture"),
            size,
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Stencil8,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[],
        };
        let texture = device.create_texture(&desc);
        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
        return Self { texture, view };
    }
}

#[allow(unused)]
pub struct Texture {
    pub texture: wgpu::Texture,
    pub view: wgpu::TextureView,
    pub sampler: wgpu::Sampler,
    pub bind_group: wgpu::BindGroup,
    pub image_flags: nvgx::ImageFlags,
}

impl Texture {
    pub fn placeholder_texture(
        device: &wgpu::Device,
        texture_bind_group_layout: &wgpu::BindGroupLayout,
    ) -> Self {
        return Self::new(
            device,
            wgpu::Extent3d {
                width: 1,
                height: 1,
                depth_or_array_layers: 1,
            },
            ImageFlags::empty(),
            nvgx::TextureType::RGBA,
            texture_bind_group_layout,
        );
    }

    pub fn new(
        device: &wgpu::Device,
        size: wgpu::Extent3d,
        image_flags: nvgx::ImageFlags,
        texture_type: nvgx::TextureType,
        texture_bind_group_layout: &wgpu::BindGroupLayout,
    ) -> Self {
        let texture = match texture_type {
            nvgx::TextureType::RGBA => {
                device.create_texture(&wgpu::TextureDescriptor {
                    label: Some("NVG RGBA Texture"),
                    size,
                    mip_level_count: 1, // mipmap not supported yet
                    sample_count: 1,
                    dimension: wgpu::TextureDimension::D2,
                    format: wgpu::TextureFormat::Rgba8Unorm,
                    usage: wgpu::TextureUsages::TEXTURE_BINDING
                        | wgpu::TextureUsages::COPY_DST
                        | wgpu::TextureUsages::RENDER_ATTACHMENT,
                    view_formats: &[],
                })
            }
            nvgx::TextureType::Alpha => {
                device.create_texture(&wgpu::TextureDescriptor {
                    label: Some("NVG Alpha Texture"),
                    size,
                    mip_level_count: 1, // mipmap not supported yet
                    sample_count: 1,
                    dimension: wgpu::TextureDimension::D2,
                    format: wgpu::TextureFormat::R8Unorm,
                    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 {
            label: Some("NVG Sampler"),
            address_mode_u: if image_flags.contains(ImageFlags::REPEATX) {
                wgpu::AddressMode::Repeat
            } else {
                wgpu::AddressMode::ClampToEdge
            },
            address_mode_v: if image_flags.contains(ImageFlags::REPEATY) {
                wgpu::AddressMode::Repeat
            } else {
                wgpu::AddressMode::ClampToEdge
            },
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: if image_flags.contains(ImageFlags::NEAREST) {
                wgpu::FilterMode::Nearest
            } else {
                wgpu::FilterMode::Linear
            },
            min_filter: if image_flags.contains(ImageFlags::NEAREST) {
                wgpu::FilterMode::Nearest
            } else {
                wgpu::FilterMode::Linear
            },
            mipmap_filter: if image_flags.contains(ImageFlags::NEAREST) {
                wgpu::FilterMode::Nearest
            } else {
                wgpu::FilterMode::Linear
            },
            ..Default::default()
        });

        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("NVG Texture Bind Group"),
            layout: &texture_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&sampler),
                },
            ],
        });

        Self {
            texture,
            view,
            sampler,
            bind_group,
            image_flags,
        }
    }

    pub fn update(
        &self,
        queue: &wgpu::Queue,
        data: &[u8],
        origin: wgpu::Origin2d,
        size: wgpu::Extent3d,
    ) {
        let bytes_per_row = match self.texture.format() {
            wgpu::TextureFormat::Rgba8Unorm => 4,
            wgpu::TextureFormat::R8Unorm => 1,
            _ => panic!("Unsupported texture format"),
        } * size.width;

        queue.write_texture(
            wgpu::TexelCopyTextureInfo {
                texture: &self.texture,
                mip_level: 0,
                origin: origin.to_3d(0),
                aspect: wgpu::TextureAspect::All,
            },
            data,
            wgpu::TexelCopyBufferLayout {
                offset: 0,
                bytes_per_row: Some(bytes_per_row),
                rows_per_image: Some(size.height),
            },
            size,
        );
    }

    #[inline]
    pub fn size(&self) -> wgpu::Extent3d {
        self.texture.size()
    }

    #[inline]
    pub fn texture_type(&self) -> nvgx::TextureType {
        match self.texture.format() {
            wgpu::TextureFormat::Rgba8Unorm => nvgx::TextureType::RGBA,
            wgpu::TextureFormat::R8Unorm => nvgx::TextureType::Alpha,
            _ => {
                panic!("unsupport texture format: {:?}", self.texture.format())
            }
        }
    }
}

pub struct TextureManager {
    stencil_texture: StencilTexture,
    pub textures: Slab<Texture>,
    pub place_holder_texture: Texture,
    pub layout: wgpu::BindGroupLayout,
}

impl TextureManager {
    pub fn new(device: &wgpu::Device, surface_config: &wgpu::SurfaceConfiguration) -> Self {
        let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("NVG Texture Bind Group Layout"),
            entries: &[
                wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Texture {
                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        view_dimension: wgpu::TextureViewDimension::D2,
                        multisampled: false,
                    },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 1,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                    count: None,
                },
            ],
        });

        let place_holder_texture = Texture::placeholder_texture(&device, &layout);

        return Self {
            stencil_texture: StencilTexture::new(
                device,
                surface_config.width,
                surface_config.height,
            ),
            textures: Slab::new(),
            place_holder_texture,
            layout,
        };
    }

    #[inline]
    pub fn configure_stencil(
        &mut self,
        device: &wgpu::Device,
        surface_config: &wgpu::SurfaceConfiguration,
    ) {
        self.stencil_texture =
            StencilTexture::new(device, surface_config.width, surface_config.height);
    }

    #[inline]
    pub fn stencil_view(&self) -> &wgpu::TextureView {
        return &self.stencil_texture.view;
    }

    #[inline]
    pub fn create(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        size: wgpu::Extent3d,
        flags: ImageFlags,
        texture_type: TextureType,
        data: Option<&[u8]>,
    ) -> usize {
        let texture = Texture::new(&device, size, flags, texture_type, &self.layout);
        if let Some(data) = data {
            texture.update(&queue, data, wgpu::Origin2d::ZERO, size);
        }
        return self.textures.insert(texture);
    }

    #[inline]
    pub fn get(&self, id: usize) -> Option<&Texture> {
        self.textures.get(id)
    }

    #[inline]
    pub fn get_mut(&mut self, id: usize) -> Option<&mut Texture> {
        self.textures.get_mut(id)
    }

    #[inline]
    pub fn remove(&mut self, id: usize) -> Texture {
        self.textures.remove(id)
    }

    #[inline]
    pub fn get_bindgroup(&self, id: Option<usize>) -> &wgpu::BindGroup {
        if let Some(id) = id {
            return &self
                .get(id)
                .unwrap_or(&self.place_holder_texture)
                .bind_group;
        } else {
            return &self.place_holder_texture.bind_group;
        }
    }
}