Add better errors in WebGL backend
diff --git a/glifo/src/atlas/cache.rs b/glifo/src/atlas/cache.rs index 9216e43..c9d6d73 100644 --- a/glifo/src/atlas/cache.rs +++ b/glifo/src/atlas/cache.rs
@@ -310,15 +310,20 @@ /// The closure receives each non-empty recorder by mutable reference. /// After the closure returns, the recorder's commands are cleared but /// the allocation is kept for reuse next frame. - pub fn replay_pending_atlas_commands(&mut self, mut f: impl FnMut(&mut AtlasCommandRecorder)) { + pub fn replay_pending_atlas_commands<E>( + &mut self, + mut f: impl FnMut(&mut AtlasCommandRecorder) -> Result<(), E>, + ) -> Result<(), E> { for slot in &mut self.pending_atlas_commands { if let Some(recorder) = slot.as_mut() && !recorder.commands.is_empty() { - f(recorder); + f(recorder)?; recorder.commands.clear(); } } + + Ok(()) } /// Get (or create) the command recorder for the given atlas page.
diff --git a/sparse_strips/vello_cpu/src/text.rs b/sparse_strips/vello_cpu/src/text.rs index e5aebd1..087a429 100644 --- a/sparse_strips/vello_cpu/src/text.rs +++ b/sparse_strips/vello_cpu/src/text.rs
@@ -163,7 +163,7 @@ // Draw all new COLR/outline glyphs into the render context, and then composite them into the // existing atlas page. let glyph_renderer = glyph_resources.glyph_renderer.as_mut(); - glyph_resources + let _: Result<(), core::convert::Infallible> = glyph_resources .glyph_atlas .replay_pending_atlas_commands(|recorder| { let page_index = recorder.page_index as usize; @@ -180,6 +180,8 @@ renderer::replay_atlas_commands(&mut recorder.commands, glyph_renderer); glyph_renderer.flush(); glyph_renderer.composite_to_pixmap_at_offset(&Self::default(), page, 0, 0); + + Ok(()) }); for (page_index, pixmap) in glyph_resources.pixmaps.iter().enumerate() {
diff --git a/sparse_strips/vello_hybrid/examples/native_webgl/src/lib.rs b/sparse_strips/vello_hybrid/examples/native_webgl/src/lib.rs index f1947f2..571e6e7 100644 --- a/sparse_strips/vello_hybrid/examples/native_webgl/src/lib.rs +++ b/sparse_strips/vello_hybrid/examples/native_webgl/src/lib.rs
@@ -28,7 +28,7 @@ impl RendererWrapper { fn new(canvas: HtmlCanvasElement) -> Self { - let renderer = vello_hybrid::WebGlRenderer::new(&canvas); + let renderer = vello_hybrid::WebGlRenderer::new(&canvas).unwrap(); Self { renderer } } @@ -201,14 +201,16 @@ let pixmap1 = ImageScene::read_flower_image(); self.renderer_wrapper .renderer - .upload_image(self.scenes[self.current_scene].resources_mut(), &pixmap1); + .upload_image(self.scenes[self.current_scene].resources_mut(), &pixmap1) + .unwrap(); // 2nd example — uploading from a WebGL texture let pixmap2 = ImageScene::read_cowboy_image(); let texture2 = self.pixmap_to_webgl_texture(&pixmap2); self.renderer_wrapper .renderer - .upload_image(self.scenes[self.current_scene].resources_mut(), &texture2); + .upload_image(self.scenes[self.current_scene].resources_mut(), &texture2) + .unwrap(); self.uploaded_scene_images[self.current_scene] = true; } @@ -472,7 +474,7 @@ .append_child(&canvas) .unwrap(); - let mut renderer = vello_hybrid::WebGlRenderer::new(&canvas); + let mut renderer = vello_hybrid::WebGlRenderer::new(&canvas).unwrap(); let render_size = vello_hybrid::RenderSize { width: width as u32,
diff --git a/sparse_strips/vello_hybrid/src/lib.rs b/sparse_strips/vello_hybrid/src/lib.rs index 4adf208..579fdcf 100644 --- a/sparse_strips/vello_hybrid/src/lib.rs +++ b/sparse_strips/vello_hybrid/src/lib.rs
@@ -62,7 +62,10 @@ pub use render::{AtlasWriter, RenderTargetConfig, Renderer}; pub use render::{Config, GpuStrip, RenderSize}; #[cfg(all(target_arch = "wasm32", feature = "webgl"))] -pub use render::{WebGlAtlasWriter, WebGlRenderer, WebGlTextureWithDimensions}; +pub use render::{ + WebGlAtlasWriter, WebGlError, WebGlInitError, WebGlRenderer, WebGlResourceType, + WebGlTextureWithDimensions, +}; pub use resources::Resources; pub use scene::{RenderSettings, Scene, SceneConstraints}; #[cfg(feature = "text")] @@ -84,12 +87,13 @@ /// TODO: Consider supporting more than a single column of slots in slot textures. #[error("No slots available for rendering")] SlotsExhausted, - /// An allocation error occurred while trying to allocate a new image. This can happen - /// if the scene contains filter layers, which need space in the image atlas for intermediate - /// storage. - #[error("Filter atlas allocation failed: {0}")] + /// An allocation in an image atlas failed. + #[error("Atlas allocation failed: {0}")] AtlasError(#[from] vello_common::multi_atlas::AtlasError), - // TODO: Consider expanding `RenderError` to replace some `.unwrap` and `.expect`. + /// A WebGL operation failed during rendering. + #[cfg(all(target_arch = "wasm32", feature = "webgl"))] + #[error(transparent)] + WebGl(#[from] WebGlError), } #[cfg(test)]
diff --git a/sparse_strips/vello_hybrid/src/render/mod.rs b/sparse_strips/vello_hybrid/src/render/mod.rs index c54aa07..d730d43 100644 --- a/sparse_strips/vello_hybrid/src/render/mod.rs +++ b/sparse_strips/vello_hybrid/src/render/mod.rs
@@ -17,6 +17,9 @@ pub use common::{Config, GpuStrip, RenderSize}; #[cfg(all(target_arch = "wasm32", feature = "webgl"))] -pub use webgl::{WebGlAtlasWriter, WebGlRenderer, WebGlTextureWithDimensions}; +pub use webgl::{ + WebGlAtlasWriter, WebGlError, WebGlInitError, WebGlRenderer, WebGlResourceType, + WebGlTextureWithDimensions, +}; #[cfg(feature = "wgpu")] pub use wgpu::{AtlasWriter, RenderTargetConfig, Renderer};
diff --git a/sparse_strips/vello_hybrid/src/render/webgl.rs b/sparse_strips/vello_hybrid/src/render/webgl.rs index 5fd8532..b2ac537 100644 --- a/sparse_strips/vello_hybrid/src/render/webgl.rs +++ b/sparse_strips/vello_hybrid/src/render/webgl.rs
@@ -40,15 +40,19 @@ LoadOp, RendererBackend, RootRenderTarget, Scheduler, SchedulerState, StripPassRenderTarget, }, }; +use alloc::format; +use alloc::string::String; use alloc::sync::Arc; use alloc::vec; use alloc::vec::Vec; use bytemuck::{Pod, Zeroable}; -use core::fmt::Debug; +use core::fmt::{self, Debug}; #[cfg(feature = "text")] use glifo::{GLYPH_PADDING, PendingClearRect}; +use thiserror::Error; use vello_common::image_cache::{ImageCache, ImageResource}; use vello_common::multi_atlas::{AtlasConfig, AtlasId}; +use vello_common::paint::ImageId; use vello_common::render_graph::LayerId; use vello_common::{ coarse::WideTile, @@ -61,7 +65,7 @@ use vello_sparse_shaders::{clear_slots, filters, render_strips}; use web_sys::wasm_bindgen::{JsCast, JsValue}; use web_sys::{ - WebGl2RenderingContext, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlTexture, + WebGl2RenderingContext, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlShader, WebGlTexture, WebGlUniformLocation, WebGlVertexArrayObject, }; @@ -72,19 +76,145 @@ transform: [0.0; 6], }); +/// Errors that can occur while interacting with WebGL. +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum WebGlError { + #[error("failed to create WebGL resource `{resource}`")] + ResourceCreation { resource: WebGlResourceType }, + #[error("{stage} shader compilation failed: {log}")] + ShaderCompile { stage: &'static str, log: String }, + #[error("program link failed: {log}")] + ProgramLink { log: String }, + #[error("missing uniform `{name}` in program `{program}`")] + MissingUniform { + program: &'static str, + name: &'static str, + }, + #[error("missing uniform block `{name}` in program `{program}`")] + MissingUniformBlock { + program: &'static str, + name: &'static str, + }, + #[error("WebGL operation `{op}` failed: {detail}")] + Operation { op: &'static str, detail: String }, +} + +/// A WebGL resource type. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum WebGlResourceType { + /// A WebGL buffer object. + Buffer, + /// A fragment shader object. + FragmentShader, + /// A framebuffer object. + Framebuffer, + /// A linked shader program object. + Program, + /// A texture object. + Texture, + /// A vertex array object. + VertexArray, + /// A vertex shader object. + VertexShader, +} + +impl fmt::Display for WebGlResourceType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let name = match self { + Self::Buffer => "buffer", + Self::FragmentShader => "fragment_shader", + Self::Framebuffer => "framebuffer", + Self::Program => "program", + Self::Texture => "texture", + Self::VertexArray => "vertex_array", + Self::VertexShader => "vertex_shader", + }; + f.write_str(name) + } +} + +/// Errors that can occur while creating the WebGL renderer. +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum WebGlInitError { + #[error("failed to get `webgl2` context: {detail}")] + GetContext { detail: String }, + #[error("`webgl2` is unavailable on this canvas/browser")] + WebGl2Unavailable, + #[error("returned context was not a `WebGl2RenderingContext`")] + WrongContextType, + #[error("WebGL context was created with `antialias: true`")] + AntialiasEnabled, + #[error(transparent)] + WebGl(#[from] WebGlError), +} + +fn js_value_to_string(value: JsValue) -> String { + value.as_string().unwrap_or_else(|| format!("{value:?}")) +} + +fn webgl_operation_error(op: &'static str, value: JsValue) -> WebGlError { + WebGlError::Operation { + op, + detail: js_value_to_string(value), + } +} + +fn missing_webgl_resource(resource: WebGlResourceType) -> WebGlError { + WebGlError::ResourceCreation { resource } +} + +fn create_webgl_buffer(gl: &WebGl2RenderingContext) -> Result<WebGlBuffer, WebGlError> { + gl.create_buffer() + .ok_or(missing_webgl_resource(WebGlResourceType::Buffer)) +} + +fn create_webgl_fragment_shader(gl: &WebGl2RenderingContext) -> Result<WebGlShader, WebGlError> { + gl.create_shader(WebGl2RenderingContext::FRAGMENT_SHADER) + .ok_or(missing_webgl_resource(WebGlResourceType::FragmentShader)) +} + +fn create_webgl_framebuffer(gl: &WebGl2RenderingContext) -> Result<WebGlFramebuffer, WebGlError> { + gl.create_framebuffer() + .ok_or(missing_webgl_resource(WebGlResourceType::Framebuffer)) +} + +fn create_webgl_program(gl: &WebGl2RenderingContext) -> Result<WebGlProgram, WebGlError> { + gl.create_program() + .ok_or(missing_webgl_resource(WebGlResourceType::Program)) +} + +fn create_webgl_texture(gl: &WebGl2RenderingContext) -> Result<WebGlTexture, WebGlError> { + gl.create_texture() + .ok_or(missing_webgl_resource(WebGlResourceType::Texture)) +} + +fn create_webgl_vertex_array( + gl: &WebGl2RenderingContext, +) -> Result<WebGlVertexArrayObject, WebGlError> { + gl.create_vertex_array() + .ok_or(missing_webgl_resource(WebGlResourceType::VertexArray)) +} + +fn create_webgl_vertex_shader(gl: &WebGl2RenderingContext) -> Result<WebGlShader, WebGlError> { + gl.create_shader(WebGl2RenderingContext::VERTEX_SHADER) + .ok_or(missing_webgl_resource(WebGlResourceType::VertexShader)) +} + /// Query the WebGL context for the max texture size. fn get_max_texture_dimension_2d(gl: &WebGl2RenderingContext) -> u32 { gl.get_parameter(WebGl2RenderingContext::MAX_TEXTURE_SIZE) - .unwrap() + .expect("MAX_TEXTURE_SIZE query should not fail") .as_f64() - .unwrap() as u32 + .expect("MAX_TEXTURE_SIZE should be numeric") as u32 } fn get_max_texture_array_layers(gl: &WebGl2RenderingContext) -> u32 { gl.get_parameter(WebGl2RenderingContext::MAX_ARRAY_TEXTURE_LAYERS) - .unwrap() + .expect("MAX_ARRAY_TEXTURE_LAYERS query should not fail") .as_f64() - .unwrap() as u32 + .expect("MAX_ARRAY_TEXTURE_LAYERS should be numeric") as u32 } /// Vello Hybrid's WebGL2 Renderer. @@ -113,25 +243,31 @@ impl WebGlRenderer { /// Creates a new WebGL2 renderer - pub fn new(canvas: &web_sys::HtmlCanvasElement) -> Self { + pub fn new(canvas: &web_sys::HtmlCanvasElement) -> Result<Self, WebGlInitError> { Self::new_with(canvas, RenderSettings::default()) } /// Creates a new WebGL2 renderer with specific settings. - pub fn new_with(canvas: &web_sys::HtmlCanvasElement, settings: RenderSettings) -> Self { + pub fn new_with( + canvas: &web_sys::HtmlCanvasElement, + settings: RenderSettings, + ) -> Result<Self, WebGlInitError> { super::common::maybe_warn_about_webgl_feature_conflict(); // We do our own anti-aliasing, so no need to enable it in the WebGL // context. let context_options = js_sys::Object::new(); - js_sys::Reflect::set(&context_options, &"antialias".into(), &JsValue::FALSE).unwrap(); + js_sys::Reflect::set(&context_options, &"antialias".into(), &JsValue::FALSE) + .expect("setting WebGL antialias option should not fail"); let gl = canvas .get_context_with_context_options("webgl2", &context_options) - .expect("WebGL2 context to be available") - .unwrap() + .map_err(|err| WebGlInitError::GetContext { + detail: js_value_to_string(err), + })? + .ok_or(WebGlInitError::WebGl2Unavailable)? .dyn_into::<WebGl2RenderingContext>() - .expect("Context to be a WebGL2 context"); + .expect("WebGL2 context has the wrong type"); #[cfg(debug_assertions)] { @@ -139,15 +275,17 @@ // creating a new one with the correct context_options set. // See this comment for why we still care about non-antialiased context: // https://github.com/linebender/vello/pull/1546/changes#r3008692535 - let context_attributes = gl.get_context_attributes().unwrap(); + let context_attributes = gl + .get_context_attributes() + .expect("WebGL context attributes should be available"); let antialias = js_sys::Reflect::get(&context_attributes, &"antialias".into()) - .unwrap() + .expect("WebGL context attributes should expose `antialias`") .as_bool() - .unwrap(); - debug_assert!( - !antialias, - "WebGL context must be created with `antialias: false` for vello_hybrid to work correctly." - ); + .expect("WebGL `antialias` attribute should be a boolean"); + + if antialias { + return Err(WebGlInitError::AntialiasEnabled); + } } let mut settings = settings; @@ -167,8 +305,8 @@ let gradient_cache = GradientRampCache::new(max_gradient_cache_size, settings.level); let filter_context = FilterContext::new(settings.atlas_config); - Self { - programs: WebGlPrograms::new(gl.clone(), &image_cache, &filter_context, total_slots), + Ok(Self { + programs: WebGlPrograms::new(gl.clone(), &image_cache, &filter_context, total_slots)?, scheduler: Scheduler::new(total_slots), scheduler_state: SchedulerState::default(), gl, @@ -178,7 +316,7 @@ filter_context, filter_pass_state: FilterPassState::default(), dummy_image_cache: Some(ImageCache::new_dummy()), - } + }) } /// Render `scene` using WebGL2 @@ -204,9 +342,7 @@ resources.before_render( self, |renderer, glyph_renderer, atlas_count, atlas_config, atlas_id| { - renderer - .render_to_atlas(glyph_renderer, atlas_count, atlas_config, atlas_id) - .expect("Failed to render glyphs to atlas"); + renderer.render_to_atlas(glyph_renderer, atlas_count, atlas_config, atlas_id) }, |renderer, image_cache, upload, dst_x, dst_y| { renderer.write_to_atlas( @@ -214,9 +350,9 @@ upload.image_id, &upload.pixmap, Some([dst_x, dst_y]), - ); + ) }, - ); + )?; } self.render_scene( @@ -260,7 +396,7 @@ atlas_id: AtlasId, ) -> Result<(), RenderError> { self.programs - .maybe_resize_atlas_texture_array(&self.gl, atlas_count); + .maybe_resize_atlas_texture_array(&self.gl, atlas_count)?; let (atlas_width, atlas_height) = atlas_config.atlas_size; let atlas_render_size = RenderSize { @@ -273,7 +409,7 @@ .resources .atlas_render_framebuffer .take() - .unwrap_or_else(|| self.gl.create_framebuffer().unwrap()); + .map_or_else(|| create_webgl_framebuffer(&self.gl), Ok)?; self.gl.bind_framebuffer( WebGl2RenderingContext::FRAMEBUFFER, Some(&atlas_framebuffer), @@ -355,11 +491,11 @@ self.prepare_gpu_encoded_paints(&encoded_paints, image_cache); self.programs - .maybe_resize_atlas_texture_array(&self.gl, image_cache.atlas_count() as u32); + .maybe_resize_atlas_texture_array(&self.gl, image_cache.atlas_count() as u32)?; self.programs.maybe_resize_filter_atlas_textures( &self.gl, self.filter_context.image_cache.atlas_count() as u32, - ); + )?; // TODO: For the time being, we upload the entire alpha buffer as one big chunk. As a future // refinement, we could have a bounded alpha buffer, and break draws when the alpha @@ -372,7 +508,7 @@ render_size, &self.paint_idxs, &self.filter_context, - ); + )?; if clear { self.programs.clear_view_framebuffer(&self.gl); @@ -415,7 +551,7 @@ &mut self, resources: &mut Resources, writer: &T, - ) -> vello_common::paint::ImageId { + ) -> Result<ImageId, RenderError> { self.upload_image_with(&mut resources.image_cache, writer, IMAGE_PADDING) } @@ -424,12 +560,13 @@ image_cache: &mut ImageCache, writer: &T, padding: u16, - ) -> vello_common::paint::ImageId { + ) -> Result<ImageId, RenderError> { let width = writer.width(); let height = writer.height(); - let image_id = image_cache.allocate(width, height, padding).unwrap(); - self.write_to_atlas(image_cache, image_id, writer, None); - image_id + let image_id = image_cache.allocate(width, height, padding)?; + self.write_to_atlas(image_cache, image_id, writer, None)?; + + Ok(image_id) } /// Write pixel data to an existing atlas allocation. @@ -444,14 +581,16 @@ pub(crate) fn write_to_atlas<T: WebGlAtlasWriter>( &mut self, image_cache: &ImageCache, - image_id: vello_common::paint::ImageId, + image_id: ImageId, writer: &T, offset_override: Option<[u32; 2]>, - ) { - let image_resource = image_cache.get(image_id).expect("Image resource not found"); + ) -> Result<(), RenderError> { + let image_resource = image_cache + .get(image_id) + .expect("image resource should exist before atlas upload"); self.programs - .maybe_resize_atlas_texture_array(&self.gl, image_cache.atlas_count() as u32); + .maybe_resize_atlas_texture_array(&self.gl, image_cache.atlas_count() as u32)?; let offset = offset_override.unwrap_or([ image_resource.offset[0] as u32, image_resource.offset[1] as u32, @@ -463,15 +602,13 @@ offset, writer.width(), writer.height(), - ); + )?; + + Ok(()) } /// Destroy an image from the cache and clear the allocated slot in the atlas. - pub fn destroy_image( - &mut self, - resources: &mut Resources, - image_id: vello_common::paint::ImageId, - ) { + pub fn destroy_image(&mut self, resources: &mut Resources, image_id: ImageId) { if let Some(image_resource) = resources.image_cache.deallocate(image_id) { let padding = image_resource.padding as u32; self.clear_atlas_region( @@ -856,25 +993,25 @@ image_cache: &ImageCache, filter_context: &FilterContext, slot_count: usize, - ) -> Self { + ) -> Result<Self, WebGlInitError> { let strip_program = create_shader_program( &gl, render_strips::VERTEX_SOURCE, render_strips::FRAGMENT_SOURCE, - ); + )?; let clear_program = create_shader_program( &gl, clear_slots::VERTEX_SOURCE, clear_slots::FRAGMENT_SOURCE, - ); + )?; let filter_program = - create_shader_program(&gl, filters::VERTEX_SOURCE, filters::FRAGMENT_SOURCE); - let filter_uniforms = get_filter_pass_uniforms(&gl, &filter_program); + create_shader_program(&gl, filters::VERTEX_SOURCE, filters::FRAGMENT_SOURCE)?; + let filter_uniforms = get_filter_pass_uniforms(&gl, &filter_program)?; - let strip_uniforms = get_strip_uniforms(&gl, &strip_program); - let clear_uniforms = get_clear_uniforms(&gl, &clear_program); + let strip_uniforms = get_strip_uniforms(&gl, &strip_program)?; + let clear_uniforms = get_clear_uniforms(&gl, &clear_program)?; - let resources = create_webgl_resources(&gl, image_cache, filter_context, slot_count); + let resources = create_webgl_resources(&gl, image_cache, filter_context, slot_count)?; initialize_strip_vao(&gl, &resources); initialize_clear_vao(&gl, &resources); @@ -888,7 +1025,7 @@ WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA, ); - Self { + Ok(Self { strip_program, clear_program, filter_program, @@ -903,7 +1040,7 @@ negate_ndc: false, encoded_paints_data, filter_data: Vec::new(), - } + }) } /// Prepare resources for rendering. @@ -916,7 +1053,7 @@ render_size: &RenderSize, paint_idxs: &[u32], filter_context: &FilterContext, - ) { + ) -> Result<(), WebGlError> { let max_texture_dimension_2d = self.resources.max_texture_dimension_2d; self.maybe_resize_alphas_tex(max_texture_dimension_2d, alphas.len()); @@ -924,15 +1061,17 @@ self.maybe_resize_filter_data_tex(filter_context); self.maybe_update_config_buffer(gl, max_texture_dimension_2d, render_size); - self.upload_alpha_texture(gl, alphas); - self.upload_encoded_paints_texture(gl, encoded_paints); - self.upload_filter_data_texture(gl, filter_context); + self.upload_alpha_texture(gl, alphas)?; + self.upload_encoded_paints_texture(gl, encoded_paints)?; + self.upload_filter_data_texture(gl, filter_context)?; if gradient_cache.has_changed() { self.maybe_resize_gradient_tex(gl, max_texture_dimension_2d, gradient_cache); - self.upload_gradient_texture(gl, gradient_cache); + self.upload_gradient_texture(gl, gradient_cache)?; gradient_cache.mark_synced(); } + + Ok(()) } /// Resize atlas texture array to accommodate more atlases. @@ -940,7 +1079,7 @@ &mut self, gl: &WebGl2RenderingContext, required_atlas_count: u32, - ) { + ) -> Result<(), WebGlError> { let WebGlTextureSize { width, height, @@ -949,10 +1088,10 @@ if required_atlas_count > current_atlas_count { // Create new texture array with more layers let new_atlas_texture_array = - create_atlas_texture_array(gl, width, height, required_atlas_count); + create_atlas_texture_array(gl, width, height, required_atlas_count)?; // Copy existing atlas data from old texture array to new one - self.copy_atlas_texture_data(gl, &new_atlas_texture_array, current_atlas_count); + self.copy_atlas_texture_data(gl, &new_atlas_texture_array, current_atlas_count)?; // Replace the old resources self.resources.atlas_texture_array = new_atlas_texture_array; @@ -964,6 +1103,8 @@ gl.delete_framebuffer(Some(&fb)); } } + + Ok(()) } /// Copy texture data from the old atlas texture array to a new one. @@ -973,7 +1114,7 @@ gl: &WebGl2RenderingContext, new_atlas_texture_array: &WebGlTextureArray, layer_count_to_copy: u32, - ) { + ) -> Result<(), WebGlError> { let WebGlTextureSize { width, height, .. } = self.resources.atlas_texture_array.size(); // Copy each layer from the old atlas to the new one @@ -994,8 +1135,10 @@ layer, [0, 0], [width, height], - ); + )?; } + + Ok(()) } fn maybe_resize_filter_data_tex(&mut self, filter_context: &FilterContext) { @@ -1018,9 +1161,9 @@ &mut self, gl: &WebGl2RenderingContext, filter_context: &FilterContext, - ) { + ) -> Result<(), WebGlError> { if filter_context.is_empty() { - return; + return Ok(()); } let width = self.resources.max_texture_dimension_2d; @@ -1044,14 +1187,15 @@ WebGl2RenderingContext::UNSIGNED_INT, Some(&packed_array), ) - .unwrap(); + .map_err(|err| webgl_operation_error("tex_image_2d(filter_data)", err))?; + Ok(()) } fn maybe_resize_filter_atlas_textures( &mut self, gl: &WebGl2RenderingContext, required_count: u32, - ) { + ) -> Result<(), WebGlError> { let current_count = self.resources.filter_atlas_textures.len() as u32; // TODO: Same as wgpu, should we be destroying // textures if they aren't needed anymore? @@ -1059,12 +1203,14 @@ let width = self.resources.filter_atlas_width; let height = self.resources.filter_atlas_height; for _ in current_count..required_count { - let tex = create_filter_atlas_texture(gl, width, height); - let fb = create_framebuffer_for_texture(gl, &tex); + let tex = create_filter_atlas_texture(gl, width, height)?; + let fb = create_framebuffer_for_texture(gl, &tex)?; self.resources.filter_atlas_textures.push(tex); self.resources.filter_atlas_framebuffers.push(fb); } } + + Ok(()) } fn clear_filter_atlas_textures(&self, gl: &WebGl2RenderingContext) { @@ -1265,9 +1411,13 @@ } /// Upload alpha data to the texture. - fn upload_alpha_texture(&mut self, gl: &WebGl2RenderingContext, alphas: &mut Vec<u8>) { + fn upload_alpha_texture( + &mut self, + gl: &WebGl2RenderingContext, + alphas: &mut Vec<u8>, + ) -> Result<(), WebGlError> { if alphas.is_empty() { - return; + return Ok(()); } let alpha_texture_width = self.resources.max_texture_dimension_2d; @@ -1290,10 +1440,11 @@ bytemuck::cast_slice::<u8, u32>(alphas), alpha_texture_width, alpha_texture_height, - ); + )?; // Truncate back to the original size. alphas.truncate(original_len); + Ok(()) } /// Upload encoded paints to the texture. @@ -1301,7 +1452,7 @@ &mut self, gl: &WebGl2RenderingContext, encoded_paints: &[GpuEncodedPaint], - ) { + ) -> Result<(), WebGlError> { if !encoded_paints.is_empty() { let encoded_paints_texture_width = self.resources.max_texture_dimension_2d; let encoded_paints_texture_height = self.resources.encoded_paints_texture_height; @@ -1319,8 +1470,10 @@ bytemuck::cast_slice::<u8, u32>(&self.encoded_paints_data), encoded_paints_texture_width, encoded_paints_texture_height, - ); + )?; } + + Ok(()) } /// Upload gradient data to the texture. @@ -1328,9 +1481,9 @@ &mut self, gl: &WebGl2RenderingContext, gradient_cache: &mut GradientRampCache, - ) { + ) -> Result<(), WebGlError> { if gradient_cache.is_empty() { - return; + return Ok(()); } let gradient_texture_width = self.resources.max_texture_dimension_2d; @@ -1358,10 +1511,11 @@ WebGl2RenderingContext::UNSIGNED_BYTE, Some(&luts), ) - .unwrap(); + .map_err(|err| webgl_operation_error("tex_image_2d(gradient)", err))?; // Restore the luts back to the cache. gradient_cache.restore_luts(luts); + Ok(()) } /// Clear the view framebuffer. @@ -1570,11 +1724,9 @@ gl: &WebGl2RenderingContext, vertex_src: &str, fragment_src: &str, -) -> WebGlProgram { +) -> Result<WebGlProgram, WebGlError> { // Compile vertex shader. - let vertex_shader = gl - .create_shader(WebGl2RenderingContext::VERTEX_SHADER) - .unwrap(); + let vertex_shader = create_webgl_vertex_shader(gl)?; gl.shader_source(&vertex_shader, vertex_src); gl.compile_shader(&vertex_shader); @@ -1586,13 +1738,14 @@ let info = gl .get_shader_info_log(&vertex_shader) .unwrap_or_else(|| "Unknown error creating vertex shader".into()); - panic!("Failed to compile vertex shader: {info}"); + return Err(WebGlError::ShaderCompile { + stage: "vertex", + log: info, + }); } // Compile fragment shader. - let fragment_shader = gl - .create_shader(WebGl2RenderingContext::FRAGMENT_SHADER) - .unwrap(); + let fragment_shader = create_webgl_fragment_shader(gl)?; gl.shader_source(&fragment_shader, fragment_src); gl.compile_shader(&fragment_shader); @@ -1604,11 +1757,14 @@ let info = gl .get_shader_info_log(&fragment_shader) .unwrap_or_else(|| "Unknown error creating fragment shader".into()); - panic!("Failed to compile fragment shader: {info}"); + return Err(WebGlError::ShaderCompile { + stage: "fragment", + log: info, + }); } // Create and link the program. - let program = gl.create_program().unwrap(); + let program = create_webgl_program(gl)?; gl.attach_shader(&program, &vertex_shader); gl.attach_shader(&program, &fragment_shader); gl.link_program(&program); @@ -1621,33 +1777,38 @@ let info = gl .get_program_info_log(&program) .unwrap_or_else(|| "Unknown error creating program".into()); - panic!("Failed to link program: {info}"); + return Err(WebGlError::ProgramLink { log: info }); } gl.delete_shader(Some(&vertex_shader)); gl.delete_shader(Some(&fragment_shader)); - program + Ok(program) } /// Get the uniform locations for the `render_strips` program. -fn get_strip_uniforms(gl: &WebGl2RenderingContext, program: &WebGlProgram) -> StripUniforms { +fn get_strip_uniforms( + gl: &WebGl2RenderingContext, + program: &WebGlProgram, +) -> Result<StripUniforms, WebGlError> { let config_vs_name = render_strips::vertex::CONFIG; let config_vs_block_index = gl.get_uniform_block_index(program, config_vs_name); let config_fs_name = render_strips::fragment::CONFIG; let config_fs_block_index = gl.get_uniform_block_index(program, config_fs_name); - debug_assert_ne!( - config_vs_block_index, - WebGl2RenderingContext::INVALID_INDEX, - "invalid uniform index" - ); - debug_assert_ne!( - config_fs_block_index, - WebGl2RenderingContext::INVALID_INDEX, - "invalid uniform index" - ); + if config_vs_block_index == WebGl2RenderingContext::INVALID_INDEX { + return Err(WebGlError::MissingUniformBlock { + program: "render_strips", + name: config_vs_name, + }); + } + if config_fs_block_index == WebGl2RenderingContext::INVALID_INDEX { + return Err(WebGlError::MissingUniformBlock { + program: "render_strips", + name: config_fs_name, + }); + } // Bind uniform blocks to binding points. gl.uniform_block_binding(program, config_vs_block_index, 0); @@ -1661,73 +1822,104 @@ let encoded_paints_texture_vs_name = render_strips::vertex::ENCODED_PAINTS_TEXTURE; let gradient_texture_name = render_strips::fragment::GRADIENT_TEXTURE; - StripUniforms { + Ok(StripUniforms { config_vs_block_index, config_fs_block_index, alphas_texture: gl .get_uniform_location(program, alphas_texture_name) - .unwrap(), + .ok_or(WebGlError::MissingUniform { + program: "render_strips", + name: alphas_texture_name, + })?, clip_input_texture: gl .get_uniform_location(program, clip_input_texture_name) - .unwrap(), + .ok_or(WebGlError::MissingUniform { + program: "render_strips", + name: clip_input_texture_name, + })?, atlas_texture_array: gl .get_uniform_location(program, atlas_texture_array_name) - .unwrap(), + .ok_or(WebGlError::MissingUniform { + program: "render_strips", + name: atlas_texture_array_name, + })?, encoded_paints_texture_fs: gl .get_uniform_location(program, encoded_paints_texture_fs_name) - .unwrap(), + .ok_or(WebGlError::MissingUniform { + program: "render_strips", + name: encoded_paints_texture_fs_name, + })?, encoded_paints_texture_vs: gl .get_uniform_location(program, encoded_paints_texture_vs_name) - .unwrap(), + .ok_or(WebGlError::MissingUniform { + program: "render_strips", + name: encoded_paints_texture_vs_name, + })?, gradient_texture: gl .get_uniform_location(program, gradient_texture_name) - .unwrap(), - } + .ok_or(WebGlError::MissingUniform { + program: "render_strips", + name: gradient_texture_name, + })?, + }) } /// Get the uniform locations for the `clear_slots` program. -fn get_clear_uniforms(gl: &WebGl2RenderingContext, program: &WebGlProgram) -> ClearUniforms { +fn get_clear_uniforms( + gl: &WebGl2RenderingContext, + program: &WebGlProgram, +) -> Result<ClearUniforms, WebGlError> { let config_name = clear_slots::vertex::CONFIG; let config_block_index = gl.get_uniform_block_index(program, config_name); - debug_assert_ne!( - config_block_index, - WebGl2RenderingContext::INVALID_INDEX, - "invalid uniform index" - ); + if config_block_index == WebGl2RenderingContext::INVALID_INDEX { + return Err(WebGlError::MissingUniformBlock { + program: "clear_slots", + name: config_name, + }); + } // Bind uniform block to binding point. gl.uniform_block_binding(program, config_block_index, 0); - ClearUniforms { config_block_index } + Ok(ClearUniforms { config_block_index }) } fn get_filter_pass_uniforms( gl: &WebGl2RenderingContext, program: &WebGlProgram, -) -> FilterPassUniforms { +) -> Result<FilterPassUniforms, WebGlError> { let filter_data = gl .get_uniform_location(program, filters::fragment::FILTER_DATA) - .unwrap(); + .ok_or(WebGlError::MissingUniform { + program: "filters", + name: filters::fragment::FILTER_DATA, + })?; let in_tex = gl .get_uniform_location(program, filters::fragment::IN_TEX) - .unwrap(); + .ok_or(WebGlError::MissingUniform { + program: "filters", + name: filters::fragment::IN_TEX, + })?; let original_tex = gl .get_uniform_location(program, filters::fragment::ORIGINAL_TEX) - .unwrap(); - FilterPassUniforms { + .ok_or(WebGlError::MissingUniform { + program: "filters", + name: filters::fragment::ORIGINAL_TEX, + })?; + Ok(FilterPassUniforms { filter_data, in_tex, original_tex, - } + }) } fn create_filter_atlas_texture( gl: &WebGl2RenderingContext, width: u32, height: u32, -) -> WebGlTexture { - let texture = gl.create_texture().unwrap(); +) -> Result<WebGlTexture, WebGlError> { + let texture = create_webgl_texture(gl)?; gl.active_texture(WebGl2RenderingContext::TEXTURE0); gl.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&texture)); gl.tex_parameteri( @@ -1761,8 +1953,8 @@ WebGl2RenderingContext::UNSIGNED_BYTE, None, ) - .unwrap(); - texture + .map_err(|err| webgl_operation_error("tex_image_2d(filter_atlas)", err))?; + Ok(texture) } /// Vertex attribute layout for [`FilterInstanceData`]. @@ -1804,18 +1996,21 @@ } /// Create a texture with nearest neighbor sampling and clamp-to-edge wrapping. -fn create_texture(gl: &WebGl2RenderingContext) -> WebGlTexture { +fn create_texture(gl: &WebGl2RenderingContext) -> Result<WebGlTexture, WebGlError> { create_texture_inner(gl, WebGl2RenderingContext::TEXTURE_2D) } /// Create a texture array with nearest neighbor sampling and /// clamp-to-edge wrapping. -fn create_texture_array(gl: &WebGl2RenderingContext) -> WebGlTexture { +fn create_texture_array(gl: &WebGl2RenderingContext) -> Result<WebGlTexture, WebGlError> { create_texture_inner(gl, WebGl2RenderingContext::TEXTURE_2D_ARRAY) } -fn create_texture_inner(gl: &WebGl2RenderingContext, target: u32) -> WebGlTexture { - let texture = gl.create_texture().unwrap(); +fn create_texture_inner( + gl: &WebGl2RenderingContext, + target: u32, +) -> Result<WebGlTexture, WebGlError> { + let texture = create_webgl_texture(gl)?; gl.active_texture(WebGl2RenderingContext::TEXTURE0); gl.bind_texture(target, Some(&texture)); // The filter and wrap modes are irrelevant because the shader @@ -1845,7 +2040,7 @@ // `NEAREST` for both filters. gl.tex_parameteri(target, WebGl2RenderingContext::TEXTURE_MAX_LEVEL, 0); - texture + Ok(texture) } /// Create all WebGL resources needed for rendering. @@ -1854,20 +2049,20 @@ image_cache: &ImageCache, filter_context: &FilterContext, slot_count: usize, -) -> WebGlResources { - let strip_vao = gl.create_vertex_array().unwrap(); - let clear_vao = gl.create_vertex_array().unwrap(); - let filter_vao = gl.create_vertex_array().unwrap(); - let filter_instance_buffer = gl.create_buffer().unwrap(); +) -> Result<WebGlResources, WebGlError> { + let strip_vao = create_webgl_vertex_array(gl)?; + let clear_vao = create_webgl_vertex_array(gl)?; + let filter_vao = create_webgl_vertex_array(gl)?; + let filter_instance_buffer = create_webgl_buffer(gl)?; - let strips_buffer = gl.create_buffer().unwrap(); - let view_config_buffer = gl.create_buffer().unwrap(); - let slot_config_buffer = gl.create_buffer().unwrap(); - let clear_slot_indices_buffer = gl.create_buffer().unwrap(); - let clear_config_buffer = gl.create_buffer().unwrap(); + let strips_buffer = create_webgl_buffer(gl)?; + let view_config_buffer = create_webgl_buffer(gl)?; + let slot_config_buffer = create_webgl_buffer(gl)?; + let clear_slot_indices_buffer = create_webgl_buffer(gl)?; + let clear_config_buffer = create_webgl_buffer(gl)?; // Create and configure alpha texture. - let alphas_texture = create_texture(gl); + let alphas_texture = create_texture(gl)?; let AtlasConfig { atlas_size: (atlas_width, atlas_height), @@ -1875,40 +2070,40 @@ .. } = image_cache.atlas_manager().config(); let atlas_texture_array = - create_atlas_texture_array(gl, *atlas_width, *atlas_height, *initial_atlas_count as u32); + create_atlas_texture_array(gl, *atlas_width, *atlas_height, *initial_atlas_count as u32)?; // Create a 1x1 stub atlas texture array for use during render_to_atlas. // This avoids binding the real atlas as a shader input while it is the render target. - let stub_atlas_texture_array = create_atlas_texture_array(gl, 1, 1, 1); + let stub_atlas_texture_array = create_atlas_texture_array(gl, 1, 1, 1)?; // Create and configure encoded paints texture. - let encoded_paints_texture = create_texture(gl); + let encoded_paints_texture = create_texture(gl)?; // Create and configure gradient texture. - let gradient_texture = create_texture(gl); + let gradient_texture = create_texture(gl)?; // Create slot textures and framebuffers. let slot_textures: [WebGlTexture; 2] = [ - create_slot_texture(gl, slot_count), - create_slot_texture(gl, slot_count), + create_slot_texture(gl, slot_count)?, + create_slot_texture(gl, slot_count)?, ]; let slot_framebuffers: [WebGlFramebuffer; 2] = [ - create_framebuffer_for_texture(gl, &slot_textures[0]), - create_framebuffer_for_texture(gl, &slot_textures[1]), + create_framebuffer_for_texture(gl, &slot_textures[0])?, + create_framebuffer_for_texture(gl, &slot_textures[1])?, ]; let max_texture_dimension_2d = get_max_texture_dimension_2d(gl); - let filter_data_texture = create_texture(gl); - let filter_config_buffer = gl.create_buffer().unwrap(); + let filter_data_texture = create_texture(gl)?; + let filter_config_buffer = create_webgl_buffer(gl)?; let AtlasConfig { atlas_size: (filter_atlas_width, filter_atlas_height), .. } = filter_context.image_cache.atlas_manager().config(); - WebGlResources { + Ok(WebGlResources { strip_vao, strips_buffer, alphas_texture, @@ -1939,7 +2134,7 @@ filter_config_buffer, filter_atlas_width: *filter_atlas_width, filter_atlas_height: *filter_atlas_height, - } + }) } /// Create an atlas texture array. @@ -1948,8 +2143,8 @@ width: u32, height: u32, layer_count: u32, -) -> WebGlTextureArray { - let atlas_texture = create_texture_array(gl); +) -> Result<WebGlTextureArray, WebGlError> { + let atlas_texture = create_texture_array(gl)?; // Initialize with empty texture array data gl.tex_image_3d_with_opt_u8_array( @@ -1964,14 +2159,22 @@ WebGl2RenderingContext::UNSIGNED_BYTE, None, ) - .unwrap(); + .map_err(|err| webgl_operation_error("tex_image_3d(atlas_texture_array)", err))?; - WebGlTextureArray::new(atlas_texture, width, height, layer_count) + Ok(WebGlTextureArray::new( + atlas_texture, + width, + height, + layer_count, + )) } /// Create a texture for slot rendering. -fn create_slot_texture(gl: &WebGl2RenderingContext, slot_count: usize) -> WebGlTexture { - let texture = create_texture(gl); +fn create_slot_texture( + gl: &WebGl2RenderingContext, + slot_count: usize, +) -> Result<WebGlTexture, WebGlError> { + let texture = create_texture(gl)?; gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_array_buffer_view( WebGl2RenderingContext::TEXTURE_2D, @@ -1984,17 +2187,17 @@ WebGl2RenderingContext::UNSIGNED_BYTE, None, ) - .unwrap(); + .map_err(|err| webgl_operation_error("tex_image_2d(slot_texture)", err))?; - texture + Ok(texture) } /// Create a framebuffer for a texture. fn create_framebuffer_for_texture( gl: &WebGl2RenderingContext, texture: &WebGlTexture, -) -> WebGlFramebuffer { - let framebuffer = gl.create_framebuffer().unwrap(); +) -> Result<WebGlFramebuffer, WebGlError> { + let framebuffer = create_webgl_framebuffer(gl)?; gl.bind_framebuffer(WebGl2RenderingContext::FRAMEBUFFER, Some(&framebuffer)); gl.framebuffer_texture_2d( @@ -2004,8 +2207,7 @@ Some(texture), 0, ); - - framebuffer + Ok(framebuffer) } /// Initialize strip VAO. @@ -2375,7 +2577,7 @@ self.do_strip_render_pass(strips, target, load_op); } - fn apply_filter(&mut self, layer_id: LayerId) { + fn apply_filter(&mut self, layer_id: LayerId) -> Result<(), RenderError> { let filter_atlas_width = self.programs.resources.filter_atlas_width; let filter_atlas_height = self.programs.resources.filter_atlas_height; @@ -2394,7 +2596,7 @@ let filter_passes = self.filter_pass_state.filter_passes(); if filter_passes.is_empty() { - return; + return Ok(()); } self.gl.disable(WebGl2RenderingContext::BLEND); @@ -2439,11 +2641,21 @@ (filter_atlas_width, filter_atlas_height) } FilterPassTarget::MainAtlas(idx) => { + if self + .programs + .resources + .filter_main_atlas_framebuffer + .is_none() + { + self.programs.resources.filter_main_atlas_framebuffer = + Some(create_webgl_framebuffer(self.gl)?); + } let fb = self .programs .resources .filter_main_atlas_framebuffer - .get_or_insert_with(|| self.gl.create_framebuffer().unwrap()); + .as_ref() + .expect("filter framebuffer should have been created"); self.gl .bind_framebuffer(WebGl2RenderingContext::FRAMEBUFFER, Some(fb)); self.gl.framebuffer_texture_layer( @@ -2488,6 +2700,8 @@ self.gl.bind_vertex_array(None); self.gl.disable(WebGl2RenderingContext::SCISSOR_TEST); self.gl.enable(WebGl2RenderingContext::BLEND); + + Ok(()) } } @@ -2512,7 +2726,7 @@ offset: [u32; 2], width: u32, height: u32, - ); + ) -> Result<(), WebGlError>; } /// Implementation for `Pixmap` - direct upload using raw pixel data. @@ -2533,7 +2747,7 @@ offset: [u32; 2], width: u32, height: u32, - ) { + ) -> Result<(), WebGlError> { // Bind the atlas texture array gl.active_texture(WebGl2RenderingContext::TEXTURE0); gl.bind_texture( @@ -2558,7 +2772,8 @@ WebGl2RenderingContext::UNSIGNED_BYTE, Some(rgba_data), ) - .unwrap(); + .map_err(|err| webgl_operation_error("tex_sub_image_3d(atlas_upload)", err))?; + Ok(()) } } @@ -2580,9 +2795,9 @@ offset: [u32; 2], width: u32, height: u32, - ) { + ) -> Result<(), WebGlError> { self.as_ref() - .write_to_atlas_layer(gl, atlas_texture_array, layer, offset, width, height); + .write_to_atlas_layer(gl, atlas_texture_array, layer, offset, width, height) } } @@ -2610,7 +2825,7 @@ offset: [u32; 2], width: u32, height: u32, - ) { + ) -> Result<(), WebGlError> { copy_to_texture_array_layer( gl, |gl| { @@ -2627,7 +2842,7 @@ layer, offset, [width, height], - ); + ) } } @@ -2659,9 +2874,9 @@ offset: [u32; 2], width: u32, height: u32, - ) { + ) -> Result<(), WebGlError> { self.texture - .write_to_atlas_layer(gl, atlas_texture_array, layer, offset, width, height); + .write_to_atlas_layer(gl, atlas_texture_array, layer, offset, width, height) } } @@ -2712,9 +2927,9 @@ dest_layer: u32, dest_offset: [u32; 2], copy_size: [u32; 2], -) { +) -> Result<(), WebGlError> { let _state_guard = WebGlStateGuard::for_texture_copy(gl); - let read_framebuffer = gl.create_framebuffer().unwrap(); + let read_framebuffer = create_webgl_framebuffer(gl)?; // Bind destination texture array gl.active_texture(WebGl2RenderingContext::TEXTURE0); @@ -2747,6 +2962,7 @@ // Clean up gl.delete_framebuffer(Some(&read_framebuffer)); + Ok(()) } // Upload the data to the currently bound texture assuming a RGBA32UI format. @@ -2755,7 +2971,7 @@ data: &[u32], texture_width: u32, texture_height: u32, -) { +) -> Result<(), WebGlError> { // Safety: This calling `Uint32Array::view` is unsafe because it provides a view into // WASM linear memory, and any additional allocations might invalidate that view. // In our case, this is not an issue because we only use this view once for uploading @@ -2787,5 +3003,6 @@ WebGl2RenderingContext::UNSIGNED_INT, Some(&packed_array), ) - .unwrap(); + .map_err(|err| webgl_operation_error("tex_image_2d(rgba32ui)", err))?; + Ok(()) }
diff --git a/sparse_strips/vello_hybrid/src/render/wgpu.rs b/sparse_strips/vello_hybrid/src/render/wgpu.rs index 0417e57..274c262 100644 --- a/sparse_strips/vello_hybrid/src/render/wgpu.rs +++ b/sparse_strips/vello_hybrid/src/render/wgpu.rs
@@ -242,16 +242,14 @@ resources.before_render( self, |renderer, glyph_renderer, atlas_count, atlas_config, atlas_id| { - renderer - .render_to_atlas( - glyph_renderer, - atlas_count, - atlas_config, - device, - queue, - atlas_id, - ) - .expect("Failed to render glyphs to atlas"); + renderer.render_to_atlas( + glyph_renderer, + atlas_count, + atlas_config, + device, + queue, + atlas_id, + ) }, |renderer, image_cache, upload, dst_x, dst_y| { renderer.write_to_atlas( @@ -263,8 +261,9 @@ &upload.pixmap, Some([dst_x, dst_y]), ); + Ok(()) }, - ); + )?; } let mut encoded_paints = scene.encoded_paints.borrow_mut(); @@ -2569,7 +2568,7 @@ self.do_strip_render_pass(strips, target, wgpu_load_op); } - fn apply_filter(&mut self, layer_id: LayerId) { + fn apply_filter(&mut self, layer_id: LayerId) -> Result<(), RenderError> { let filter_atlas = &self.programs.resources.filter_atlas; self.filter_context.build_filter_passes( self.filter_pass_state, @@ -2587,7 +2586,7 @@ let filter_passes = self.filter_pass_state.filter_passes(); if filter_passes.is_empty() { - return; + return Ok(()); } let instances = self.filter_pass_state.instances(); @@ -2658,6 +2657,8 @@ ); render_pass.draw(0..4, 0..1); } + + Ok(()) } }
diff --git a/sparse_strips/vello_hybrid/src/schedule.rs b/sparse_strips/vello_hybrid/src/schedule.rs index 63131be..5af1a3b 100644 --- a/sparse_strips/vello_hybrid/src/schedule.rs +++ b/sparse_strips/vello_hybrid/src/schedule.rs
@@ -250,7 +250,7 @@ ); /// Apply filter effects for the given layer after its content has been rendered. - fn apply_filter(&mut self, layer_id: LayerId); + fn apply_filter(&mut self, layer_id: LayerId) -> Result<(), RenderError>; } /// Backend agnostic enum that specifies the operation to perform to the output attachment at the @@ -470,7 +470,7 @@ if self.rounds_queue.is_empty() { return Err(RenderError::SlotsExhausted); } - self.flush(renderer); + self.flush(renderer)?; } let slot_ix = self.free[texture].pop().unwrap(); @@ -541,13 +541,13 @@ } while !self.rounds_queue.is_empty() { - self.flush(renderer); + self.flush(renderer)?; } // This will actually apply the filter and store the filtered texture in the image // atlas if let StripPassRenderTarget::FilterLayer(layer_id) = self.output_target { - renderer.apply_filter(layer_id); + renderer.apply_filter(layer_id)?; } } @@ -858,7 +858,7 @@ /// Flush one round. /// /// The rounds queue must not be empty. - fn flush<R: RendererBackend>(&mut self, renderer: &mut R) { + fn flush<R: RendererBackend>(&mut self, renderer: &mut R) -> Result<(), RenderError> { let round = self.rounds_queue.pop_front().unwrap(); for (i, draw) in round.draws.iter().enumerate() { #[cfg(debug_assertions)] @@ -914,6 +914,8 @@ self.round += 1; self.round_pool.return_to_pool(round); + + Ok(()) } // Find the appropriate draw call for rendering.
diff --git a/sparse_strips/vello_hybrid/src/text.rs b/sparse_strips/vello_hybrid/src/text.rs index 282c926..ebcda78 100644 --- a/sparse_strips/vello_hybrid/src/text.rs +++ b/sparse_strips/vello_hybrid/src/text.rs
@@ -77,7 +77,10 @@ u32::try_from(self.image_cache.atlas_count()).unwrap() } - fn replay_pending_atlas_commands(&mut self, mut f: impl FnMut(&Scene, AtlasId)) { + fn replay_pending_atlas_commands<E>( + &mut self, + mut f: impl FnMut(&Scene, AtlasId) -> Result<(), E>, + ) -> Result<(), E> { if let Some(glyph_resources) = self.glyph_resources.as_mut() { glyph_resources .glyph_atlas @@ -90,22 +93,30 @@ f( &glyph_resources.glyph_renderer, AtlasId::new(recorder.page_index), - ); - }); + ) + })?; } + + Ok(()) } - pub(crate) fn before_render<T>( + pub(crate) fn before_render<T, E>( &mut self, backend: &mut T, - mut render_to_atlas: impl FnMut(&mut T, &Scene, u32, AtlasConfig, AtlasId), - mut upload_to_atlas: impl FnMut(&mut T, &ImageCache, &PendingBitmapUpload, u32, u32), - ) { + mut render_to_atlas: impl FnMut(&mut T, &Scene, u32, AtlasConfig, AtlasId) -> Result<(), E>, + mut upload_to_atlas: impl FnMut( + &mut T, + &ImageCache, + &PendingBitmapUpload, + u32, + u32, + ) -> Result<(), E>, + ) -> Result<(), E> { let atlas_count = self.atlas_count(); let atlas_config = self.atlas_config(); self.replay_pending_atlas_commands(|glyph_renderer, atlas_id| { - render_to_atlas(backend, glyph_renderer, atlas_count, atlas_config, atlas_id); - }); + render_to_atlas(backend, glyph_renderer, atlas_count, atlas_config, atlas_id) + })?; const PADDING: u32 = GLYPH_PADDING as u32; @@ -114,9 +125,11 @@ let resource = self.image_cache.get(upload.image_id).unwrap(); let dst_x = resource.offset[0] as u32 + PADDING; let dst_y = resource.offset[1] as u32 + PADDING; - upload_to_atlas(backend, &self.image_cache, &upload, dst_x, dst_y); + upload_to_atlas(backend, &self.image_cache, &upload, dst_x, dst_y)?; } } + + Ok(()) } pub(crate) fn after_render<T>(
diff --git a/sparse_strips/vello_sparse_tests/tests/renderer.rs b/sparse_strips/vello_sparse_tests/tests/renderer.rs index 2264ff5..9d525df 100644 --- a/sparse_strips/vello_sparse_tests/tests/renderer.rs +++ b/sparse_strips/vello_sparse_tests/tests/renderer.rs
@@ -646,7 +646,9 @@ #[cfg(all(target_arch = "wasm32", feature = "webgl"))] impl HybridRenderer { fn upload_image(&mut self, pixmap: &Arc<Pixmap>) -> ImageId { - self.renderer.upload_image(&mut self.resources, pixmap) + self.renderer + .upload_image(&mut self.resources, pixmap) + .unwrap() } } @@ -688,7 +690,7 @@ .unwrap(); canvas.set_width(width.into()); canvas.set_height(height.into()); - let renderer = vello_hybrid::WebGlRenderer::new(&canvas); + let renderer = vello_hybrid::WebGlRenderer::new(&canvas).unwrap(); let gl = canvas .get_context("webgl2") .unwrap()