scena 1.7.0

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use wasm_bindgen::{JsCast, JsValue};

use super::{SceneHostError, SceneHostErrorCode};

pub(super) fn browser_canvas_rgba8(
    canvas: &web_sys::HtmlCanvasElement,
) -> Result<Option<(u32, u32, Vec<u8>)>, SceneHostError> {
    let width = canvas.width();
    let height = canvas.height();
    let len = width
        .checked_mul(height)
        .and_then(|pixels| pixels.checked_mul(4))
        .ok_or_else(|| {
            SceneHostError::new(
                SceneHostErrorCode::Capture,
                format!("canvas readback dimensions overflow: {width}x{height}"),
            )
        })?;
    if len == 0 {
        return Ok(None);
    }

    let get_context = js_sys::Reflect::get(canvas.as_ref(), &JsValue::from_str("getContext"))
        .map_err(browser_readback_error)?
        .dyn_into::<js_sys::Function>()
        .map_err(browser_readback_error)?;
    let options = js_sys::Object::new();
    js_sys::Reflect::set(
        &options,
        &JsValue::from_str("preserveDrawingBuffer"),
        &JsValue::TRUE,
    )
    .map_err(browser_readback_error)?;
    let context = get_context
        .call2(
            canvas.as_ref(),
            &JsValue::from_str("webgl2"),
            options.as_ref(),
        )
        .map_err(browser_readback_error)?;
    if context.is_null() || context.is_undefined() {
        return Ok(None);
    }

    let rgba = js_sys::Reflect::get(&context, &JsValue::from_str("RGBA"))
        .map_err(browser_readback_error)?;
    let unsigned_byte = js_sys::Reflect::get(&context, &JsValue::from_str("UNSIGNED_BYTE"))
        .map_err(browser_readback_error)?;
    let read_pixels = js_sys::Reflect::get(&context, &JsValue::from_str("readPixels"))
        .map_err(browser_readback_error)?
        .dyn_into::<js_sys::Function>()
        .map_err(browser_readback_error)?;

    let bytes = js_sys::Uint8Array::new_with_length(len);
    let args = js_sys::Array::new();
    args.push(&JsValue::from_f64(0.0));
    args.push(&JsValue::from_f64(0.0));
    args.push(&JsValue::from_f64(f64::from(width)));
    args.push(&JsValue::from_f64(f64::from(height)));
    args.push(&rgba);
    args.push(&unsigned_byte);
    args.push(bytes.as_ref());
    read_pixels
        .apply(&context, &args)
        .map_err(browser_readback_error)?;

    let mut rgba8 = vec![0; len as usize];
    bytes.copy_to(rgba8.as_mut_slice());
    Ok(Some((width, height, rgba8)))
}

fn browser_readback_error(error: JsValue) -> SceneHostError {
    SceneHostError::new(
        SceneHostErrorCode::Capture,
        error
            .as_string()
            .unwrap_or_else(|| format!("browser canvas readback failed: {error:?}")),
    )
}