scena 1.3.0

A Rust-native scene-graph renderer with typed scene state, glTF assets, and explicit prepare/render lifecycles.
Documentation
use crate::assets::AssetPath;
use crate::diagnostics::AssetError;

use super::TextureSourceFormat;
use super::texture_ktx2::ktx2_descriptor_only_error;

pub(super) fn resolve_texture_source_bytes(
    path: &AssetPath,
    source_format: TextureSourceFormat,
    source_bytes: Option<&[u8]>,
) -> Result<Option<Vec<u8>>, AssetError> {
    if let Some(bytes) = source_bytes {
        return Ok(Some(bytes.to_vec()));
    }
    if path.as_str().starts_with("data:") {
        return decode_data_uri(path).map(Some);
    }
    match source_format {
        TextureSourceFormat::Ktx2Basisu => Err(ktx2_descriptor_only_error(path)),
        TextureSourceFormat::Png | TextureSourceFormat::Jpeg | TextureSourceFormat::Webp => {
            Ok(None)
        }
    }
}

#[cfg(target_arch = "wasm32")]
pub(super) fn browser_native_decode_format(source_format: TextureSourceFormat) -> bool {
    matches!(
        source_format,
        TextureSourceFormat::Png | TextureSourceFormat::Jpeg | TextureSourceFormat::Webp
    )
}

#[cfg(target_arch = "wasm32")]
pub(crate) async fn decode_browser_image_bitmap(
    path: &AssetPath,
    bytes: std::sync::Arc<[u8]>,
) -> Result<web_sys::ImageBitmap, AssetError> {
    use wasm_bindgen::JsCast;
    use wasm_bindgen_futures::JsFuture;

    let window = web_sys::window().ok_or_else(|| AssetError::Io {
        path: path.as_str().to_string(),
        reason: "browser image decode requires a Window".to_string(),
    })?;
    let array = js_sys::Uint8Array::from(bytes.as_ref());
    let parts = js_sys::Array::of1(&array.into());
    let blob = web_sys::Blob::new_with_u8_array_sequence(&parts.into()).map_err(|error| {
        AssetError::Io {
            path: path.as_str().to_string(),
            reason: error
                .as_string()
                .unwrap_or_else(|| format!("Blob construction failed: {error:?}")),
        }
    })?;
    let promise = window
        .create_image_bitmap_with_blob(&blob)
        .map_err(|error| AssetError::Io {
            path: path.as_str().to_string(),
            reason: error
                .as_string()
                .unwrap_or_else(|| format!("createImageBitmap failed: {error:?}")),
        })?;
    JsFuture::from(promise)
        .await
        .map_err(|error| AssetError::Io {
            path: path.as_str().to_string(),
            reason: error
                .as_string()
                .unwrap_or_else(|| format!("createImageBitmap await failed: {error:?}")),
        })?
        .dyn_into::<web_sys::ImageBitmap>()
        .map_err(|error| AssetError::Io {
            path: path.as_str().to_string(),
            reason: error
                .as_string()
                .unwrap_or_else(|| format!("createImageBitmap returned wrong type: {error:?}")),
        })
}

fn decode_data_uri(path: &AssetPath) -> Result<Vec<u8>, AssetError> {
    let Some((_, encoded)) = path.as_str().split_once(";base64,") else {
        return Err(AssetError::Parse {
            path: path.as_str().to_string(),
            reason: "only base64 texture data URIs are supported for embedded texture decoding"
                .to_string(),
        });
    };
    use base64::Engine;
    base64::engine::general_purpose::STANDARD
        .decode(encoded)
        .map_err(|error| AssetError::Parse {
            path: path.as_str().to_string(),
            reason: format!("invalid embedded texture base64: {error}"),
        })
}