nvgx-wgpu 0.1.0

WGPU backend for Pure-rust implementation of NanoVG
Documentation
use std::ops::Range;
use std::sync::Arc;

use nvgx::{BufferUsage, VertexSlice};
use wgpu::{Extent3d, Origin2d};

use crate::{
    call::{Call, GpuPath},
    unifroms::{RenderCommand, ShaderType},
};

use super::{Renderer, call::CallType, mesh::Mesh};

impl nvgx::RendererDevice for Renderer {
    type VertexBuffer = Arc<wgpu::Buffer>;

    fn edge_antialias(&self) -> bool {
        return self.config.antialias;
    }

    fn create_vertex_buffer(
        &mut self,
        buffer_size: usize,
        _usage: BufferUsage,
    ) -> anyhow::Result<Self::VertexBuffer> {
        return Ok(Arc::new(Mesh::create_buffer(&self.device, buffer_size)));
    }

    fn update_vertex_buffer(
        &mut self,
        buffer: Option<&Self::VertexBuffer>,
        data: &[u8],
    ) -> anyhow::Result<()> {
        if let Some(buffer) = buffer {
            self.resources
                .mesh
                .update_buffer(&self.queue, buffer.as_ref(), data)?;
        } else {
            self.resources
                .mesh
                .update_inner_buffer(&self.device, &self.queue, data);
        };
        Ok(())
    }

    fn resize(&mut self, _width: u32, _height: u32) -> anyhow::Result<()> {
        self.surface_config.width = _width;
        self.surface_config.height = _height;
        self.surface.configure(&self.device, &self.surface_config);
        self.resources
            .texture_manager
            .configure_stencil(&self.device, &self.surface_config);
        Ok(())
    }

    fn create_texture(
        &mut self,
        texture_type: nvgx::TextureType,
        width: u32,
        height: u32,
        flags: nvgx::ImageFlags,
        data: Option<&[u8]>,
    ) -> anyhow::Result<nvgx::ImageId> {
        Ok(self.resources.texture_manager.create(
            &self.device,
            &self.queue,
            wgpu::Extent3d {
                width: width,
                height: height,
                depth_or_array_layers: 1,
            },
            flags,
            texture_type,
            data,
        ) as nvgx::ImageId)
    }

    fn delete_texture(&mut self, img: nvgx::ImageId) -> anyhow::Result<()> {
        self.resources.texture_manager.remove(img as usize);
        Ok(())
    }

    fn update_texture(
        &mut self,
        img: nvgx::ImageId,
        x: u32,
        y: u32,
        width: u32,
        height: u32,
        data: &[u8],
    ) -> anyhow::Result<()> {
        let texture = self
            .resources
            .texture_manager
            .get_mut(img as usize)
            .ok_or_else(|| anyhow::anyhow!("Texture not found"))?;
        texture.update(
            &self.queue,
            data,
            Origin2d { x, y },
            Extent3d {
                width: width,
                height: height,
                depth_or_array_layers: 1,
            },
        );
        Ok(())
    }

    fn texture_size(&self, img: nvgx::ImageId) -> anyhow::Result<(u32, u32)> {
        let texture = self
            .resources
            .texture_manager
            .get(img as usize)
            .ok_or_else(|| anyhow::anyhow!("Texture not found"))?;
        let size = texture.size();
        Ok((size.width, size.height))
    }

    fn viewport(&mut self, extent: nvgx::Extent, _device_pixel_ratio: f32) -> anyhow::Result<()> {
        self.resources.viewsize_uniform.value = extent;
        Ok(())
    }

    #[inline]
    fn cancel(&mut self) -> anyhow::Result<()> {
        self.resources.calls.clear();
        self.resources.paths.clear();
        self.resources.render_unifrom.value.clear();
        Ok(())
    }

    fn flush(&mut self) -> anyhow::Result<()> {
        self.resources
            .viewsize_uniform
            .update_buffer(&self.device, &self.queue);
        self.resources
            .render_unifrom
            .update_buffer(&self.device, &self.queue);

        if let Some((image, stencil_view)) = self.target_fb.as_ref() {
            let texture = self.resources.texture_manager.get(*image).unwrap();
            self.resources.render(
                &self.device,
                &self.queue,
                &texture.view,
                &stencil_view,
                &mut self.pipeline_manager,
                self.clear_cmd.take(),
            );
        } else {
            let output = self.surface.get_current_texture().unwrap();
            let view = output
                .texture
                .create_view(&wgpu::TextureViewDescriptor::default());

            self.resources.render(
                &self.device,
                &self.queue,
                &view,
                self.resources.texture_manager.stencil_view(),
                &mut self.pipeline_manager,
                self.clear_cmd.take(),
            );
            output.present();
        };
        return self.cancel();
    }

