scena 1.7.1

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

use crate::diagnostics::RenderError;
use crate::material::Color;
#[cfg(feature = "browser-probe")]
use wasm_bindgen::JsValue;
#[cfg(feature = "browser-probe")]
use wasm_bindgen_futures::JsFuture;

use super::super::RasterTarget;
use super::draw_common::wgpu_clear_color;
use super::{GpuDeviceState, GpuRenderResult};

impl GpuDeviceState {
    pub(in crate::render) fn render_empty_surface(
        &mut self,
        target: RasterTarget,
        background_color: Color,
    ) -> Result<GpuRenderResult, RenderError> {
        let Some(surface) = self.surface.as_ref() else {
            return Err(RenderError::GpuResourcesNotPrepared {
                backend: target.backend,
            });
        };
        let surface_output = match surface.surface.get_current_texture() {
            wgpu::CurrentSurfaceTexture::Success(output)
            | wgpu::CurrentSurfaceTexture::Suboptimal(output) => output,
            wgpu::CurrentSurfaceTexture::Timeout
            | wgpu::CurrentSurfaceTexture::Occluded
            | wgpu::CurrentSurfaceTexture::Outdated
            | wgpu::CurrentSurfaceTexture::Lost
            | wgpu::CurrentSurfaceTexture::Validation => return Ok(GpuRenderResult::default()),
        };
        let surface_view = surface_output
            .texture
            .create_view(&wgpu::TextureViewDescriptor::default());
        let mut encoder = self
            .device
            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("scena.browser.empty_surface_encoder"),
            });
        {
            let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                label: Some("scena.browser.empty_surface_pass"),
                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                    view: &surface_view,
                    depth_slice: None,
                    resolve_target: None,
                    ops: wgpu::Operations {
                        load: wgpu::LoadOp::Clear(wgpu_clear_color(background_color)),
                        store: wgpu::StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: None,
                timestamp_writes: None,
                occlusion_query_set: None,
                multiview_mask: None,
            });
        }
        self.queue.submit(Some(encoder.finish()));
        surface_output.present();
        Ok(GpuRenderResult::default())
    }

    #[cfg(feature = "browser-probe")]
    pub(in crate::render) async fn browser_probe_readback_rgba8(
        &mut self,
        target: RasterTarget,
    ) -> Result<Option<Vec<u8>>, JsValue> {
        let Some(resources) = self.resources.as_ref() else {
            return Ok(None);
        };
        let Some(readback) = resources.readback.as_ref() else {
            return Ok(None);
        };
        if resources.target != target {
            return Err(JsValue::from_str(&format!(
                "browser proof readback resources were prepared for {:?}, not {:?}",
                resources.target, target
            )));
        }
        let slice = readback.buffer.slice(..);
        let promise = js_sys::Promise::new(&mut |resolve, reject| {
            let resolve = resolve.clone();
            let reject = reject.clone();
            slice.map_async(wgpu::MapMode::Read, move |result| match result {
                Ok(()) => {
                    let _ = resolve.call0(&JsValue::UNDEFINED);
                }
                Err(error) => {
                    let _ = reject.call1(
                        &JsValue::UNDEFINED,
                        &JsValue::from_str(&format!("browser proof readback failed: {error:?}")),
                    );
                }
            });
        });
        JsFuture::from(promise).await?;
        let mapped = slice.get_mapped_range();
        let mut frame = vec![0; target.byte_len()];
        for row in 0..target.height as usize {
            let source_start = row * readback.padded_bytes_per_row as usize;
            let source_end = source_start + readback.unpadded_bytes_per_row as usize;
            let target_start = row * readback.unpadded_bytes_per_row as usize;
            let target_end = target_start + readback.unpadded_bytes_per_row as usize;
            frame[target_start..target_end].copy_from_slice(&mapped[source_start..source_end]);
        }
        drop(mapped);
        readback.buffer.unmap();
        Ok(Some(frame))
    }
}