mocari 0.1.2

A pure Rust Live2D/Cubism-compatible runtime
Documentation
use super::{
    WgpuLive2dRenderer,
    buffers::WgpuMeshBuffers,
    clipping::{WgpuClippingResources, WgpuMaskRenderTarget},
    texture::{WgpuTexture, WgpuTransform},
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WgpuRenderError {
    InvalidTextureIndex {
        texture_index: i32,
    },
    MissingTexture {
        texture_index: i32,
    },
    MissingDrawable {
        drawable_index: usize,
    },
    MissingClippingContext {
        drawable_index: usize,
    },
    UnsupportedClippingMasks {
        drawable_index: usize,
        mask_count: usize,
    },
}

impl std::fmt::Display for WgpuRenderError {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidTextureIndex { texture_index } => {
                write!(formatter, "invalid texture index {texture_index}")
            }
            Self::MissingTexture { texture_index } => {
                write!(formatter, "missing texture bind group {texture_index}")
            }
            Self::MissingDrawable { drawable_index } => {
                write!(formatter, "missing drawable {drawable_index}")
            }
            Self::MissingClippingContext { drawable_index } => write!(
                formatter,
                "drawable {drawable_index} has clipping masks but no prepared clipping context"
            ),
            Self::UnsupportedClippingMasks {
                drawable_index,
                mask_count,
            } => write!(
                formatter,
                "drawable {drawable_index} uses {mask_count} clipping masks, but clipping is not implemented"
            ),
        }
    }
}

impl std::error::Error for WgpuRenderError {}

impl WgpuLive2dRenderer {
    pub fn draw(
        &self,
        render_pass: &mut wgpu::RenderPass<'_>,
        mesh_buffers: &WgpuMeshBuffers,
        texture_bind_groups: &[wgpu::BindGroup],
    ) -> Result<u32, WgpuRenderError> {
        self.draw_with_bind_groups_and_transform(
            render_pass,
            mesh_buffers,
            texture_bind_groups,
            &self.identity_transform,
        )
    }

    pub fn draw_with_bind_groups_and_transform(
        &self,
        render_pass: &mut wgpu::RenderPass<'_>,
        mesh_buffers: &WgpuMeshBuffers,
        texture_bind_groups: &[wgpu::BindGroup],
        transform: &WgpuTransform,
    ) -> Result<u32, WgpuRenderError> {
        self.draw_with_bind_group_provider(render_pass, mesh_buffers, transform, |texture_index| {
            texture_bind_group_at(texture_bind_groups, texture_index)
        })
    }

    pub fn draw_with_textures(
        &self,
        render_pass: &mut wgpu::RenderPass<'_>,
        mesh_buffers: &WgpuMeshBuffers,
        textures: &[WgpuTexture],
    ) -> Result<u32, WgpuRenderError> {
        self.draw_with_textures_and_transform(
            render_pass,
            mesh_buffers,
            textures,
            &self.identity_transform,
        )
    }

    pub fn draw_with_textures_and_transform(
        &self,
        render_pass: &mut wgpu::RenderPass<'_>,
        mesh_buffers: &WgpuMeshBuffers,
        textures: &[WgpuTexture],
        transform: &WgpuTransform,
    ) -> Result<u32, WgpuRenderError> {
        self.draw_with_bind_group_provider(render_pass, mesh_buffers, transform, |texture_index| {
            texture_bind_group_from_textures(textures, texture_index)
        })
    }

    pub fn draw_with_textures_and_clipping(
        &self,
        render_pass: &mut wgpu::RenderPass<'_>,
        mesh_buffers: &WgpuMeshBuffers,
        textures: &[WgpuTexture],
        clipping_resources: &WgpuClippingResources,
        mask_target: &WgpuMaskRenderTarget,
    ) -> Result<u32, WgpuRenderError> {
        self.draw_with_textures_clipping_and_transform(
            render_pass,
            mesh_buffers,
            textures,
            clipping_resources,
            mask_target,
            &self.identity_transform,
        )
    }

