Skip to content
6 changes: 4 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,10 @@ 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,
/// Number of trailing zeros in the atlas texture dimension (log2 of the square atlas size).
/// Used to normalize pixel coordinates to UVs for `textureSample` without calling
/// `textureDimensions` per pixel.
pub atlas_dim_bits: u32,
}

/// A GPU strip instance for rendering.
Expand Down
46 changes: 42 additions & 4 deletions sparse_strips/vello_hybrid/src/render/webgl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,9 @@ struct WebGlResources {
filter_atlas_width: u32,
/// Cached atlas height for creating new filter atlas textures.
filter_atlas_height: u32,
/// log2 of the square atlas texture dimension, passed to the shader uniform
/// so it can normalize pixel coords to UVs without `textureDimensions`.
atlas_dim_bits: u32,
}

/// Config for the clear slots pipeline.
Expand Down Expand Up @@ -1167,7 +1170,7 @@ impl WebGlPrograms {
encoded_paints_tex_width_bits: max_texture_dimension_2d.trailing_zeros(),
strip_offset_x: 0,
strip_offset_y: 0,
_padding: 0,
atlas_dim_bits: self.resources.atlas_dim_bits,
};

gl.bind_buffer(
Expand All @@ -1193,7 +1196,7 @@ impl WebGlPrograms {
encoded_paints_tex_width_bits: max_texture_dimension_2d.trailing_zeros(),
strip_offset_x: 0,
strip_offset_y: 0,
_padding: 0,
atlas_dim_bits: self.resources.atlas_dim_bits,
};

gl.bind_buffer(
Expand Down Expand Up @@ -1826,7 +1829,36 @@ fn create_texture(gl: &WebGl2RenderingContext) -> WebGlTexture {
/// Create a texture array with nearest neighbor sampling and
/// clamp-to-edge wrapping.
fn create_texture_array(gl: &WebGl2RenderingContext) -> WebGlTexture {
create_texture_inner(gl, WebGl2RenderingContext::TEXTURE_2D_ARRAY)
let target = WebGl2RenderingContext::TEXTURE_2D_ARRAY;
let texture = gl.create_texture().unwrap();
gl.active_texture(WebGl2RenderingContext::TEXTURE0);
gl.bind_texture(target, Some(&texture));
// The filter and wrap modes are irrelevant because the shader
// (`render_strips.wgsl`) exclusively uses `textureLoad`, which bypasses
// the sampler entirely.
gl.tex_parameteri(
target,
WebGl2RenderingContext::TEXTURE_MIN_FILTER,
WebGl2RenderingContext::LINEAR as i32,
);
gl.tex_parameteri(
target,
WebGl2RenderingContext::TEXTURE_MAG_FILTER,
WebGl2RenderingContext::LINEAR as i32,
);
gl.tex_parameteri(
target,
WebGl2RenderingContext::TEXTURE_WRAP_S,
WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
);
gl.tex_parameteri(
target,
WebGl2RenderingContext::TEXTURE_WRAP_T,
WebGl2RenderingContext::CLAMP_TO_EDGE as i32,
);
gl.tex_parameteri(target, WebGl2RenderingContext::TEXTURE_MAX_LEVEL, 0);

texture
}

fn create_texture_inner(gl: &WebGl2RenderingContext, target: u32) -> WebGlTexture {
Expand Down Expand Up @@ -1889,6 +1921,11 @@ fn create_webgl_resources(
initial_atlas_count,
..
} = image_cache.atlas_manager().config();
debug_assert_eq!(
atlas_width, atlas_height,
"Atlas must be square for atlas_dim_bits to work"
);
let atlas_dim_bits = atlas_width.trailing_zeros();
let atlas_texture_array =
create_atlas_texture_array(gl, *atlas_width, *atlas_height, *initial_atlas_count as u32);

Expand Down Expand Up @@ -1960,6 +1997,7 @@ fn create_webgl_resources(
filter_config_buffer,
filter_atlas_width: *filter_atlas_width,
filter_atlas_height: *filter_atlas_height,
atlas_dim_bits,
}
}

Expand Down Expand Up @@ -2151,7 +2189,7 @@ impl WebGlRendererContext<'_> {
.trailing_zeros(),
strip_offset_x,
strip_offset_y,
_padding: 0,
atlas_dim_bits: self.programs.resources.atlas_dim_bits,
};
let buf = &self.programs.resources.filter_config_buffer;
self.gl
Expand Down
97 changes: 71 additions & 26 deletions sparse_strips/vello_hybrid/src/render/wgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,8 @@ struct GpuResources {
atlas_texture_array: Texture,
/// View for atlas texture array
atlas_texture_array_view: TextureView,
/// Bilinear sampler for GPU-native image sampling
atlas_sampler: Sampler,
/// Bind group for atlas textures (as texture array)
atlas_bind_group: BindGroup,
/// Filter atlas textures and their associated views/bind groups.
Expand Down Expand Up @@ -881,6 +883,10 @@ struct GpuResources {
/// Placeholder atlas bind group with a 1x1 dummy texture, used during
/// `render_to_atlas` to avoid a read-write conflict on the real atlas texture.
stub_atlas_bind_group: BindGroup,

/// log2 of the square atlas texture dimension, passed to the shader uniform
/// so it can normalize pixel coords to UVs without `textureDimensions`.
atlas_dim_bits: u32,
}

const SIZE_OF_CONFIG: NonZeroU64 = NonZeroU64::new(size_of::<Config>() as u64).unwrap();
Expand Down Expand Up @@ -960,18 +966,33 @@ impl Programs {
let atlas_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Atlas Texture Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
},
count: None,
},
count: None,
}],
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});

