blob: 3ea1de4e86f3640e47d626aeedbe55be627cba51 [file]
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use alloc::{format, string::String};
use core::fmt;
use thiserror::Error;
use web_sys::wasm_bindgen::JsValue;
use crate::RenderError;
/// Errors produced by the WebGL backend.
#[derive(Error, Debug, Clone)]
#[non_exhaustive]
pub enum WebGlError {
/// Rendering failed for a certain reason.
#[error(transparent)]
Render(#[from] RenderError),
/// A WebGL2 context could not be created for the canvas.
#[error("WebGL2 context unavailable")]
ContextUnavailable,
/// A WebGL operation failed.
#[error(
"WebGL operation failed while {operation:?}{suffix}",
suffix = MessageSuffix(.message.as_deref())
)]
OperationFailed {
/// The failed operation.
operation: WebGlOperation,
/// Additional diagnostic information, when available.
message: Option<String>,
},
/// A render pass contains too many instances for WebGL.
#[error("too many instances")]
TooManyInstances,
/// A resource texture allocation exceeds the supported texture dimensions.
#[error(
"resource texture allocation {width}x{height} exceeds maximum dimension {max_dimension}"
)]
ResourceTextureTooLarge {
/// The requested texture width.
width: u32,
/// The requested texture height.
height: u32,
/// The maximum supported width or height.
max_dimension: u32,
},
/// The WebGL context is incompatible with the renderer.
#[error("incompatible WebGL context: {0}")]
IncompatibleContext(IncompatibleContextReason),
}
/// A fallible operation performed by the WebGL backend.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WebGlOperation {
/// A context operation.
Context(WebGlContextOperation),
/// Creation of a WebGL resource.
ResourceCreation(WebGlResourceKind),
/// Shader compilation or program linking.
ShaderProgram(WebGlShaderProgramOperation),
/// Resolution of a shader interface binding.
ShaderInterface(WebGlShaderInterfaceOperation),
/// A data-transfer operation.
DataTransfer(WebGlDataTransferOperation),
/// A renderer probe operation.
#[cfg(feature = "probe")]
Probe(WebGlProbeOperation),
}
/// WebGL context operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WebGlContextOperation {
/// Creating a WebGL context.
Create,
/// Configuring context creation or state.
Configure,
/// Querying context state or limits.
Query,
/// Querying or invoking an extension.
Extension,
}
/// Kinds of WebGL resources.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WebGlResourceKind {
/// A texture.
Texture,
/// A buffer.
Buffer,
/// A framebuffer.
Framebuffer,
/// A renderbuffer.
Renderbuffer,
/// A synchronization object.
Sync,
/// A shader program.
Program,
/// A shader.
Shader(WebGlShaderStage),
/// A vertex array.
VertexArray,
}
/// WebGL shader stages.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WebGlShaderStage {
/// A vertex shader.
Vertex,
/// A fragment shader.
Fragment,
}
/// Shader compilation and program linking operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WebGlShaderProgramOperation {
/// Compiling a shader.
Compile(WebGlShaderStage),
/// Linking a shader program.
Link,
}
/// Shader interface operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WebGlShaderInterfaceOperation {
/// Resolving a uniform.
Uniform,
/// Resolving a uniform block.
UniformBlock,
}
/// WebGL data-transfer operations.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WebGlDataTransferOperation {
/// Uploading texture data.
TextureUpload,
/// Invalidating framebuffer attachments.
FramebufferInvalidation,
}
/// WebGL renderer probe operations.
#[cfg(feature = "probe")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WebGlProbeOperation {
/// Uploading the probe image.
ImageUpload,
/// Reading the probe result back.
Readback,
}
/// Reasons a WebGL context can be incompatible with the renderer.
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum IncompatibleContextReason {
/// Antialiasing is enabled on the WebGL context.
#[error("antialiasing is enabled")]
AntialiasingEnabled,
/// The WebGL context's depth buffer does not have enough bits.
#[error("depth buffer has {actual_bits} bits, but {required_bits} are required")]
InsufficientDepthBuffer {
/// Number of depth bits provided by the WebGL context.
actual_bits: u32,
/// Minimum number of depth bits required by the renderer.
required_bits: u32,
},
}
/// Converts JavaScript exceptions into WebGL operation errors.
pub(crate) trait WebGlResultExt<T> {
/// Attach the WebGL operation that produced this JavaScript result.
fn map_js_error(self, operation: WebGlOperation) -> Result<T, WebGlError>;
}
impl<T> WebGlResultExt<T> for Result<T, JsValue> {
fn map_js_error(self, operation: WebGlOperation) -> Result<T, WebGlError> {
self.map_err(|value| WebGlError::OperationFailed {
operation,
message: Some(value.as_string().unwrap_or_else(|| format!("{value:?}"))),
})
}
}
struct MessageSuffix<'a>(Option<&'a str>);
impl fmt::Display for MessageSuffix<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(message) = self.0 {
write!(f, ": {message}")?;
}
Ok(())
}
}