-
Notifications
You must be signed in to change notification settings - Fork 241
vello_hybrid: Avoid rendering into an intermediate framebuffer #1546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
90edecd
vello_hybrid: Avoid rendering into an intermediate framebuffer
laurenz-canva 72e3628
Don't use `ndc_y_negate` for clear slots
LaurenzV 1fec241
Add a comment
LaurenzV 5dbcf7b
Disable anti-alias
LaurenzV 8a0369a
Fix negate_ndc handling
LaurenzV 408e80a
Fix compile
LaurenzV ef282cd
Re-add debug assertion
laurenz-canva File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -113,15 +113,8 @@ impl WebGlRenderer { | |
| pub fn new_with(canvas: &web_sys::HtmlCanvasElement, settings: RenderSettings) -> Self { | ||
| super::common::maybe_warn_about_webgl_feature_conflict(); | ||
|
|
||
| // The WebGL context must be created with anti-aliasing disabled such that we can blit the | ||
| // view framebuffer onto the default framebuffer. This technique is required for the code | ||
| // that converts the WebGPU coordinate system into the WebGL coordinate system, adapted from | ||
| // the `wgpu` library. The coordinate space is fixed via two steps: | ||
| // 1. naga adds a coordinate transform to the glsl vertex shaders – however Y axis remains | ||
| // flipped. | ||
| // 2. A view framebuffer is used as an intermediate render target. The final result is | ||
| // blit onto the default framebuffer reflected to fix the flipped Y axis. | ||
| // Anti-aliasing causes the blit operation to fail. | ||
| // 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(); | ||
|
|
||
|
|
@@ -135,9 +128,9 @@ impl WebGlRenderer { | |
| #[cfg(debug_assertions)] | ||
| { | ||
| // If a WebGL context already exists on this canvas, it will be returned instead of | ||
| // creating a new one with the correct context_options set. A cached context with | ||
| // antialias enabled will cause vello_hybrid to fail silently. This assertion catches | ||
| // that error early. | ||
| // 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 antialias = js_sys::Reflect::get(&context_attributes, &"antialias".into()) | ||
| .unwrap() | ||
|
|
@@ -187,64 +180,6 @@ impl WebGlRenderer { | |
| ); | ||
|
|
||
| self.render_scene(scene, render_size, true)?; | ||
|
|
||
| // Blit the view framebuffer to the default framebuffer (canvas element), reflecting the | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's very nice to see this code get removed :D Awesome! |
||
| // image along the Y axis to complete the WebGPU to WebGL2 coordinate transform. | ||
| self.gl.bind_framebuffer( | ||
| WebGl2RenderingContext::READ_FRAMEBUFFER, | ||
| Some(&self.programs.resources.view_framebuffer), | ||
| ); | ||
| #[cfg(debug_assertions)] | ||
| { | ||
| let status = self | ||
| .gl | ||
| .check_framebuffer_status(WebGl2RenderingContext::READ_FRAMEBUFFER); | ||
| debug_assert_eq!( | ||
| status, | ||
| WebGl2RenderingContext::FRAMEBUFFER_COMPLETE, | ||
| "read framebuffer not complete" | ||
| ); | ||
| } | ||
|
|
||
| self.gl | ||
| .bind_framebuffer(WebGl2RenderingContext::DRAW_FRAMEBUFFER, None); | ||
|
|
||
| #[cfg(debug_assertions)] | ||
| { | ||
| let status = self | ||
| .gl | ||
| .check_framebuffer_status(WebGl2RenderingContext::DRAW_FRAMEBUFFER); | ||
| debug_assert_eq!( | ||
| status, | ||
| WebGl2RenderingContext::FRAMEBUFFER_COMPLETE, | ||
| "write framebuffer not complete" | ||
| ); | ||
| } | ||
|
|
||
| self.gl.blit_framebuffer( | ||
| 0, | ||
| render_size.height as i32, | ||
| render_size.width as i32, | ||
| 0, | ||
| 0, | ||
| 0, | ||
| render_size.width as i32, | ||
| render_size.height as i32, | ||
| WebGl2RenderingContext::COLOR_BUFFER_BIT, | ||
| WebGl2RenderingContext::LINEAR, | ||
| ); | ||
|
|
||
| #[cfg(debug_assertions)] | ||
| { | ||
| // `get_error` cause synchronous stalls on the calling thread. It's best practice in | ||
| // release to omit this call. | ||
| // Reference: | ||
| // https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#avoid_blocking_api_calls_in_production | ||
| let error = self.gl.get_error(); | ||
| if error != WebGl2RenderingContext::NO_ERROR { | ||
| panic!("WebGL error {error}"); | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
|
|
@@ -291,12 +226,9 @@ impl WebGlRenderer { | |
| atlas_id.as_u32() as i32, | ||
| ); | ||
|
|
||
| // Swap the view framebuffer and render size so the scheduler renders to the | ||
| // atlas layer instead of the normal view. | ||
| let saved_framebuffer = core::mem::replace( | ||
| &mut self.programs.resources.view_framebuffer, | ||
| atlas_framebuffer, | ||
| ); | ||
| // Set the view framebuffer override so the scheduler renders to the | ||
| // atlas layer instead of the default framebuffer. | ||
| self.programs.resources.view_framebuffer_override = Some(atlas_framebuffer); | ||
|
|
||
| // Swap in the stub atlas texture array to avoid binding the real atlas | ||
| // texture as a shader input while it is also the render target. | ||
|
|
@@ -313,12 +245,9 @@ impl WebGlRenderer { | |
| &mut self.programs.resources.stub_atlas_texture_array, | ||
| ); | ||
|
|
||
| // Restore the original view framebuffer and cache the atlas FBO for reuse. | ||
| let atlas_fb = core::mem::replace( | ||
| &mut self.programs.resources.view_framebuffer, | ||
| saved_framebuffer, | ||
| ); | ||
| self.programs.resources.atlas_render_framebuffer = Some(atlas_fb); | ||
| // Restore the default view framebuffer and cache the atlas FBO for reuse. | ||
| self.programs.resources.atlas_render_framebuffer = | ||
| self.programs.resources.view_framebuffer_override.take(); | ||
|
|
||
| result | ||
| } | ||
|
|
@@ -693,6 +622,8 @@ struct WebGlPrograms { | |
| resources: WebGlResources, | ||
| /// Dimensions of the rendering target. | ||
| render_size: RenderSize, | ||
| /// Whether the last config buffer upload had NDC Y negation enabled. | ||
| negate_ndc: bool, | ||
| /// Scratch buffer for staging encoded paints texture data. | ||
| encoded_paints_data: Vec<u8>, | ||
| /// Scratch buffer for staging filter data texture data. | ||
|
|
@@ -768,10 +699,7 @@ struct WebGlResources { | |
| /// Config buffer for clear program. | ||
| clear_config_buffer: WebGlBuffer, | ||
|
|
||
| /// Intermediate surface texture for the main view. | ||
| view_texture: WebGlTexture, | ||
| /// Framebuffer for the vfiew texture. | ||
| view_framebuffer: WebGlFramebuffer, | ||
| view_framebuffer_override: Option<WebGlFramebuffer>, | ||
|
|
||
| /// Slot textures. | ||
| slot_textures: [WebGlTexture; 2], | ||
|
|
@@ -879,6 +807,7 @@ impl WebGlPrograms { | |
| width: 0, | ||
| height: 0, | ||
| }, | ||
| negate_ndc: false, | ||
| encoded_paints_data, | ||
| filter_data: Vec::new(), | ||
| } | ||
|
|
@@ -1156,7 +1085,13 @@ impl WebGlPrograms { | |
| max_texture_dimension_2d: u32, | ||
| new_render_size: &RenderSize, | ||
| ) { | ||
| if self.render_size != *new_render_size { | ||
| // Only negate if we are rendering to the main frame buffer. | ||
| let negate_ndc = self.resources.view_framebuffer_override.is_none(); | ||
|
|
||
| // TODO: Collect all attributes that influence the config buffer into a | ||
| // single struct and compare that, such that we cannot forget to update the | ||
| // condition in case we add new fields in the future. | ||
| if self.render_size != *new_render_size || self.negate_ndc != negate_ndc { | ||
| // Update view config buffer | ||
| { | ||
| let config = Config { | ||
|
|
@@ -1167,7 +1102,7 @@ impl WebGlPrograms { | |
| encoded_paints_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), | ||
| strip_offset_x: 0, | ||
| strip_offset_y: 0, | ||
| _padding: 0, | ||
| negate_ndc: u32::from(negate_ndc), | ||
| }; | ||
|
|
||
| gl.bind_buffer( | ||
|
|
@@ -1193,7 +1128,8 @@ impl WebGlPrograms { | |
| encoded_paints_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), | ||
| strip_offset_x: 0, | ||
| strip_offset_y: 0, | ||
| _padding: 0, | ||
| // Always use y-down when rendering to slots. | ||
| negate_ndc: 0, | ||
| }; | ||
|
|
||
| gl.bind_buffer( | ||
|
|
@@ -1230,52 +1166,8 @@ impl WebGlPrograms { | |
| ); | ||
| } | ||
|
|
||
| // Resize the view texture. | ||
| gl.bind_texture( | ||
| WebGl2RenderingContext::TEXTURE_2D, | ||
| Some(&self.resources.view_texture), | ||
| ); | ||
| gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_array_buffer_view( | ||
| WebGl2RenderingContext::TEXTURE_2D, | ||
| 0, | ||
| WebGl2RenderingContext::RGBA8 as i32, | ||
| new_render_size.width as i32, | ||
| new_render_size.height as i32, | ||
| 0, | ||
| WebGl2RenderingContext::RGBA, | ||
| WebGl2RenderingContext::UNSIGNED_BYTE, | ||
| None, | ||
| ) | ||
| .unwrap(); | ||
|
|
||
| // Re-attach the texture to the framebuffer after reallocation. Some GPU | ||
| // drivers (notably Mali on low-end Android devices, accessed through | ||
| // ANGLE) cache framebuffer completeness and don't re-evaluate it when an | ||
| // attached texture's storage is reallocated via tex_image_2d while the | ||
| // framebuffer is not bound. | ||
| gl.bind_framebuffer( | ||
| WebGl2RenderingContext::FRAMEBUFFER, | ||
| Some(&self.resources.view_framebuffer), | ||
| ); | ||
| gl.framebuffer_texture_2d( | ||
| WebGl2RenderingContext::FRAMEBUFFER, | ||
| WebGl2RenderingContext::COLOR_ATTACHMENT0, | ||
| WebGl2RenderingContext::TEXTURE_2D, | ||
| Some(&self.resources.view_texture), | ||
| 0, | ||
| ); | ||
|
|
||
| #[cfg(debug_assertions)] | ||
| { | ||
| let status = gl.check_framebuffer_status(WebGl2RenderingContext::FRAMEBUFFER); | ||
| debug_assert_eq!( | ||
| status, | ||
| WebGl2RenderingContext::FRAMEBUFFER_COMPLETE, | ||
| "view framebuffer incomplete" | ||
| ); | ||
| } | ||
|
|
||
| self.render_size = new_render_size.clone(); | ||
| self.negate_ndc = negate_ndc; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1384,7 +1276,7 @@ impl WebGlPrograms { | |
| fn clear_view_framebuffer(&mut self, gl: &WebGl2RenderingContext) { | ||
| gl.bind_framebuffer( | ||
| WebGl2RenderingContext::FRAMEBUFFER, | ||
| Some(&self.resources.view_framebuffer), | ||
| self.resources.view_framebuffer_override.as_ref(), | ||
| ); | ||
| gl.clear_color(0.0, 0.0, 0.0, 0.0); | ||
| gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT); | ||
|
|
@@ -1902,11 +1794,6 @@ fn create_webgl_resources( | |
| // Create and configure gradient texture. | ||
| let gradient_texture = create_texture(gl); | ||
|
|
||
| // Create and configure view texture. | ||
| let view_texture = create_texture(gl); | ||
| // Create framebuffer for the view texture. | ||
| let view_framebuffer = create_framebuffer_for_texture(gl, &view_texture); | ||
|
|
||
| // Create slot textures and framebuffers. | ||
| let slot_textures: [WebGlTexture; 2] = [ | ||
| create_slot_texture(gl, slot_count), | ||
|
|
@@ -1945,8 +1832,7 @@ fn create_webgl_resources( | |
| clear_config_buffer, | ||
| slot_textures, | ||
| slot_framebuffers, | ||
| view_texture, | ||
| view_framebuffer, | ||
| view_framebuffer_override: None, | ||
| max_texture_dimension_2d, | ||
| stub_atlas_texture_array, | ||
| atlas_render_framebuffer: None, | ||
|
|
@@ -2151,7 +2037,7 @@ impl WebGlRendererContext<'_> { | |
| .trailing_zeros(), | ||
| strip_offset_x, | ||
| strip_offset_y, | ||
| _padding: 0, | ||
| negate_ndc: 0, | ||
| }; | ||
| let buf = &self.programs.resources.filter_config_buffer; | ||
| self.gl | ||
|
|
@@ -2175,7 +2061,7 @@ impl WebGlRendererContext<'_> { | |
| StripPassRenderTarget::Output(OutputTarget::FinalView) => { | ||
| self.gl.bind_framebuffer( | ||
| WebGl2RenderingContext::FRAMEBUFFER, | ||
| Some(&self.programs.resources.view_framebuffer), | ||
| self.programs.resources.view_framebuffer_override.as_ref(), | ||
| ); | ||
| let width = self.programs.render_size.width; | ||
| let height = self.programs.render_size.height; | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.