let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Atlas Bilinear Sampler"),
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});

let encoded_paints_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Encoded Paints Bind Group Layout"),
Expand Down Expand Up @@ -1304,16 +1325,29 @@ impl Programs {
slot_count as u64 * size_of::<u32>() as u64,
);

let max_texture_dimension_2d = device.limits().max_texture_dimension_2d;

let AtlasConfig {
atlas_size: (atlas_width, atlas_height),
initial_atlas_count,
..
} = image_cache.atlas_manager().config();
debug_assert_eq!(
atlas_width, atlas_height,
"Atlas must be square for atlas_dim_bits to work"
);
let atlas_dim_bits = atlas_width.trailing_zeros();

let slot_config_buffer = Self::create_config_buffer(
device,
&RenderSize {
width: u32::from(WideTile::WIDTH),
height: u32::from(Tile::HEIGHT) * slot_count as u32,
},
device.limits().max_texture_dimension_2d,
max_texture_dimension_2d,
atlas_dim_bits,
);

let max_texture_dimension_2d = device.limits().max_texture_dimension_2d;
const INITIAL_ALPHA_TEXTURE_HEIGHT: u32 = 1;
let alphas_texture = Self::create_alphas_texture(
device,
Expand All @@ -1327,13 +1361,8 @@ impl Programs {
height: render_target_config.height,
},
max_texture_dimension_2d,
atlas_dim_bits,
);

let AtlasConfig {
atlas_size: (atlas_width, atlas_height),
initial_atlas_count,
..
} = image_cache.atlas_manager().config();
let (atlas_texture_array, atlas_texture_array_view) = Self::create_atlas_texture_array(
device,
*atlas_width,
Expand All @@ -1344,15 +1373,20 @@ impl Programs {
device,
&atlas_bind_group_layout,
&atlas_texture_array_view,
&atlas_sampler,
);

// Create a 1x1 stub atlas texture array for use during render_to_atlas.
// This avoids the read-write conflict that occurs when the real atlas is both
// a shader input (bind group) and render target in the same pass.
let (_stub_atlas_texture, stub_atlas_view) =
Self::create_atlas_texture_array(device, 1, 1, 1);
let stub_atlas_bind_group =
Self::create_atlas_bind_group(device, &atlas_bind_group_layout, &stub_atlas_view);
let stub_atlas_bind_group = Self::create_atlas_bind_group(
device,
&atlas_bind_group_layout,
&stub_atlas_view,
&atlas_sampler,
);

const INITIAL_ENCODED_PAINTS_TEXTURE_HEIGHT: u32 = 1;
let encoded_paints_data = vec![
Expand Down Expand Up @@ -1429,6 +1463,7 @@ impl Programs {
alphas_texture,
atlas_texture_array,
atlas_texture_array_view,
atlas_sampler,
atlas_bind_group,
filter_atlas,
stub_atlas_bind_group,
Expand All @@ -1439,6 +1474,7 @@ impl Programs {
filter_data_texture,
filter_base_bind_group,
view_config_buffer,
atlas_dim_bits,
};

Self {
Expand Down Expand Up @@ -1493,6 +1529,7 @@ impl Programs {
device: &Device,
render_size: &RenderSize,
alpha_texture_width: u32,
atlas_dim_bits: u32,
) -> Buffer {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Config Buffer"),
Expand All @@ -1504,7 +1541,7 @@ impl Programs {
encoded_paints_tex_width_bits: alpha_texture_width.trailing_zeros(),
strip_offset_x: 0,
strip_offset_y: 0,
_padding: 0,
atlas_dim_bits,
}),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
})
Expand Down Expand Up @@ -1609,14 +1646,21 @@ impl Programs {
device: &Device,
atlas_bind_group_layout: &BindGroupLayout,
atlas_texture_array_view: &TextureView,
atlas_sampler: &Sampler,
) -> BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Atlas Bind Group"),
layout: atlas_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(atlas_texture_array_view),
}],
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(atlas_texture_array_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(atlas_sampler),
},
],
})
}

Expand Down Expand Up @@ -1955,7 +1999,7 @@ impl Programs {
encoded_paints_tex_width_bits: max_texture_dimension_2d.trailing_zeros(),
strip_offset_x: 0,
strip_offset_y: 0,
_padding: 0,
atlas_dim_bits: self.resources.atlas_dim_bits,
};
let mut buffer = queue
.write_buffer_with(&self.resources.view_config_buffer, 0, SIZE_OF_CONFIG)
Expand Down Expand Up @@ -1999,6 +2043,7 @@ impl Programs {
device,
atlas_bind_group_layout,
&new_atlas_texture_array_view,
&resources.atlas_sampler,
);

// Replace the old resources
Expand Down Expand Up @@ -2294,7 +2339,7 @@ impl RendererContext<'_> {
.trailing_zeros(),
strip_offset_x,
strip_offset_y,
_padding: 0,
atlas_dim_bits: self.programs.resources.atlas_dim_bits,
}),
usage: wgpu::BufferUsages::UNIFORM,
});
Expand Down
4 changes: 4 additions & 0 deletions sparse_strips/vello_hybrid/src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ impl Scene {
}
}

pub fn paint_transform(&self) -> &Affine {
&self.render_state.paint_transform
}

/// Encode the current paint into a `Paint` that can be used for rendering.
///
/// For solid colors, this is a simple conversion. For gradients and images,
Expand Down
Loading
Loading