scena 1.0.2

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
#![cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]

use super::super::RasterTarget;
use super::materials::MaterialResources;
use super::pipeline::{BYTES_PER_PIXEL, create_unlit_pipeline};
use super::pipeline::{UnlitPass, encode_unlit_pass};
use super::vertices::PrimitiveDrawBatch;

#[derive(Debug)]
pub(super) struct BrowserReadbackResources {
    pub(super) texture: wgpu::Texture,
    pub(super) view: wgpu::TextureView,
    pub(super) buffer: wgpu::Buffer,
    pub(super) pipeline: wgpu::RenderPipeline,
    pub(super) padded_bytes_per_row: u32,
    pub(super) unpadded_bytes_per_row: u32,
}

pub(super) fn create_browser_readback_resources(
    device: &wgpu::Device,
    target: RasterTarget,
    output_bind_group_layout: &wgpu::BindGroupLayout,
    material_bind_group_layout: &wgpu::BindGroupLayout,
    draw_bind_group_layout: &wgpu::BindGroupLayout,
    depth_compare: Option<wgpu::CompareFunction>,
) -> BrowserReadbackResources {
    let texture = device.create_texture(&wgpu::TextureDescriptor {
        label: Some("scena.browser_webgpu.proof_readback_target"),
        size: wgpu::Extent3d {
            width: target.width,
            height: target.height,
            depth_or_array_layers: 1,
        },
        mip_level_count: 1,
        sample_count: 1,
        dimension: wgpu::TextureDimension::D2,
        format: wgpu::TextureFormat::Rgba8Unorm,
        usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
        view_formats: &[],
    });
    let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
    let unpadded_bytes_per_row = target.width.saturating_mul(BYTES_PER_PIXEL);
    let padded_bytes_per_row = align_to(unpadded_bytes_per_row, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
    let buffer = device.create_buffer(&wgpu::BufferDescriptor {
        label: Some("scena.browser_webgpu.proof_readback_buffer"),
        size: u64::from(padded_bytes_per_row) * u64::from(target.height),
        usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
        mapped_at_creation: false,
    });
    let pipeline = create_unlit_pipeline(
        device,
        wgpu::TextureFormat::Rgba8Unorm,
        output_bind_group_layout,
        material_bind_group_layout,
        draw_bind_group_layout,
        depth_compare,
    );
    BrowserReadbackResources {
        texture,
        view,
        buffer,
        pipeline,
        padded_bytes_per_row,
        unpadded_bytes_per_row,
    }
}

pub(super) struct BrowserReadbackPass<'a> {
    pub(super) target: RasterTarget,
    pub(super) readback: &'a BrowserReadbackResources,
    pub(super) depth_view: Option<&'a wgpu::TextureView>,
    pub(super) vertex_buffer: &'a wgpu::Buffer,
    pub(super) output_bind_group: &'a wgpu::BindGroup,
    pub(super) draw_bind_group: &'a wgpu::BindGroup,
    pub(super) material_resources: &'a MaterialResources,
    pub(super) draw_batches: &'a [PrimitiveDrawBatch],
    pub(super) clear_color: wgpu::Color,
}

pub(super) fn encode_browser_readback_pass(
    encoder: &mut wgpu::CommandEncoder,
    pass: BrowserReadbackPass<'_>,
) {
    encode_unlit_pass(
        encoder,
        UnlitPass {
            view: &pass.readback.view,
            depth_view: pass.depth_view,
            vertex_buffer: pass.vertex_buffer,
            output_bind_group: pass.output_bind_group,
            draw_bind_group: pass.draw_bind_group,
            material_resources: pass.material_resources,
            draw_batches: pass.draw_batches,
            pipeline: &pass.readback.pipeline,
            clear_color: pass.clear_color,
            label: "scena.browser.webgpu_proof_readback_pass",
        },
    );
    encoder.copy_texture_to_buffer(
        wgpu::TexelCopyTextureInfo {
            texture: &pass.readback.texture,
            mip_level: 0,
            origin: wgpu::Origin3d::ZERO,
            aspect: wgpu::TextureAspect::All,
        },
        wgpu::TexelCopyBufferInfo {
            buffer: &pass.readback.buffer,
            layout: wgpu::TexelCopyBufferLayout {
                offset: 0,
                bytes_per_row: Some(pass.readback.padded_bytes_per_row),
                rows_per_image: None,
            },
        },
        wgpu::Extent3d {
            width: pass.target.width,
            height: pass.target.height,
            depth_or_array_layers: 1,
        },
    );
}

fn align_to(value: u32, alignment: u32) -> u32 {
    value.div_ceil(alignment) * alignment
}