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`.
 ///