    fn fill(
        &mut self,
        vertex_buffer: Option<Self::VertexBuffer>,
        instances: Option<(Self::VertexBuffer, Range<u32>)>,
        paint: &nvgx::PaintPattern,
        composite_operation: nvgx::CompositeOperationState,
        fill_type: nvgx::PathFillType,
        scissor: &nvgx::Scissor,
        fringe: f32,
        bounds_offset: Option<usize>,
        paths: &[nvgx::PathSlice],
    ) -> anyhow::Result<()> {
        let path_offset = self.resources.paths.len();
        let mut fill_vertex_count = 0;
        self.resources.paths.extend(paths.iter().filter_map(|p| {
            let fill = p.get_fill();
            if fill.count < 3 {
                None
            } else {
                fill_vertex_count += fill.count;
                Some(GpuPath {
                    fill,
                    stroke: p.get_stroke(),
                })
            }
        }));

        self.resources
            .mesh
            .update_indices(&self.device, &self.queue, fill_vertex_count as u64);

        let call = Call {
            call_type: if bounds_offset.is_some() {
                crate::call::CallType::Fill(fill_type)
            } else {
                crate::call::CallType::ConvexFill
            },
            image: paint.image,
            path_range: path_offset..self.resources.paths.len(),
            triangle: if let Some(offset) = bounds_offset {
                VertexSlice { offset, count: 4 }
            } else {
                Default::default()
            },
            uniform_offset: self.resources.render_unifrom.offset(),
            blend_func: composite_operation,
            vertex_buffer,
            instances,
        };

        if let CallType::Fill(_) = call.call_type {
            self.resources.render_unifrom.value.push(RenderCommand {
                stroke_thr: -1.0,
                render_type: ShaderType::Simple as u32,
                ..Default::default()
            });
            self.resources.render_unifrom.value.push(RenderCommand::new(
                &self, paint, scissor, fringe, fringe, -1.0,
            ));
        } else {
            self.resources.render_unifrom.value.push(RenderCommand::new(
                &self, paint, scissor, fringe, fringe, -1.0,
            ));
        }
        self.resources.calls.push(call);
        Ok(())
    }

    fn stroke(
        &mut self,
        vertex_buffer: Option<Self::VertexBuffer>,
        instances: Option<(Self::VertexBuffer, Range<u32>)>,
        paint: &nvgx::PaintPattern,
        composite_operation: nvgx::CompositeOperationState,
        scissor: &nvgx::Scissor,
        fringe: f32,
        stroke_width: f32,
        paths: &[nvgx::PathSlice],
    ) -> anyhow::Result<()> {
        let path_offset = self.resources.paths.len();

        self.resources.paths.extend(paths.iter().filter_map(|p| {
            let stroke = p.get_stroke();
            Some(GpuPath {
                stroke: stroke,
                ..Default::default()
            })
        }));

        let call = Call {
            call_type: CallType::Stroke,
            image: paint.image,
            path_range: path_offset..self.resources.paths.len(),
            uniform_offset: self.resources.render_unifrom.offset(),
            blend_func: composite_operation,
            vertex_buffer,
            triangle: VertexSlice::default(),
            instances,
        };

        self.resources.render_unifrom.value.push(RenderCommand::new(
            &self,
            paint,
            scissor,
            stroke_width,
            fringe,
            -1.0,
        ));
        self.resources.calls.push(call);
        Ok(())
    }

    fn triangles(
        &mut self,
        vertex_buffer: Option<Self::VertexBuffer>,
        instances: Option<(Self::VertexBuffer, Range<u32>)>,
        paint: &nvgx::PaintPattern,
        composite_operation: nvgx::CompositeOperationState,
        scissor: &nvgx::Scissor,
        slice: VertexSlice,
    ) -> anyhow::Result<()> {
        let call = Call {
            call_type: CallType::Triangles,
            image: paint.image,
            triangle: slice,
            path_range: 0..0,
            uniform_offset: self.resources.render_unifrom.offset(),
            blend_func: composite_operation,
            vertex_buffer,
            instances,
        };

        self.resources.calls.push(call);

        self.resources.render_unifrom.value.push(
            RenderCommand::new(&self, paint, scissor, 1.0, 1.0, -1.0).set_type(ShaderType::Image),
        );
        Ok(())
    }

    fn clear(&mut self, color: nvgx::Color) -> anyhow::Result<()> {
        self.cancel()?;
        self.clear_cmd = Some(wgpu::Color {
            r: color.r as f64,
            g: color.g as f64,
            b: color.b as f64,
            a: color.a as f64,
        });
        Ok(())
    }

    #[cfg(feature = "wirelines")]
    fn wirelines(
        &mut self,
        vertex_buffer: Option<Self::VertexBuffer>,
        instances: Option<(Self::VertexBuffer, Range<u32>)>,
        paint: &nvgx::PaintPattern,
        composite_operation: nvgx::CompositeOperationState,
        scissor: &nvgx::Scissor,
        paths: &[nvgx::PathSlice],
    ) -> anyhow::Result<()> {
        let path_offset = self.resources.paths.len();

        self.resources.paths.extend(paths.iter().filter_map(|p| {
            let stroke = p.get_stroke();
            Some(GpuPath {
                stroke: stroke,
                ..Default::default()
            })
        }));

        let call = Call {
            call_type: CallType::Lines,
            image: paint.image,
            path_range: path_offset..self.resources.paths.len(),
            uniform_offset: self.resources.render_unifrom.offset(),
            blend_func: composite_operation,
            vertex_buffer,
            triangle: VertexSlice::default(),
            instances,
        };

        self.resources.calls.push(call);

        self.resources
            .render_unifrom
            .value
            .push(RenderCommand::new(&self, paint, scissor, 1.0, 1.0, -1.0));
        Ok(())
    }
}