vello_hybrid: Check WebGL errors before returning
diff --git a/sparse_strips/vello_hybrid/src/lib.rs b/sparse_strips/vello_hybrid/src/lib.rs index c754362..a2650ab 100644 --- a/sparse_strips/vello_hybrid/src/lib.rs +++ b/sparse_strips/vello_hybrid/src/lib.rs
@@ -93,6 +93,10 @@ /// A draw referenced a [`TextureId`] that was not provided at render time. #[error("Missing texture binding for {0:?}")] MissingTextureBinding(TextureId), + /// WebGL reported an error after rendering completed. + #[cfg(feature = "webgl")] + #[error("WebGL error: {}", render::webgl_error_name(*.0))] + WebGlError(u32), // TODO: Consider expanding `RenderError` to replace some `.unwrap` and `.expect`. }
diff --git a/sparse_strips/vello_hybrid/src/render/mod.rs b/sparse_strips/vello_hybrid/src/render/mod.rs index 4233ba7..84b3bd5 100644 --- a/sparse_strips/vello_hybrid/src/render/mod.rs +++ b/sparse_strips/vello_hybrid/src/render/mod.rs
@@ -23,6 +23,8 @@ #[cfg(all(feature = "webgl", feature = "probe"))] pub use vello_common::probe::{Probe, ProbeResult}; #[cfg(feature = "webgl")] +pub(crate) use webgl::webgl_error_name; +#[cfg(feature = "webgl")] pub use webgl::{AtlasTextureInfo, WebGlAtlasWriter, WebGlRenderer, WebGlTextureWithDimensions}; #[cfg(feature = "wgpu")] pub use wgpu::{AtlasWriter, RenderTargetConfig, Renderer, TextureBindings};
diff --git a/sparse_strips/vello_hybrid/src/render/probe.rs b/sparse_strips/vello_hybrid/src/render/probe.rs index b1d608b..6eafb3f 100644 --- a/sparse_strips/vello_hybrid/src/render/probe.rs +++ b/sparse_strips/vello_hybrid/src/render/probe.rs
@@ -5,11 +5,11 @@ use crate::render::webgl::resource::Framebuffer; use crate::render::webgl::{ WebGlStateConfig, WebGlStateGuard, create_atlas_texture_array, create_framebuffer_for_texture, - create_texture, + create_texture, webgl_error_name, }; use crate::target::RootTarget; use crate::{RenderError, RenderSize, Scene, WebGlRenderer}; -use alloc::{borrow::Cow, format, sync::Arc}; +use alloc::sync::Arc; use core::ops::Deref; use thiserror::Error; use vello_common::filter_effects::Filter; @@ -254,21 +254,6 @@ } } -fn webgl_error_name(error: u32) -> Cow<'static, str> { - let name = match error { - WebGl2RenderingContext::NO_ERROR => "NO_ERROR", - WebGl2RenderingContext::INVALID_ENUM => "INVALID_ENUM", - WebGl2RenderingContext::INVALID_VALUE => "INVALID_VALUE", - WebGl2RenderingContext::INVALID_OPERATION => "INVALID_OPERATION", - WebGl2RenderingContext::INVALID_FRAMEBUFFER_OPERATION => "INVALID_FRAMEBUFFER_OPERATION", - WebGl2RenderingContext::OUT_OF_MEMORY => "OUT_OF_MEMORY", - WebGl2RenderingContext::CONTEXT_LOST_WEBGL => "CONTEXT_LOST_WEBGL", - _ => return Cow::Owned(format!("UNKNOWN_WEBGL_ERROR ({error:#06x})")), - }; - - Cow::Borrowed(name) -} - impl Drop for WebGlPendingProbe { fn drop(&mut self) { if let Some(sync) = self.sync.take() {
diff --git a/sparse_strips/vello_hybrid/src/render/webgl.rs b/sparse_strips/vello_hybrid/src/render/webgl.rs index d853677..971f9d5 100644 --- a/sparse_strips/vello_hybrid/src/render/webgl.rs +++ b/sparse_strips/vello_hybrid/src/render/webgl.rs
@@ -50,9 +50,7 @@ RootTarget, TextureParity, }, }; -use alloc::sync::Arc; -use alloc::vec; -use alloc::vec::Vec; +use alloc::{borrow::Cow, format, sync::Arc, vec, vec::Vec}; use core::fmt::Debug; #[cfg(feature = "text")] use glifo::{GLYPH_PADDING, PendingClearRect}; @@ -77,6 +75,21 @@ WebGlShader, WebGlTexture, WebGlUniformLocation, WebGlVertexArrayObject, }; +pub(crate) fn webgl_error_name(error: u32) -> Cow<'static, str> { + let name = match error { + WebGl2RenderingContext::NO_ERROR => "NO_ERROR", + WebGl2RenderingContext::INVALID_ENUM => "INVALID_ENUM", + WebGl2RenderingContext::INVALID_VALUE => "INVALID_VALUE", + WebGl2RenderingContext::INVALID_OPERATION => "INVALID_OPERATION", + WebGl2RenderingContext::INVALID_FRAMEBUFFER_OPERATION => "INVALID_FRAMEBUFFER_OPERATION", + WebGl2RenderingContext::OUT_OF_MEMORY => "OUT_OF_MEMORY", + WebGl2RenderingContext::CONTEXT_LOST_WEBGL => "CONTEXT_LOST_WEBGL", + _ => return Cow::Owned(format!("UNKNOWN_WEBGL_ERROR ({error:#06x})")), + }; + + Cow::Borrowed(name) +} + /// Placeholder value for uninitialized GPU encoded paints. const GPU_PAINT_PLACEHOLDER: GpuEncodedPaint = GpuEncodedPaint::LinearGradient(GpuLinearGradient { texture_width_and_extend_mode: 0, @@ -299,6 +312,14 @@ }); } + // Note that this might yield errors that existed before Vello started + // rendering, but still better to err on the side of caution and return + // an error if there is one. + let webgl_error = self.gl.get_error(); + if webgl_error != WebGl2RenderingContext::NO_ERROR { + return Err(RenderError::WebGlError(webgl_error)); + } + Ok(()) }
diff --git a/sparse_strips/vello_sparse_tests/tests/image_atlas.rs b/sparse_strips/vello_sparse_tests/tests/image_atlas.rs index 2e4eb99..18a3337 100644 --- a/sparse_strips/vello_sparse_tests/tests/image_atlas.rs +++ b/sparse_strips/vello_sparse_tests/tests/image_atlas.rs
@@ -5,7 +5,8 @@ use vello_common::pixmap::Pixmap; use vello_hybrid::{ - AtlasConfig, AtlasTextureInfo, MemorySettings, RenderSettings, Resources, WebGlRenderer, + AtlasConfig, AtlasTextureInfo, MemorySettings, RenderError, RenderSettings, RenderSize, + Resources, Scene, WebGlRenderer, }; use wasm_bindgen::JsCast; use wasm_bindgen_test::*; @@ -70,6 +71,36 @@ ); } +#[wasm_bindgen_test] +fn render_returns_queued_webgl_error() { + let document = web_sys::window().unwrap().document().unwrap(); + let canvas = document + .create_element("canvas") + .unwrap() + .dyn_into::<HtmlCanvasElement>() + .unwrap(); + canvas.set_width(100); + canvas.set_height(100); + + let mut renderer = WebGlRenderer::new(&canvas); + let mut resources = Resources::new(); + let scene = Scene::new(100, 100); + let render_size = RenderSize { + width: 100, + height: 100, + }; + + renderer.gl_context().enable(u32::MAX); + + let error = renderer + .render(&scene, &mut resources, &render_size) + .unwrap_err(); + assert!(matches!( + error, + RenderError::WebGlError(WebGl2RenderingContext::INVALID_ENUM) + )); +} + /// Uploading an image that is larger than the configured atlas must fail with /// `AtlasError::TextureTooLarge`. ///