Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions sparse_strips/vello_hybrid/src/render/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,25 @@ pub struct Config {
pub strip_offset_x: i32,
/// A vertical offset to apply to strips.
pub strip_offset_y: i32,
/// Padding to satisfy WebGL's 16-byte alignment requirement for uniform buffers.
pub _padding: u32,
/// Whether to flip the y-component of the NDC position.
///
/// When transpiling a shader with naga, it applies a y-flip transform when
/// compiling to GLSL to account for the difference between the y-down
/// coordinate of WebGPU and the y-up coordinate system of WebGL framebuffers.
///
/// However, for the native WebGL backend, we want to _avoid_ this transform so that we
/// can write directly into the user-provided framebuffer, but still ensure that the scene
Comment thread
LaurenzV marked this conversation as resolved.
/// is correctly flipped. Otherwise, we would need to allocate another framebuffer,
/// render into that and then pay the cost for flipping it.
///
/// Therefore, we optionally negate the coordinates in NDC.
/// (Note that naga provides a flag for disabling this behavior. However, the problem
/// is that many parts of Vello Hybrid's code (such as slot textures) also assume a
/// y-down coordinate system. Therefore, just disabling this flag causes complications in
/// other places. From my experiments, it's much easier to enable the flag by default
/// and just apply the second negation manually in case we render to the final output surface
/// in the WebGL backend.
pub negate_ndc: u32,
}

/// A GPU strip instance for rendering.
Expand Down
174 changes: 30 additions & 144 deletions sparse_strips/vello_hybrid/src/render/webgl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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()
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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(())
}

Expand Down Expand Up @@ -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.
Expand All @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -879,6 +807,7 @@ impl WebGlPrograms {
width: 0,
height: 0,
},
negate_ndc: false,
encoded_paints_data,
filter_data: Vec::new(),
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions sparse_strips/vello_hybrid/src/render/wgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1504,7 +1504,7 @@ impl Programs {
encoded_paints_tex_width_bits: alpha_texture_width.trailing_zeros(),
strip_offset_x: 0,
strip_offset_y: 0,
_padding: 0,
negate_ndc: 0,
}),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
})
Expand Down Expand Up @@ -1955,7 +1955,7 @@ impl Programs {
encoded_paints_tex_width_bits: max_texture_dimension_2d.trailing_zeros(),
strip_offset_x: 0,
strip_offset_y: 0,
_padding: 0,
negate_ndc: 0,
};
let mut buffer = queue
.write_buffer_with(&self.resources.view_config_buffer, 0, SIZE_OF_CONFIG)
Expand Down Expand Up @@ -2294,7 +2294,7 @@ impl RendererContext<'_> {
.trailing_zeros(),
strip_offset_x,
strip_offset_y,
_padding: 0,
negate_ndc: 0,
}),
usage: wgpu::BufferUsages::UNIFORM,
});
Expand Down
Loading
Loading