    pub fn draw_with_textures_clipping_and_transform(
        &self,
        render_pass: &mut wgpu::RenderPass<'_>,
        mesh_buffers: &WgpuMeshBuffers,
        textures: &[WgpuTexture],
        clipping_resources: &WgpuClippingResources,
        mask_target: &WgpuMaskRenderTarget,
        transform: &WgpuTransform,
    ) -> Result<u32, WgpuRenderError> {
        self.draw_with_clipping_bind_group_provider(
            render_pass,
            mesh_buffers,
            clipping_resources,
            mask_target.bind_group(),
            transform,
            |texture_index| texture_bind_group_from_textures(textures, texture_index),
        )
    }

    pub fn draw_masks(
        &self,
        render_pass: &mut wgpu::RenderPass<'_>,
        mesh_buffers: &WgpuMeshBuffers,
        clipping_resources: &WgpuClippingResources,
        texture_bind_groups: &[wgpu::BindGroup],
    ) -> Result<u32, WgpuRenderError> {
        self.draw_masks_with_bind_group_provider(
            render_pass,
            mesh_buffers,
            clipping_resources,
            |texture_index| texture_bind_group_at(texture_bind_groups, texture_index),
        )
    }

    pub fn draw_masks_with_textures(
        &self,
        render_pass: &mut wgpu::RenderPass<'_>,
        mesh_buffers: &WgpuMeshBuffers,
        clipping_resources: &WgpuClippingResources,
        textures: &[WgpuTexture],
    ) -> Result<u32, WgpuRenderError> {
        self.draw_masks_with_bind_group_provider(
            render_pass,
            mesh_buffers,
            clipping_resources,
            |texture_index| texture_bind_group_from_textures(textures, texture_index),
        )
    }

