Shader constants
diff --git a/sparse_strips/vello_hybrid/Cargo.toml b/sparse_strips/vello_hybrid/Cargo.toml index 300cfc0..d5179ae 100644 --- a/sparse_strips/vello_hybrid/Cargo.toml +++ b/sparse_strips/vello_hybrid/Cargo.toml
@@ -55,7 +55,7 @@ wgpu_default = ["wgpu", "wgpu/default"] # Add support for text rendering. text = ["vello_common/text"] -webgl = ["dep:js-sys", "dep:web-sys", "dep:vello_sparse_shaders", "vello_sparse_shaders/glsl"] +webgl = ["dep:js-sys", "dep:web-sys", "dep:vello_sparse_shaders", "vello_sparse_shaders/gles"] [lints] workspace = true
diff --git a/sparse_strips/vello_hybrid/src/render/common.rs b/sparse_strips/vello_hybrid/src/render/common.rs index 596ea12..75f95a1 100644 --- a/sparse_strips/vello_hybrid/src/render/common.rs +++ b/sparse_strips/vello_hybrid/src/render/common.rs
@@ -39,14 +39,6 @@ pub width: u32, /// Height of the rendering target. pub height: u32, - /// Height of a strip in the rendering. - pub strip_height: u32, - /// Number of trailing zeros in `alphas_tex_width` (log2 of width). - /// Pre-calculated on CPU since downlevel targets do not support `firstTrailingBit`. - pub alphas_tex_width_bits: u32, - /// Number of trailing zeros in the encoded paints texture width (log2 of width). - /// Pre-calculated on CPU since downlevel targets do not support `firstTrailingBit`. - pub encoded_paints_tex_width_bits: u32, /// A horizontal offset to apply to strips. pub strip_offset_x: i32, /// A vertical offset to apply to strips. @@ -70,6 +62,8 @@ /// and just apply the second negation manually in case we render to the final output surface /// in the WebGL backend. pub negate_ndc: u32, + /// Padding to ensure 16-byte alignment. + pub _padding: [u32; 3], } /// A GPU strip instance for rendering.
diff --git a/sparse_strips/vello_hybrid/src/render/webgl.rs b/sparse_strips/vello_hybrid/src/render/webgl.rs index 371ce8c..1b98801 100644 --- a/sparse_strips/vello_hybrid/src/render/webgl.rs +++ b/sparse_strips/vello_hybrid/src/render/webgl.rs
@@ -31,8 +31,8 @@ GPU_ENCODED_IMAGE_SIZE_TEXELS, GPU_LINEAR_GRADIENT_SIZE_TEXELS, GPU_RADIAL_GRADIENT_SIZE_TEXELS, GPU_SWEEP_GRADIENT_SIZE_TEXELS, GpuEncodedImage, GpuEncodedPaint, GpuLinearGradient, GpuRadialGradient, GpuSweepGradient, - pack_image_offset, pack_image_params, pack_image_size, pack_radial_kind_and_swapped, - pack_texture_width_and_extend_mode, pack_tint, + pack_image_offset, pack_image_params, pack_image_size, + pack_radial_kind_and_swapped, pack_texture_width_and_extend_mode, pack_tint, }, }, scene::Scene, @@ -56,7 +56,8 @@ pixmap::Pixmap, tile::Tile, }; -use vello_sparse_shaders::{clear_slots, filters, render_strips}; +use vello_sparse_shaders::ShaderConstants; +use vello_sparse_shaders::gles::{clear_slots, filters, render_strips}; use web_sys::wasm_bindgen::{JsCast, JsValue}; use web_sys::{ WebGl2RenderingContext, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlTexture, @@ -764,11 +765,17 @@ filter_context: &FilterContext, slot_count: usize, ) -> Self { - let strip_program = create_shader_program( - &gl, - render_strips::VERTEX_SOURCE, - render_strips::FRAGMENT_SOURCE, - ); + let max_texture_dimension_2d = get_max_texture_dimension_2d(&gl); + let shader_constants = ShaderConstants { + strip_height: u32::from(Tile::HEIGHT), + alphas_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), + encoded_paints_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), + gradient_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), + }; + + let vs_source = render_strips::vertex_source(&shader_constants); + let fs_source = render_strips::fragment_source(&shader_constants); + let strip_program = create_shader_program(&gl, &vs_source, &fs_source); let clear_program = create_shader_program( &gl, clear_slots::VERTEX_SOURCE, @@ -1097,12 +1104,10 @@ let config = Config { width: new_render_size.width, height: new_render_size.height, - strip_height: u32::from(Tile::HEIGHT), - alphas_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), - encoded_paints_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), strip_offset_x: 0, strip_offset_y: 0, negate_ndc: u32::from(negate_ndc), + ..Config::zeroed() }; gl.bind_buffer( @@ -1123,13 +1128,11 @@ let slot_config = Config { width: u32::from(WideTile::WIDTH), height: u32::from(Tile::HEIGHT) * total_slots, - strip_height: u32::from(Tile::HEIGHT), - alphas_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), - encoded_paints_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), strip_offset_x: 0, strip_offset_y: 0, // Always use y-down when rendering to slots. negate_ndc: 0, + ..Config::zeroed() }; gl.bind_buffer( @@ -2024,20 +2027,10 @@ let config = Config { width: atlas_width, height: atlas_height, - strip_height: u32::from(Tile::HEIGHT), - alphas_tex_width_bits: self - .programs - .resources - .max_texture_dimension_2d - .trailing_zeros(), - encoded_paints_tex_width_bits: self - .programs - .resources - .max_texture_dimension_2d - .trailing_zeros(), strip_offset_x, strip_offset_y, negate_ndc: 0, + ..Config::zeroed() }; let buf = &self.programs.resources.filter_config_buffer; self.gl
diff --git a/sparse_strips/vello_hybrid/src/render/wgpu.rs b/sparse_strips/vello_hybrid/src/render/wgpu.rs index 3aa9419..482b4af 100644 --- a/sparse_strips/vello_hybrid/src/render/wgpu.rs +++ b/sparse_strips/vello_hybrid/src/render/wgpu.rs
@@ -53,6 +53,7 @@ pixmap::Pixmap, tile::Tile, }; +use vello_sparse_shaders::ShaderConstants; use wgpu::{ BindGroup, BindGroupLayout, BlendState, Buffer, ColorTargetState, ColorWrites, CommandEncoder, Device, Extent3d, PipelineCompilationOptions, Queue, RenderPassColorAttachment, @@ -1018,9 +1019,19 @@ }], }); + let max_texture_dimension_2d = device.limits().max_texture_dimension_2d; + let shader_constants = ShaderConstants { + strip_height: Tile::HEIGHT.into(), + alphas_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), + encoded_paints_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), + gradient_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), + }; + let strip_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Strip Shader"), - source: wgpu::ShaderSource::Wgsl(vello_sparse_shaders::wgsl::RENDER_STRIPS.into()), + source: wgpu::ShaderSource::Wgsl( + vello_sparse_shaders::wgsl::render_strips(&shader_constants).into(), + ), }); let clear_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { @@ -1310,10 +1321,8 @@ width: u32::from(WideTile::WIDTH), height: u32::from(Tile::HEIGHT) * slot_count as u32, }, - device.limits().max_texture_dimension_2d, ); - 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, @@ -1326,7 +1335,6 @@ width: render_target_config.width, height: render_target_config.height, }, - max_texture_dimension_2d, ); let AtlasConfig { @@ -1489,22 +1497,16 @@ }) } - fn create_config_buffer( - device: &Device, - render_size: &RenderSize, - alpha_texture_width: u32, - ) -> Buffer { + fn create_config_buffer(device: &Device, render_size: &RenderSize) -> Buffer { device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Config Buffer"), contents: bytemuck::bytes_of(&Config { width: render_size.width, height: render_size.height, - strip_height: Tile::HEIGHT.into(), - alphas_tex_width_bits: alpha_texture_width.trailing_zeros(), - encoded_paints_tex_width_bits: alpha_texture_width.trailing_zeros(), strip_offset_x: 0, strip_offset_y: 0, negate_ndc: 0, + ..Config::zeroed() }), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }) @@ -1763,7 +1765,7 @@ self.maybe_resize_alphas_tex(device, max_texture_dimension_2d, alphas.len()); self.maybe_resize_encoded_paints_tex(device, max_texture_dimension_2d, paint_idxs); self.maybe_resize_filter_tex(device, max_texture_dimension_2d, filter_context); - self.maybe_update_config_buffer(queue, max_texture_dimension_2d, new_render_size); + self.maybe_update_config_buffer(queue, new_render_size); self.upload_alpha_texture(queue, alphas); self.upload_encoded_paints_texture(queue, encoded_paints); @@ -1943,19 +1945,16 @@ fn maybe_update_config_buffer( &mut self, queue: &Queue, - max_texture_dimension_2d: u32, new_render_size: &RenderSize, ) { if self.render_size != *new_render_size { let config = Config { width: new_render_size.width, height: new_render_size.height, - strip_height: Tile::HEIGHT.into(), - alphas_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), - encoded_paints_tex_width_bits: max_texture_dimension_2d.trailing_zeros(), strip_offset_x: 0, strip_offset_y: 0, negate_ndc: 0, + ..Config::zeroed() }; let mut buffer = queue .write_buffer_with(&self.resources.view_config_buffer, 0, SIZE_OF_CONFIG) @@ -2277,24 +2276,10 @@ contents: bytemuck::bytes_of(&Config { width: atlas_size.width, height: atlas_size.height, - strip_height: Tile::HEIGHT.into(), - alphas_tex_width_bits: self - .programs - .resources - .alphas_texture - .size() - .width - .trailing_zeros(), - encoded_paints_tex_width_bits: self - .programs - .resources - .alphas_texture - .size() - .width - .trailing_zeros(), strip_offset_x, strip_offset_y, negate_ndc: 0, + ..Config::zeroed() }), usage: wgpu::BufferUsages::UNIFORM, });
diff --git a/sparse_strips/vello_sparse_shaders/Cargo.toml b/sparse_strips/vello_sparse_shaders/Cargo.toml index 40e4a6c..11c39bc 100644 --- a/sparse_strips/vello_sparse_shaders/Cargo.toml +++ b/sparse_strips/vello_sparse_shaders/Cargo.toml
@@ -2,7 +2,7 @@ name = "vello_sparse_shaders" # When updating, also update the version in the workspace dependency in the root Cargo.toml version = "0.0.7" -description = "Provide compilation of wgsl to glsl to support the WebGL `vello_hybrid` backend." +description = "Provide compilation of wgsl to glsl es to support the WebGL `vello_hybrid` backend." categories = ["rendering", "graphics"] keywords = ["2d", "vector-graphics"] edition.workspace = true @@ -23,7 +23,7 @@ naga = { workspace = true, features = ["wgsl-in", "glsl-out"], optional = true } [features] -glsl = ["dep:naga"] +gles = ["dep:naga"] [lints] workspace = true
diff --git a/sparse_strips/vello_sparse_shaders/build.rs b/sparse_strips/vello_sparse_shaders/build.rs index 90218ef..26d0e25 100644 --- a/sparse_strips/vello_sparse_shaders/build.rs +++ b/sparse_strips/vello_sparse_shaders/build.rs
@@ -9,15 +9,15 @@ use std::path::{Path, PathBuf}; #[allow(warnings)] -#[cfg(feature = "glsl")] +#[cfg(feature = "gles")] #[path = "src/compile.rs"] mod compile; #[allow(warnings)] -#[cfg(feature = "glsl")] +#[cfg(feature = "gles")] #[path = "src/types.rs"] mod types; -#[cfg(feature = "glsl")] +#[cfg(feature = "gles")] use compile::compile_wgsl_shader; // TODO: Format the generated code via `rustfmt`. @@ -76,13 +76,10 @@ // Implementation for creating a CompiledGlsl struct per shader assuming the standard entry // names of `vs_main` and `fs_main`. - #[cfg(feature = "glsl")] + #[cfg(feature = "gles")] { - writeln!( - buf, - "/// Build time GLSL shaders derived from wgsl shaders." - ) - .unwrap(); + writeln!(buf, "/// Build-time GLSL ES shaders derived from WGSL shaders.").unwrap(); + writeln!(buf, "pub mod gles {{").unwrap(); for (shader_name, shader_source) in shader_infos { let compiled = compile_wgsl_shader(shader_source, "vs_main", "fs_main"); @@ -90,6 +87,8 @@ let generated_code = compiled.to_generated_code(shader_name); writeln!(buf, "{generated_code}").unwrap(); } + + writeln!(buf, "}}").unwrap(); } }
diff --git a/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl b/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl index 30f558f..e546e49 100644 --- a/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl +++ b/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl
@@ -105,16 +105,6 @@ width: u32, // Height of the rendering target height: u32, - // Height of a strip in the rendering - // CAUTION: When changing this value, you must also update the fragment shader's - // logic to handle the new strip height. - strip_height: u32, - // Number of trailing zeros in alphas_tex_width (log2 of width). - // Pre-calculated on CPU since WebGL2 doesn't support `firstTrailingBit`. - alphas_tex_width_bits: u32, - // Number of trailing zeros in encoded_paints_tex_width (log2 of width). - // Pre-calculated on CPU since WebGL2 doesn't support `firstTrailingBit`. - encoded_paints_tex_width_bits: u32, // An offset to apply to the strip. // // In most cases, these will be zero. However, @@ -127,6 +117,17 @@ ndc_y_negate: u32, } +// Shader-lifetime constants whose placeholder values (0u / 1u) are replaced +// with device-specific literals at renderer init time via string substitution. +// +// NOTE: Since we transpile this shader to GLES at build-time, we cannot use WGPU +// shader constants because, to transpile a shader of that type to GLES, requires +// Naga, which we want to avoid adding to the binary. +const STRIP_HEIGHT: u32 = 0u; +const ALPHAS_TEX_WIDTH_BITS: u32 = 0u; +const ENCODED_PAINTS_TEX_WIDTH_BITS: u32 = 0u; +const GRADIENT_TEX_WIDTH_BITS: u32 = 0u; + // A `StripInstance` can represent either a **normal strip** (representing a sparse fill or alpha fill of height // Tile::HEIGHT) or a **rect strip** (an entire rectangle rendered as a single quad, with anti-aliasing support). // The two modes are distinguished by RECT_STRIP_FLAG (bit 31 of `paint_and_rect_flag`). @@ -263,7 +264,7 @@ let dense_width = instance.widths_or_rect_height >> 16u; let is_rect = (instance.paint_and_rect_flag & RECT_STRIP_FLAG) != 0u; - var height = config.strip_height; + var height = STRIP_HEIGHT; if is_rect { height = dense_width; out.dense_end_or_rect_size = width | (dense_width << 16u); @@ -350,15 +351,13 @@ // channel encodes the alpha values for a single column within a strip. // Divide x by 4 to get the texel position. let alphas_index = x; - let tex_dimensions = textureDimensions(alphas_texture); - let alphas_tex_width = tex_dimensions.x; // Which texel contains the alpha values for this column let texel_index = alphas_index / 4u; // Which channel (R,G,B,A) in the texel contains the alpha values for this column let channel_index = alphas_index % 4u; // Calculate texel coordinates - let tex_x = texel_index & (alphas_tex_width - 1u); - let tex_y = texel_index >> config.alphas_tex_width_bits; + let tex_x = texel_index & ((1u << ALPHAS_TEX_WIDTH_BITS) - 1u); + let tex_y = texel_index >> ALPHAS_TEX_WIDTH_BITS; // Load all 4 channels from the texture let rgba_values = textureLoad(alphas_texture, vec2<u32>(tex_x, tex_y), 0); @@ -520,7 +519,7 @@ // within the wide tile slot! Therefore, we need to subtract the strip // offset here. let clip_x = u32(i32(in.position.x) - config.strip_offset_x) & 0xFFu; - let clip_y = (u32(i32(sample_y) - config.strip_offset_y) & 3u) + in.payload * config.strip_height; + let clip_y = (u32(i32(sample_y) - config.strip_offset_y) & 3u) + in.payload * STRIP_HEIGHT; let clip_in_color = textureLoad(clip_input_texture, vec2(clip_x, clip_y), 0); // Extract opacity from first 8 bits (quantized from [0, 255]) @@ -540,11 +539,11 @@ // See the comment above for why we need to subtract the strip offset. let clip_x = u32(i32(in.position.x) - config.strip_offset_x) & 0xFFu; let clip_y_in_strip = u32(i32(sample_y) - config.strip_offset_y) & 3u; - let src_y = clip_y_in_strip + src_slot * config.strip_height; + let src_y = clip_y_in_strip + src_slot * STRIP_HEIGHT; let src_color = textureLoad(clip_input_texture, vec2(clip_x, src_y), 0); // Read destination color from slot - let dest_y = clip_y_in_strip + dest_slot * config.strip_height; + let dest_y = clip_y_in_strip + dest_slot * STRIP_HEIGHT; let dest_color = textureLoad(clip_input_texture, vec2(clip_x, dest_y), 0); final_color = blend_mix_compose(dest_color, src_color * opacity * alpha, compose_mode, mix_mode); @@ -856,8 +855,8 @@ // Convert a flat texel index to 2D texture coordinates for the encoded paints texture. fn encoded_paint_coord(flat_idx: u32) -> vec2<u32> { return vec2<u32>( - flat_idx & ((1u << config.encoded_paints_tex_width_bits) - 1u), - flat_idx >> config.encoded_paints_tex_width_bits + flat_idx & ((1u << ENCODED_PAINTS_TEX_WIDTH_BITS) - 1u), + flat_idx >> ENCODED_PAINTS_TEX_WIDTH_BITS ); } @@ -1105,9 +1104,8 @@ // Calculate absolute position in flat gradient texture let flat_coord = gradient_start + t_offset; // Convert flat coordinate to 2D texture coordinate - let gradient_tex_width = textureDimensions(gradient_texture).x; - let tex_x = flat_coord % gradient_tex_width; - let tex_y = flat_coord / gradient_tex_width; + let tex_x = flat_coord & ((1u << GRADIENT_TEX_WIDTH_BITS) - 1u); + let tex_y = flat_coord >> GRADIENT_TEX_WIDTH_BITS; // Sample from the gradient texture at calculated position let gradient_color = textureLoad(gradient_texture, vec2<u32>(tex_x, tex_y), 0); return gradient_color;
diff --git a/sparse_strips/vello_sparse_shaders/src/lib.rs b/sparse_strips/vello_sparse_shaders/src/lib.rs index 6932063..25a3b59 100644 --- a/sparse_strips/vello_sparse_shaders/src/lib.rs +++ b/sparse_strips/vello_sparse_shaders/src/lib.rs
@@ -3,9 +3,75 @@ //! This is a utility library to help integrate `vello_hybrid` WebGPU wgsl shaders into glsl. -#[cfg(feature = "glsl")] +extern crate alloc; + +#[cfg(feature = "gles")] mod compile; -#[cfg(feature = "glsl")] +mod specialize; +#[cfg(feature = "gles")] mod types; -include!(concat!(env!("OUT_DIR"), "/compiled_shaders.rs")); +pub use specialize::ShaderConstants; + +mod generated { + include!(concat!(env!("OUT_DIR"), "/compiled_shaders.rs")); +} + +/// WGSL shader sources. +pub mod wgsl { + use alloc::string::String; + + use crate::ShaderConstants; + + /// Returns specialised WGSL source. + pub fn render_strips(constants: &ShaderConstants) -> String { + constants.specialize_wgsl(super::generated::wgsl::RENDER_STRIPS) + } + + /// WGSL source for the `clear_slots` shader. + pub const CLEAR_SLOTS: &str = super::generated::wgsl::CLEAR_SLOTS; + + /// WGSL source for the `filters` shader. + pub const FILTERS: &str = super::generated::wgsl::FILTERS; +} + +/// GLSL ES shader sources. +#[cfg(feature = "gles")] +pub mod gles { + /// GLSL ES sources for the `render_strips` shader. + pub mod render_strips { + use alloc::string::String; + + use crate::ShaderConstants; + + /// Returns specialised GLSL ES vertex source. + pub fn vertex_source(constants: &ShaderConstants) -> String { + constants.specialize_glsl(crate::generated::gles::render_strips::VERTEX_SOURCE) + } + + /// Returns specialised GLSL ES fragment source. + pub fn fragment_source(constants: &ShaderConstants) -> String { + constants.specialize_glsl(crate::generated::gles::render_strips::FRAGMENT_SOURCE) + } + + /// Vertex-stage reflection metadata. + pub mod vertex { + pub use crate::generated::gles::render_strips::vertex::*; + } + + /// Fragment-stage reflection metadata. + pub mod fragment { + pub use crate::generated::gles::render_strips::fragment::*; + } + } + + /// GLSL ES sources for the `clear_slots` shader. + pub mod clear_slots { + pub use crate::generated::gles::clear_slots::*; + } + + /// GLSL ES sources for the `filters` shader. + pub mod filters { + pub use crate::generated::gles::filters::*; + } +}
diff --git a/sparse_strips/vello_sparse_shaders/src/specialize.rs b/sparse_strips/vello_sparse_shaders/src/specialize.rs new file mode 100644 index 0000000..63273fb --- /dev/null +++ b/sparse_strips/vello_sparse_shaders/src/specialize.rs
@@ -0,0 +1,113 @@ +// Copyright 2026 the Vello Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Shader specialization via string substitution. +//! +//! The WGSL shader source declares shader build time constants with placeholder +//! values. At shader build time (during runtime on the app), [`ShaderConstants::specialize_wgsl`] +//! (and [`ShaderConstants::specialize_glsl`] for the WebGL backend) replace these +//! placeholders with device-specific literals, turning them into compile-time constants. +//! +//! Shader constants are supported by Naga, but would require to be shipped in the binary +//! at runtime for Naga to perform the substitution. We intentionally created `vello_sparse_shaders` +//! to avoid this binary bloat, so instead we perform this substitution ourselves. + +use alloc::format; +use alloc::string::String; + +/// Values that are constant for the lifetime of a renderer. At shader build time their +/// placeholder `const` declarations in the shader source are replaced with literals +/// via string substitution. +/// +/// For example, for `ShaderConstants` with `strip_height` set to `16`, +/// ```wgsl +/// const STRIP_HEIGHT: u32 = 0u; +/// ``` +/// Is string replaced with: +/// ```wgsl +/// const STRIP_HEIGHT: u32 = 16u; +/// ``` +#[derive(Debug, Clone)] +pub struct ShaderConstants { + /// Height of a strip. + pub strip_height: u32, + /// log2 of the alphas texture width (for bit-shift addressing). + pub alphas_tex_width_bits: u32, + /// log2 of the encoded-paints texture width (for bit-shift addressing). + pub encoded_paints_tex_width_bits: u32, + /// log2 of the gradient texture width (for bit-shift addressing). + pub gradient_tex_width_bits: u32, +} + +/// Assumes that each replacement is expected to appear at most once and to be fully contained within a single line. +fn replace_all_once(source: &str, replacements: &[(&str, &str)]) -> String { + let mut result = String::with_capacity(source.len() + 128); + for (i, line) in source.split('\n').enumerate() { + if i > 0 { + result.push('\n'); + } + if let Some((pat, rep)) = replacements.iter().find(|(pat, _)| line.contains(pat)) { + result.push_str(&line.replacen(pat, rep, 1)); + } else { + result.push_str(line); + } + } + result +} + +impl ShaderConstants { + /// Specialize a WGSL shader source by replacing build time `const` declarations. + pub(crate) fn specialize_wgsl(&self, source: &str) -> String { + let strip_height = format!("const STRIP_HEIGHT: u32 = {}u;", self.strip_height); + let alphas = format!( + "const ALPHAS_TEX_WIDTH_BITS: u32 = {}u;", + self.alphas_tex_width_bits + ); + let paints = format!( + "const ENCODED_PAINTS_TEX_WIDTH_BITS: u32 = {}u;", + self.encoded_paints_tex_width_bits + ); + let gradient = format!( + "const GRADIENT_TEX_WIDTH_BITS: u32 = {}u;", + self.gradient_tex_width_bits + ); + + replace_all_once( + source, + &[ + ("const STRIP_HEIGHT: u32 = 0u;", &strip_height), + ("const ALPHAS_TEX_WIDTH_BITS: u32 = 0u;", &alphas), + ("const ENCODED_PAINTS_TEX_WIDTH_BITS: u32 = 0u;", &paints), + ("const GRADIENT_TEX_WIDTH_BITS: u32 = 0u;", &gradient), + ], + ) + } + + /// Specialize a GLSL shader source by replacing build time `const` declarations. + #[cfg(feature = "gles")] + pub(crate) fn specialize_glsl(&self, source: &str) -> String { + let strip_height = format!("const uint STRIP_HEIGHT = {}u;", self.strip_height); + let alphas = format!( + "const uint ALPHAS_TEX_WIDTH_BITS = {}u;", + self.alphas_tex_width_bits + ); + let paints = format!( + "const uint ENCODED_PAINTS_TEX_WIDTH_BITS = {}u;", + self.encoded_paints_tex_width_bits + ); + let gradient = format!( + "const uint GRADIENT_TEX_WIDTH_BITS = {}u;", + self.gradient_tex_width_bits + ); + + replace_all_once( + source, + &[ + ("const uint STRIP_HEIGHT = 0u;", &strip_height), + ("const uint ALPHAS_TEX_WIDTH_BITS = 0u;", &alphas), + ("const uint ENCODED_PAINTS_TEX_WIDTH_BITS = 0u;", &paints), + ("const uint GRADIENT_TEX_WIDTH_BITS = 0u;", &gradient), + ], + ) + } +}