mdanceio 0.1.2

MDanceIO is a MMD(MikuMikuDance) compatible implementation targeting at browser through wasm.
Documentation
use crate::{
    base_application_service::BaseApplicationService, error::MdanceioError, injector::Injector,
    project::ModelHandle,
};

pub struct OffscreenProxy {
    texture: wgpu::Texture,
    target: wgpu::TextureView,
    buffer_dimensions: BufferDimensions,
    target_buffer: wgpu::Buffer,

    device: wgpu::Device,
    queue: wgpu::Queue,

    application: BaseApplicationService,
}

impl OffscreenProxy {
    pub async fn init(width: u32, height: u32) -> Self {
        let texture_format = wgpu::TextureFormat::Bgra8UnormSrgb;

        let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY);
        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: wgpu::PowerPreference::default(),
                force_fallback_adapter: false,
                compatible_surface: None,
            })
            .await
            .unwrap();
        let (device, queue) = adapter
            .request_device(
                &wgpu::DeviceDescriptor {
                    label: Some("OffscreenRendererDevice"),
                    ..Default::default()
                },
                None,
            )
            .await
            .unwrap();

        let texture_desc = wgpu::TextureDescriptor {
            label: Some("OffscreenTarget"),
            size: wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: texture_format,
            usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
        };
        let texture = device.create_texture(&texture_desc);
        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());

        let buffer_dimensions = BufferDimensions::new(width, height);
        let target_buffer_desc = wgpu::BufferDescriptor {
            label: Some("OffscreenTargetBuffer"),
            size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height)
                as wgpu::BufferAddress,
            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
            mapped_at_creation: false,
        };
        let target_buffer = device.create_buffer(&target_buffer_desc);

        let service = BaseApplicationService::new(
            &adapter,
            &device,
            &queue,
            Injector {
                pixel_format: texture_format,
                viewport_size: [width, height],
            },
        );

        Self {
            texture,
            target: view,
            buffer_dimensions,
            target_buffer,
            device,
            queue,
            application: service,
        }
    }

    pub fn load_model(&mut self, data: &[u8]) -> Result<ModelHandle, MdanceioError> {
        let handle = self
            .application
            .load_model(data, &self.device, &self.queue)?;
        self.application.enable_shadow_map(handle, true)?;
        Ok(handle)
    }

    pub fn load_texture(&mut self, key: &str, data: &[u8], update_bind: bool) {
        self.application
            .load_texture(key, data, update_bind, &self.device, &self.queue);
    }

    pub fn update_texture_bind(&mut self) {
        self.application.update_bind_texture(&self.device);
    }

    pub fn load_model_motion(&mut self, data: &[u8]) -> Result<(), MdanceioError> {
        self.application.load_model_motion(data)
    }

    pub fn redraw(&mut self) -> Vec<u8> {
        self.application
            .draw_default_pass(&self.target, &self.device, &self.queue);

        let mut encoder = self
            .device
            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("OffscreenBufferEncoder"),
            });
        encoder.copy_texture_to_buffer(
            self.texture.as_image_copy(),
            wgpu::ImageCopyBuffer {
                buffer: &self.target_buffer,
                layout: wgpu::ImageDataLayout {
                    offset: 0,
                    bytes_per_row: Some(
                        std::num::NonZeroU32::new(
                            self.buffer_dimensions.padded_bytes_per_row as u32,
                        )
                        .unwrap(),
                    ),
                    rows_per_image: std::num::NonZeroU32::new(self.buffer_dimensions.height),
                },
            },
            wgpu::Extent3d {
                width: self.buffer_dimensions.width,
                height: self.buffer_dimensions.height,
                depth_or_array_layers: 1,
            },
        );
        let submission_index = self.queue.submit(Some(encoder.finish()));

        let buffer_slice = self.target_buffer.slice(..);
        buffer_slice.map_async(wgpu::MapMode::Read, |_| ());
        self.device
            .poll(wgpu::Maintain::WaitForSubmissionIndex(submission_index));

        let mut result = vec![];
        let mapped_buffer = buffer_slice.get_mapped_range();
        for chunk in mapped_buffer.chunks(self.buffer_dimensions.padded_bytes_per_row as usize) {
            result.extend(&chunk[..self.buffer_dimensions.unpadded_bytes_per_row as usize]);
        }
        drop(mapped_buffer);
        self.target_buffer.unmap();
        result
    }

    pub fn play(&mut self) {
        self.application.play();
    }

    pub fn viewport_size(&self) -> (u32, u32) {
        (self.buffer_dimensions.width, self.buffer_dimensions.height)
    }
}

struct BufferDimensions {
    width: u32,
    height: u32,
    unpadded_bytes_per_row: u32,
    padded_bytes_per_row: u32,
}

impl BufferDimensions {
    fn new(width: u32, height: u32) -> Self {
        let bytes_per_pixel = std::mem::size_of::<u32>() as u32;
        let unpadded_bytes_per_row = width * bytes_per_pixel;
        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
        let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
        let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
        Self {
            width,
            height,
            unpadded_bytes_per_row,
            padded_bytes_per_row,
        }
    }
}