    fn draw_with_bind_group_provider<'a>(
        &self,
        render_pass: &mut wgpu::RenderPass<'_>,
        mesh_buffers: &WgpuMeshBuffers,
        transform: &WgpuTransform,
        mut bind_group_for_texture: impl FnMut(i32) -> Result<&'a wgpu::BindGroup, WgpuRenderError>,
    ) -> Result<u32, WgpuRenderError> {
        let mut drawn = 0;
        for drawable_index in mesh_buffers.draw_order_indices() {
            let drawable = mesh_buffers
                .drawables()
                .get(drawable_index)
                .ok_or(WgpuRenderError::MissingDrawable { drawable_index })?;
            if drawable.is_empty() {
                continue;
            }
            if !drawable.masks().is_empty() {
                return Err(WgpuRenderError::UnsupportedClippingMasks {
                    drawable_index,
                    mask_count: drawable.masks().len(),
                });
            }
            let texture_bind_group = bind_group_for_texture(drawable.texture_index())?;

            render_pass.set_pipeline(self.pipeline_for_blend_mode(drawable.blend_mode()));
            render_pass.set_bind_group(0, texture_bind_group, &[]);
            render_pass.set_bind_group(1, transform.bind_group(), &[]);
            render_pass.set_vertex_buffer(0, drawable.vertex_buffer().slice(..));
            render_pass
                .set_index_buffer(drawable.index_buffer().slice(..), wgpu::IndexFormat::Uint16);
            render_pass.draw_indexed(0..drawable.index_count(), 0, 0..1);
            drawn += 1;
        }

        Ok(drawn)
    }

    fn draw_masks_with_bind_group_provider<'a>(
        &self,
        render_pass: &mut wgpu::RenderPass<'_>,
        mesh_buffers: &WgpuMeshBuffers,
        clipping_resources: &WgpuClippingResources,
        mut bind_group_for_texture: impl FnMut(i32) -> Result<&'a wgpu::BindGroup, WgpuRenderError>,
    ) -> Result<u32, WgpuRenderError> {
        let mut drawn = 0;
        for context in clipping_resources.contexts() {
            for &drawable_index in context.mask_drawable_indices() {
                let drawable = mesh_buffers
                    .drawables()
                    .get(drawable_index)
                    .ok_or(WgpuRenderError::MissingDrawable { drawable_index })?;
                if drawable.is_empty() {
                    continue;
                }
                let texture_bind_group = bind_group_for_texture(drawable.texture_index())?;

                render_pass.set_pipeline(&self.mask_pipeline);
                render_pass.set_bind_group(0, texture_bind_group, &[]);
                render_pass.set_bind_group(1, context.mask_transform().bind_group(), &[]);
                render_pass.set_bind_group(2, context.mask_params().bind_group(), &[]);
                render_pass.set_vertex_buffer(0, drawable.vertex_buffer().slice(..));
                render_pass
                    .set_index_buffer(drawable.index_buffer().slice(..), wgpu::IndexFormat::Uint16);
                render_pass.draw_indexed(0..drawable.index_count(), 0, 0..1);
                drawn += 1;
            }
        }

        Ok(drawn)
    }

    fn draw_with_clipping_bind_group_provider<'a>(
        &self,
        render_pass: &mut wgpu::RenderPass<'_>,
        mesh_buffers: &WgpuMeshBuffers,
        clipping_resources: &WgpuClippingResources,
        mask_bind_group: &wgpu::BindGroup,
        transform: &WgpuTransform,
        mut bind_group_for_texture: impl FnMut(i32) -> Result<&'a wgpu::BindGroup, WgpuRenderError>,
    ) -> Result<u32, WgpuRenderError> {
        let mut drawn = 0;
        for drawable_index in mesh_buffers.draw_order_indices() {
            let drawable = mesh_buffers
                .drawables()
                .get(drawable_index)
                .ok_or(WgpuRenderError::MissingDrawable { drawable_index })?;
            if drawable.is_empty() {
                continue;
            }
            let texture_bind_group = bind_group_for_texture(drawable.texture_index())?;

            if drawable.masks().is_empty() {
                render_pass.set_pipeline(self.pipeline_for_blend_mode(drawable.blend_mode()));
                render_pass.set_bind_group(0, texture_bind_group, &[]);
                render_pass.set_bind_group(1, transform.bind_group(), &[]);
            } else {
                let context = clipping_resources
                    .context_for_drawable(drawable_index)
                    .ok_or(WgpuRenderError::MissingClippingContext { drawable_index })?;
                render_pass
                    .set_pipeline(self.masked_pipeline_for_blend_mode(drawable.blend_mode()));
                render_pass.set_bind_group(0, texture_bind_group, &[]);
                render_pass.set_bind_group(1, transform.bind_group(), &[]);
                render_pass.set_bind_group(2, mask_bind_group, &[]);
                render_pass.set_bind_group(3, context.clip_params().bind_group(), &[]);
            }

            render_pass.set_vertex_buffer(0, drawable.vertex_buffer().slice(..));
            render_pass
                .set_index_buffer(drawable.index_buffer().slice(..), wgpu::IndexFormat::Uint16);
            render_pass.draw_indexed(0..drawable.index_count(), 0, 0..1);
            drawn += 1;
        }

        Ok(drawn)
    }
}

fn texture_bind_group_at(
    texture_bind_groups: &[wgpu::BindGroup],
    texture_index: i32,
) -> Result<&wgpu::BindGroup, WgpuRenderError> {
    let texture_index_usize = usize::try_from(texture_index)
        .map_err(|_| WgpuRenderError::InvalidTextureIndex { texture_index })?;
    texture_bind_groups
        .get(texture_index_usize)
        .ok_or(WgpuRenderError::MissingTexture { texture_index })
}

fn texture_bind_group_from_textures(
    textures: &[WgpuTexture],
    texture_index: i32,
) -> Result<&wgpu::BindGroup, WgpuRenderError> {
    let texture_index_usize = usize::try_from(texture_index)
        .map_err(|_| WgpuRenderError::InvalidTextureIndex { texture_index })?;
    textures
        .get(texture_index_usize)
        .map(WgpuTexture::bind_group)
        .ok_or(WgpuRenderError::MissingTexture { texture_index })
}