webgl impl
diff --git a/sparse_strips/vello_hybrid/src/render/webgl.rs b/sparse_strips/vello_hybrid/src/render/webgl.rs index 23e945b..ad3b7d0 100644 --- a/sparse_strips/vello_hybrid/src/render/webgl.rs +++ b/sparse_strips/vello_hybrid/src/render/webgl.rs
@@ -62,7 +62,10 @@ pixmap::Pixmap, tile::Tile, }; -use vello_sparse_shaders::{clear_slots, filters, render_strips}; +use vello_sparse_shaders::{ + clear_slots, filters, opaque_gradient, opaque_image, opaque_image_untinted, opaque_solid, + render_strips, +}; use web_sys::wasm_bindgen::{JsCast, JsValue}; use web_sys::{ HtmlCanvasElement, WebGl2RenderingContext, WebGlBuffer, WebGlFramebuffer, WebGlProgram, @@ -545,6 +548,8 @@ programs: &mut self.programs, gl: &self.gl, image_cache, + encoded_paints: &encoded_paints, + paint_idxs: &self.paint_idxs, filter_context: &self.filter_context, filter_pass_state: &mut self.filter_pass_state, }; @@ -879,6 +884,22 @@ strip_program: WebGlProgram, /// Uniform locations for the strip program strip_uniforms: StripUniforms, + /// Program for opaque solid strips on the final view. + opaque_solid_program: WebGlProgram, + /// Uniform locations for the opaque solid program. + opaque_solid_uniforms: StripUniforms, + /// Program for opaque tinted image strips on the final view. + opaque_image_program: WebGlProgram, + /// Uniform locations for the opaque tinted image program. + opaque_image_uniforms: StripUniforms, + /// Program for opaque untinted image strips on the final view. + opaque_image_untinted_program: WebGlProgram, + /// Uniform locations for the opaque untinted image program. + opaque_image_untinted_uniforms: StripUniforms, + /// Program for opaque gradient strips on the final view. + opaque_gradient_program: WebGlProgram, + /// Uniform locations for the opaque gradient program. + opaque_gradient_uniforms: StripUniforms, /// Program for clearing slots in slot textures. clear_program: WebGlProgram, /// Uniform locations for the `clear_program`. @@ -909,22 +930,18 @@ /// Uniform locations for `strip_program`. #[derive(Debug)] struct StripUniforms { - /// Config uniform block index for vertex shader. - config_vs_block_index: u32, - /// Config uniform block index for fragment shader. - config_fs_block_index: u32, /// Alphas texture location. - alphas_texture: WebGlUniformLocation, + alphas_texture: Option<WebGlUniformLocation>, /// Clip input texture location. - clip_input_texture: WebGlUniformLocation, + clip_input_texture: Option<WebGlUniformLocation>, /// Atlas texture location. - atlas_texture_array: WebGlUniformLocation, + atlas_texture_array: Option<WebGlUniformLocation>, /// Encoded paints texture location for fragment shader. - encoded_paints_texture_fs: WebGlUniformLocation, + encoded_paints_texture_fs: Option<WebGlUniformLocation>, /// Encoded paints texture location for vertex shader. - encoded_paints_texture_vs: WebGlUniformLocation, + encoded_paints_texture_vs: Option<WebGlUniformLocation>, /// Gradient texture location. - gradient_texture: WebGlUniformLocation, + gradient_texture: Option<WebGlUniformLocation>, } /// Uniform locations for `clear_program`. @@ -1042,6 +1059,26 @@ render_strips::VERTEX_SOURCE, render_strips::FRAGMENT_SOURCE, ); + let opaque_solid_program = create_shader_program( + &gl, + opaque_solid::VERTEX_SOURCE, + opaque_solid::FRAGMENT_SOURCE, + ); + let opaque_image_program = create_shader_program( + &gl, + opaque_image::VERTEX_SOURCE, + opaque_image::FRAGMENT_SOURCE, + ); + let opaque_image_untinted_program = create_shader_program( + &gl, + opaque_image_untinted::VERTEX_SOURCE, + opaque_image_untinted::FRAGMENT_SOURCE, + ); + let opaque_gradient_program = create_shader_program( + &gl, + opaque_gradient::VERTEX_SOURCE, + opaque_gradient::FRAGMENT_SOURCE, + ); let clear_program = create_shader_program( &gl, clear_slots::VERTEX_SOURCE, @@ -1052,6 +1089,11 @@ let filter_uniforms = get_filter_pass_uniforms(&gl, &filter_program); let strip_uniforms = get_strip_uniforms(&gl, &strip_program); + let opaque_solid_uniforms = get_opaque_solid_uniforms(&gl, &opaque_solid_program); + let opaque_image_uniforms = get_opaque_image_uniforms(&gl, &opaque_image_program); + let opaque_image_untinted_uniforms = + get_opaque_image_untinted_uniforms(&gl, &opaque_image_untinted_program); + let opaque_gradient_uniforms = get_opaque_gradient_uniforms(&gl, &opaque_gradient_program); let clear_uniforms = get_clear_uniforms(&gl, &clear_program); let resources = create_webgl_resources(&gl, image_cache, filter_context, slot_count); @@ -1074,6 +1116,14 @@ filter_program, filter_uniforms, strip_uniforms, + opaque_solid_program, + opaque_solid_uniforms, + opaque_image_program, + opaque_image_uniforms, + opaque_image_untinted_program, + opaque_image_untinted_uniforms, + opaque_gradient_program, + opaque_gradient_uniforms, clear_uniforms, resources, render_size: RenderSize { @@ -1555,14 +1605,9 @@ gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT); } - /// Uploads two strip slices (opaque then alpha) into a single GPU buffer. - fn upload_strip_pair( - &mut self, - gl: &WebGl2RenderingContext, - opaque_strips: &[GpuStrip], - alpha_strips: &[GpuStrip], - ) { - if opaque_strips.is_empty() && alpha_strips.is_empty() { + /// Uploads a strip slice into the GPU instance buffer. + fn upload_strips(&mut self, gl: &WebGl2RenderingContext, strips: &[GpuStrip]) { + if strips.is_empty() { return; } @@ -1571,32 +1616,17 @@ Some(&self.resources.strips_buffer), ); - let opaque_bytes: &[u8] = bytemuck::cast_slice(opaque_strips); - let alpha_bytes: &[u8] = bytemuck::cast_slice(alpha_strips); - let total_len = opaque_bytes.len() + alpha_bytes.len(); - - // Allocate buffer, then write both slices via bufferSubData. - // We don't want to pay for concatenating the two slices. It's better to - // simply write twice. + let strip_bytes: &[u8] = bytemuck::cast_slice(strips); gl.buffer_data_with_i32( WebGl2RenderingContext::ARRAY_BUFFER, - total_len as i32, + strip_bytes.len() as i32, WebGl2RenderingContext::DYNAMIC_DRAW, ); - if !opaque_bytes.is_empty() { - gl.buffer_sub_data_with_i32_and_u8_array( - WebGl2RenderingContext::ARRAY_BUFFER, - 0, - opaque_bytes, - ); - } - if !alpha_bytes.is_empty() { - gl.buffer_sub_data_with_i32_and_u8_array( - WebGl2RenderingContext::ARRAY_BUFFER, - opaque_bytes.len() as i32, - alpha_bytes, - ); - } + gl.buffer_sub_data_with_i32_and_u8_array( + WebGl2RenderingContext::ARRAY_BUFFER, + 0, + strip_bytes, + ); } } @@ -1869,56 +1899,134 @@ /// Get the uniform locations for the `render_strips` program. fn get_strip_uniforms(gl: &WebGl2RenderingContext, program: &WebGlProgram) -> StripUniforms { - let config_vs_name = render_strips::vertex::CONFIG; - let config_vs_block_index = gl.get_uniform_block_index(program, config_vs_name); + get_optional_strip_uniforms( + gl, + program, + Some(render_strips::vertex::CONFIG), + Some(render_strips::fragment::CONFIG), + Some(render_strips::fragment::ALPHAS_TEXTURE), + Some(render_strips::fragment::CLIP_INPUT_TEXTURE), + Some(render_strips::fragment::ATLAS_TEXTURE_ARRAY), + Some(render_strips::fragment::ENCODED_PAINTS_TEXTURE), + Some(render_strips::vertex::ENCODED_PAINTS_TEXTURE), + Some(render_strips::fragment::GRADIENT_TEXTURE), + ) +} - let config_fs_name = render_strips::fragment::CONFIG; - let config_fs_block_index = gl.get_uniform_block_index(program, config_fs_name); +fn get_opaque_solid_uniforms(gl: &WebGl2RenderingContext, program: &WebGlProgram) -> StripUniforms { + get_optional_strip_uniforms( + gl, + program, + Some(opaque_solid::vertex::CONFIG), + None, + None, + None, + None, + None, + None, + None, + ) +} - 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" - ); +fn get_opaque_image_uniforms(gl: &WebGl2RenderingContext, program: &WebGlProgram) -> StripUniforms { + get_optional_strip_uniforms( + gl, + program, + Some(opaque_image::vertex::CONFIG), + Some(opaque_image::fragment::CONFIG), + None, + None, + Some(opaque_image::fragment::ATLAS_TEXTURE_ARRAY), + Some(opaque_image::fragment::ENCODED_PAINTS_TEXTURE), + Some(opaque_image::vertex::ENCODED_PAINTS_TEXTURE), + None, + ) +} - // Bind uniform blocks to binding points. - gl.uniform_block_binding(program, config_vs_block_index, 0); - gl.uniform_block_binding(program, config_fs_block_index, 0); +fn get_opaque_image_untinted_uniforms( + gl: &WebGl2RenderingContext, + program: &WebGlProgram, +) -> StripUniforms { + get_optional_strip_uniforms( + gl, + program, + Some(opaque_image_untinted::vertex::CONFIG), + Some(opaque_image_untinted::fragment::CONFIG), + None, + None, + Some(opaque_image_untinted::fragment::ATLAS_TEXTURE_ARRAY), + Some(opaque_image_untinted::fragment::ENCODED_PAINTS_TEXTURE), + Some(opaque_image_untinted::vertex::ENCODED_PAINTS_TEXTURE), + None, + ) +} - // Get texture uniform locations. - let alphas_texture_name = render_strips::fragment::ALPHAS_TEXTURE; - let clip_input_texture_name = render_strips::fragment::CLIP_INPUT_TEXTURE; - let atlas_texture_array_name = render_strips::fragment::ATLAS_TEXTURE_ARRAY; - let encoded_paints_texture_fs_name = render_strips::fragment::ENCODED_PAINTS_TEXTURE; - let encoded_paints_texture_vs_name = render_strips::vertex::ENCODED_PAINTS_TEXTURE; - let gradient_texture_name = render_strips::fragment::GRADIENT_TEXTURE; +fn get_opaque_gradient_uniforms( + gl: &WebGl2RenderingContext, + program: &WebGlProgram, +) -> StripUniforms { + get_optional_strip_uniforms( + gl, + program, + Some(opaque_gradient::vertex::CONFIG), + Some(opaque_gradient::fragment::CONFIG), + None, + None, + None, + Some(opaque_gradient::fragment::ENCODED_PAINTS_TEXTURE), + None, + Some(opaque_gradient::fragment::GRADIENT_TEXTURE), + ) +} + +#[expect( + clippy::too_many_arguments, + reason = "Uniform metadata is naturally wide here." +)] +fn get_optional_strip_uniforms( + gl: &WebGl2RenderingContext, + program: &WebGlProgram, + config_vs_name: Option<&str>, + config_fs_name: Option<&str>, + alphas_texture_name: Option<&str>, + clip_input_texture_name: Option<&str>, + atlas_texture_array_name: Option<&str>, + encoded_paints_texture_fs_name: Option<&str>, + encoded_paints_texture_vs_name: Option<&str>, + gradient_texture_name: Option<&str>, +) -> StripUniforms { + let _config_vs_block_index = config_vs_name.and_then(|name| { + let idx = gl.get_uniform_block_index(program, name); + if idx == WebGl2RenderingContext::INVALID_INDEX { + None + } else { + gl.uniform_block_binding(program, idx, 0); + Some(idx) + } + }); + + let _config_fs_block_index = config_fs_name.and_then(|name| { + let idx = gl.get_uniform_block_index(program, name); + if idx == WebGl2RenderingContext::INVALID_INDEX { + None + } else { + gl.uniform_block_binding(program, idx, 0); + Some(idx) + } + }); StripUniforms { - config_vs_block_index, - config_fs_block_index, - alphas_texture: gl - .get_uniform_location(program, alphas_texture_name) - .unwrap(), - clip_input_texture: gl - .get_uniform_location(program, clip_input_texture_name) - .unwrap(), - atlas_texture_array: gl - .get_uniform_location(program, atlas_texture_array_name) - .unwrap(), - encoded_paints_texture_fs: gl - .get_uniform_location(program, encoded_paints_texture_fs_name) - .unwrap(), - encoded_paints_texture_vs: gl - .get_uniform_location(program, encoded_paints_texture_vs_name) - .unwrap(), - gradient_texture: gl - .get_uniform_location(program, gradient_texture_name) - .unwrap(), + alphas_texture: alphas_texture_name.and_then(|name| gl.get_uniform_location(program, name)), + clip_input_texture: clip_input_texture_name + .and_then(|name| gl.get_uniform_location(program, name)), + atlas_texture_array: atlas_texture_array_name + .and_then(|name| gl.get_uniform_location(program, name)), + encoded_paints_texture_fs: encoded_paints_texture_fs_name + .and_then(|name| gl.get_uniform_location(program, name)), + encoded_paints_texture_vs: encoded_paints_texture_vs_name + .and_then(|name| gl.get_uniform_location(program, name)), + gradient_texture: gradient_texture_name + .and_then(|name| gl.get_uniform_location(program, name)), } } @@ -2315,10 +2423,137 @@ programs: &'a mut WebGlPrograms, gl: &'a WebGl2RenderingContext, image_cache: &'a ImageCache, + encoded_paints: &'a [EncodedPaint], + paint_idxs: &'a [u32], filter_context: &'a FilterContext, filter_pass_state: &'a mut FilterPassState, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum OpaqueBucket { + ImageUntinted, + ImageTinted, + Solid, + Gradient, +} + +impl OpaqueBucket { + fn from_strip(strip: &GpuStrip, encoded_paints: &[EncodedPaint], paint_idxs: &[u32]) -> Self { + const COLOR_SOURCE_PAYLOAD: u32 = 0; + const PAINT_TYPE_SOLID: u32 = 0; + const PAINT_TYPE_IMAGE: u32 = 1; + const PAINT_TYPE_LINEAR_GRADIENT: u32 = 2; + const PAINT_TYPE_RADIAL_GRADIENT: u32 = 3; + const PAINT_TYPE_SWEEP_GRADIENT: u32 = 4; + + let paint_and_rect_flag = strip.paint_and_rect_flag; + let color_source = (paint_and_rect_flag >> 29) & 0x3; + debug_assert_eq!( + color_source, COLOR_SOURCE_PAYLOAD, + "only payload-backed strips may be emitted as opaque" + ); + + match (paint_and_rect_flag >> 26) & 0x7 { + PAINT_TYPE_SOLID => Self::Solid, + PAINT_TYPE_IMAGE => { + let paint_tex_idx = paint_and_rect_flag & 0x03FF_FFFF; + let encoded_paint_idx = paint_idxs[..encoded_paints.len()] + .binary_search(&paint_tex_idx) + .expect("opaque image strip must reference a valid encoded paint"); + let EncodedPaint::Image(img) = &encoded_paints[encoded_paint_idx] else { + panic!("opaque image strip must reference an encoded image paint"); + }; + if img.tint.is_some() { + Self::ImageTinted + } else { + Self::ImageUntinted + } + } + PAINT_TYPE_LINEAR_GRADIENT | PAINT_TYPE_RADIAL_GRADIENT | PAINT_TYPE_SWEEP_GRADIENT => { + Self::Gradient + } + paint_type => panic!("unexpected opaque paint type {paint_type}"), + } + } +} + +fn bind_strip_program( + gl: &WebGl2RenderingContext, + program: &WebGlProgram, + uniforms: &StripUniforms, + resources: &WebGlResources, + clip_texture_idx: usize, +) { + gl.use_program(Some(program)); + + if let Some(location) = &uniforms.alphas_texture { + gl.active_texture(WebGl2RenderingContext::TEXTURE0); + gl.bind_texture( + WebGl2RenderingContext::TEXTURE_2D, + Some(&resources.alphas_texture), + ); + gl.uniform1i(Some(location), 0); + } + + if let Some(location) = &uniforms.clip_input_texture { + gl.active_texture(WebGl2RenderingContext::TEXTURE1); + gl.bind_texture( + WebGl2RenderingContext::TEXTURE_2D, + Some(&resources.slot_textures[clip_texture_idx]), + ); + gl.uniform1i(Some(location), 1); + } + + if let Some(location) = &uniforms.atlas_texture_array { + gl.active_texture(WebGl2RenderingContext::TEXTURE2); + gl.bind_texture( + WebGl2RenderingContext::TEXTURE_2D_ARRAY, + Some(&resources.atlas_texture_array.texture), + ); + gl.uniform1i(Some(location), 2); + } + + if let Some(location) = &uniforms.encoded_paints_texture_fs { + gl.active_texture(WebGl2RenderingContext::TEXTURE3); + gl.bind_texture( + WebGl2RenderingContext::TEXTURE_2D, + Some(&resources.encoded_paints_texture), + ); + gl.uniform1i(Some(location), 3); + } + + if let Some(location) = &uniforms.encoded_paints_texture_vs { + gl.active_texture(WebGl2RenderingContext::TEXTURE3); + gl.bind_texture( + WebGl2RenderingContext::TEXTURE_2D, + Some(&resources.encoded_paints_texture), + ); + gl.uniform1i(Some(location), 3); + } + + if let Some(location) = &uniforms.gradient_texture { + gl.active_texture(WebGl2RenderingContext::TEXTURE4); + gl.bind_texture( + WebGl2RenderingContext::TEXTURE_2D, + Some(&resources.gradient_texture), + ); + gl.uniform1i(Some(location), 4); + } +} + +fn set_strip_instance_offset(gl: &WebGl2RenderingContext, base_instance: i32) { + let base_byte_offset = base_instance * STRIP_STRIDE; + for i in 0..STRIP_ATTR_COUNT { + gl.vertex_attrib_i_pointer_with_i32( + i as u32, + 1, + WebGl2RenderingContext::UNSIGNED_INT, + STRIP_STRIDE, + i * 4 + base_byte_offset, + ); + } +} + impl WebGlRendererContext<'_> { /// Render strips to the specified render target. fn do_strip_render_pass( @@ -2384,16 +2619,8 @@ bytemuck::bytes_of(&config), WebGl2RenderingContext::DYNAMIC_DRAW, ); - self.gl.bind_buffer_base( - WebGl2RenderingContext::UNIFORM_BUFFER, - self.programs.strip_uniforms.config_vs_block_index, - Some(buf), - ); - self.gl.bind_buffer_base( - WebGl2RenderingContext::UNIFORM_BUFFER, - self.programs.strip_uniforms.config_fs_block_index, - Some(buf), - ); + self.gl + .bind_buffer_base(WebGl2RenderingContext::UNIFORM_BUFFER, 0, Some(buf)); Some([ resources.offset[0] as i32, @@ -2413,12 +2640,7 @@ self.gl.bind_buffer_base( WebGl2RenderingContext::UNIFORM_BUFFER, - self.programs.strip_uniforms.config_vs_block_index, - Some(&self.programs.resources.view_config_buffer), - ); - self.gl.bind_buffer_base( - WebGl2RenderingContext::UNIFORM_BUFFER, - self.programs.strip_uniforms.config_fs_block_index, + 0, Some(&self.programs.resources.view_config_buffer), ); @@ -2441,12 +2663,7 @@ // Use slot config buffer for rendering to a slot texture. self.gl.bind_buffer_base( WebGl2RenderingContext::UNIFORM_BUFFER, - self.programs.strip_uniforms.config_vs_block_index, - Some(&self.programs.resources.slot_config_buffer), - ); - self.gl.bind_buffer_base( - WebGl2RenderingContext::UNIFORM_BUFFER, - self.programs.strip_uniforms.config_fs_block_index, + 0, Some(&self.programs.resources.slot_config_buffer), ); @@ -2467,66 +2684,18 @@ self.gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT); } - // Use the strip program. - self.gl.use_program(Some(&self.programs.strip_program)); - - // Set up attributes. - self.gl - .bind_vertex_array(Some(&self.programs.resources.strip_vao)); - - // Bind textures. - self.gl.active_texture(WebGl2RenderingContext::TEXTURE0); - self.gl.bind_texture( - WebGl2RenderingContext::TEXTURE_2D, - Some(&self.programs.resources.alphas_texture), - ); - self.gl - .uniform1i(Some(&self.programs.strip_uniforms.alphas_texture), 0); - let clip_texture_idx = match &target { StripPassRenderTarget::SlotTexture(1) => 0, _ => 1, }; - self.gl.active_texture(WebGl2RenderingContext::TEXTURE1); - self.gl.bind_texture( - WebGl2RenderingContext::TEXTURE_2D, - Some(&self.programs.resources.slot_textures[clip_texture_idx]), - ); - self.gl - .uniform1i(Some(&self.programs.strip_uniforms.clip_input_texture), 1); - // Bind atlas texture array for image rendering - self.gl.active_texture(WebGl2RenderingContext::TEXTURE2); - self.gl.bind_texture( - WebGl2RenderingContext::TEXTURE_2D_ARRAY, - Some(&self.programs.resources.atlas_texture_array.texture), - ); + // Set up attributes. self.gl - .uniform1i(Some(&self.programs.strip_uniforms.atlas_texture_array), 2); - - // Bind encoded paints texture for image metadata - self.gl.active_texture(WebGl2RenderingContext::TEXTURE3); - self.gl.bind_texture( - WebGl2RenderingContext::TEXTURE_2D, - Some(&self.programs.resources.encoded_paints_texture), + .bind_vertex_array(Some(&self.programs.resources.strip_vao)); + self.gl.bind_buffer( + WebGl2RenderingContext::ARRAY_BUFFER, + Some(&self.programs.resources.strips_buffer), ); - self.gl.uniform1i( - Some(&self.programs.strip_uniforms.encoded_paints_texture_fs), - 3, - ); - self.gl.uniform1i( - Some(&self.programs.strip_uniforms.encoded_paints_texture_vs), - 3, - ); - - // Bind gradient texture for gradient rendering - self.gl.active_texture(WebGl2RenderingContext::TEXTURE4); - self.gl.bind_texture( - WebGl2RenderingContext::TEXTURE_2D, - Some(&self.programs.resources.gradient_texture), - ); - self.gl - .uniform1i(Some(&self.programs.strip_uniforms.gradient_texture), 4); // TODO: Today, we only support early-z rejection on the final view. If we wanted to support // intermediate layers, we would require separate depth buffers for each target. We can explore @@ -2535,11 +2704,59 @@ target, StripPassRenderTarget::Root(RootRenderTarget::UserSurface) ); + let mut image_untinted_opaque = Vec::new(); + let mut image_tinted_opaque = Vec::new(); + let mut solid_opaque = Vec::new(); + let mut gradient_opaque = Vec::new(); + let mut uploaded_strips = Vec::new(); + let (alpha_start, alpha_count) = if is_final_view { + image_untinted_opaque.reserve(opaque_strips.len()); + image_tinted_opaque.reserve(opaque_strips.len()); + solid_opaque.reserve(opaque_strips.len()); + gradient_opaque.reserve(opaque_strips.len()); + for strip in opaque_strips { + match OpaqueBucket::from_strip(strip, self.encoded_paints, self.paint_idxs) { + OpaqueBucket::ImageUntinted => image_untinted_opaque.push(*strip), + OpaqueBucket::ImageTinted => image_tinted_opaque.push(*strip), + OpaqueBucket::Solid => solid_opaque.push(*strip), + OpaqueBucket::Gradient => gradient_opaque.push(*strip), + } + } - self.programs - .upload_strip_pair(self.gl, opaque_strips, alpha_strips); - let opaque_count = opaque_strips.len() as i32; - let alpha_count = alpha_strips.len() as i32; + uploaded_strips.reserve( + image_untinted_opaque.len() + + image_tinted_opaque.len() + + solid_opaque.len() + + gradient_opaque.len() + + alpha_strips.len(), + ); + uploaded_strips.extend_from_slice(&image_untinted_opaque); + uploaded_strips.extend_from_slice(&image_tinted_opaque); + uploaded_strips.extend_from_slice(&solid_opaque); + uploaded_strips.extend_from_slice(&gradient_opaque); + let alpha_start = uploaded_strips.len() as i32; + uploaded_strips.extend_from_slice(alpha_strips); + (alpha_start, alpha_strips.len() as i32) + } else { + debug_assert!( + opaque_strips.is_empty(), + "opaque strips should only be emitted for the user surface" + ); + uploaded_strips.extend_from_slice(alpha_strips); + (0, alpha_strips.len() as i32) + }; + + let image_untinted_start = 0_i32; + let image_untinted_count = image_untinted_opaque.len() as i32; + let image_tinted_start = image_untinted_count; + let image_tinted_count = image_tinted_opaque.len() as i32; + let solid_start = image_tinted_start + image_tinted_count; + let solid_count = solid_opaque.len() as i32; + let gradient_start = solid_start + solid_count; + let gradient_count = gradient_opaque.len() as i32; + + self.programs.upload_strips(self.gl, &uploaded_strips); + set_strip_instance_offset(self.gl, 0); if is_final_view { self.gl.enable(WebGl2RenderingContext::DEPTH_TEST); @@ -2553,32 +2770,85 @@ } // Opaque pass: front-to-back, depth test ON, depth write ON, blend OFF. - if opaque_count > 0 { + if image_untinted_count + image_tinted_count + solid_count + gradient_count > 0 { self.gl.depth_mask(true); self.gl.disable(WebGl2RenderingContext::BLEND); + } + if image_untinted_count > 0 { + bind_strip_program( + self.gl, + &self.programs.opaque_image_untinted_program, + &self.programs.opaque_image_untinted_uniforms, + &self.programs.resources, + clip_texture_idx, + ); + set_strip_instance_offset(self.gl, image_untinted_start); self.gl.draw_arrays_instanced( WebGl2RenderingContext::TRIANGLE_STRIP, 0, 4, - opaque_count, + image_untinted_count, + ); + } + if image_tinted_count > 0 { + bind_strip_program( + self.gl, + &self.programs.opaque_image_program, + &self.programs.opaque_image_uniforms, + &self.programs.resources, + clip_texture_idx, + ); + set_strip_instance_offset(self.gl, image_tinted_start); + self.gl.draw_arrays_instanced( + WebGl2RenderingContext::TRIANGLE_STRIP, + 0, + 4, + image_tinted_count, + ); + } + if solid_count > 0 { + bind_strip_program( + self.gl, + &self.programs.opaque_solid_program, + &self.programs.opaque_solid_uniforms, + &self.programs.resources, + clip_texture_idx, + ); + set_strip_instance_offset(self.gl, solid_start); + self.gl.draw_arrays_instanced( + WebGl2RenderingContext::TRIANGLE_STRIP, + 0, + 4, + solid_count, + ); + } + if gradient_count > 0 { + bind_strip_program( + self.gl, + &self.programs.opaque_gradient_program, + &self.programs.opaque_gradient_uniforms, + &self.programs.resources, + clip_texture_idx, + ); + set_strip_instance_offset(self.gl, gradient_start); + self.gl.draw_arrays_instanced( + WebGl2RenderingContext::TRIANGLE_STRIP, + 0, + 4, + gradient_count, ); } // Alpha pass: back-to-front, depth test ON, depth write OFF, blend ON. if alpha_count > 0 { - // Rebind attribute pointers with offset to start at the alpha portion - // of the buffer. - let alpha_byte_offset = opaque_count * STRIP_STRIDE; - for i in 0..STRIP_ATTR_COUNT { - self.gl.vertex_attrib_i_pointer_with_i32( - i as u32, - 1, - WebGl2RenderingContext::UNSIGNED_INT, - STRIP_STRIDE, - i * 4 + alpha_byte_offset, - ); - } - + bind_strip_program( + self.gl, + &self.programs.strip_program, + &self.programs.strip_uniforms, + &self.programs.resources, + clip_texture_idx, + ); + set_strip_instance_offset(self.gl, alpha_start); self.gl.depth_mask(false); self.gl.enable(WebGl2RenderingContext::BLEND); self.gl.draw_arrays_instanced( @@ -2587,30 +2857,27 @@ 4, alpha_count, ); - - // Restore attribute offsets to base for subsequent passes. - for i in 0..STRIP_ATTR_COUNT { - self.gl.vertex_attrib_i_pointer_with_i32( - i as u32, - 1, - WebGl2RenderingContext::UNSIGNED_INT, - STRIP_STRIDE, - i * 4, - ); - } } // Restore state. self.gl.disable(WebGl2RenderingContext::DEPTH_TEST); self.gl.depth_mask(true); self.gl.enable(WebGl2RenderingContext::BLEND); + set_strip_instance_offset(self.gl, 0); } else { // Slot texture / intermediate: single draw with blending, no depth. + bind_strip_program( + self.gl, + &self.programs.strip_program, + &self.programs.strip_uniforms, + &self.programs.resources, + clip_texture_idx, + ); self.gl.draw_arrays_instanced( WebGl2RenderingContext::TRIANGLE_STRIP, 0, 4, - opaque_count + alpha_count, + alpha_count, ); }