blade: mono/poly chrome sprite rendering

This commit is contained in:
Dzmitry Malyshau 2024-02-03 21:30:47 -08:00
parent 59642bf29a
commit d0a0ce1885
4 changed files with 208 additions and 22 deletions

View file

@ -4,7 +4,7 @@
use super::{BladeBelt, BladeBeltDescriptor}; use super::{BladeBelt, BladeBeltDescriptor};
use crate::{ use crate::{
AtlasTextureKind, AtlasTile, BladeAtlas, Bounds, ContentMask, Hsla, Path, PathId, PathVertex, AtlasTextureKind, AtlasTile, BladeAtlas, Bounds, ContentMask, Hsla, Path, PathId, PathVertex,
PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Underline, PATH_TEXTURE_FORMAT, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Underline, MonochromeSprite, PolychromeSprite, PATH_TEXTURE_FORMAT,
}; };
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use collections::HashMap; use collections::HashMap;
@ -43,8 +43,8 @@ struct ShaderPathRasterizationData {
#[derive(blade_macros::ShaderData)] #[derive(blade_macros::ShaderData)]
struct ShaderPathsData { struct ShaderPathsData {
globals: GlobalParams, globals: GlobalParams,
t_tile: gpu::TextureView, t_sprite: gpu::TextureView,
s_tile: gpu::Sampler, s_sprite: gpu::Sampler,
b_path_sprites: gpu::BufferPiece, b_path_sprites: gpu::BufferPiece,
} }
@ -54,6 +54,22 @@ struct ShaderUnderlinesData {
b_underlines: gpu::BufferPiece, b_underlines: gpu::BufferPiece,
} }
#[derive(blade_macros::ShaderData)]
struct ShaderMonoSpritesData {
globals: GlobalParams,
t_sprite: gpu::TextureView,
s_sprite: gpu::Sampler,
b_mono_sprites: gpu::BufferPiece,
}
#[derive(blade_macros::ShaderData)]
struct ShaderPolySpritesData {
globals: GlobalParams,
t_sprite: gpu::TextureView,
s_sprite: gpu::Sampler,
b_poly_sprites: gpu::BufferPiece,
}
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)] #[repr(C)]
struct PathSprite { struct PathSprite {
@ -68,10 +84,14 @@ struct BladePipelines {
path_rasterization: gpu::RenderPipeline, path_rasterization: gpu::RenderPipeline,
paths: gpu::RenderPipeline, paths: gpu::RenderPipeline,
underlines: gpu::RenderPipeline, underlines: gpu::RenderPipeline,
mono_sprites: gpu::RenderPipeline,
poly_sprites: gpu::RenderPipeline,
} }
impl BladePipelines { impl BladePipelines {
fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self { fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self {
use gpu::ShaderData as _;
let shader = gpu.create_shader(gpu::ShaderDesc { let shader = gpu.create_shader(gpu::ShaderDesc {
source: include_str!("shaders.wgsl"), source: include_str!("shaders.wgsl"),
}); });
@ -83,17 +103,13 @@ impl BladePipelines {
); );
shader.check_struct_size::<PathSprite>(); shader.check_struct_size::<PathSprite>();
shader.check_struct_size::<Underline>(); shader.check_struct_size::<Underline>();
shader.check_struct_size::<MonochromeSprite>();
let quads_layout = <ShaderQuadsData as gpu::ShaderData>::layout(); shader.check_struct_size::<PolychromeSprite>();
let shadows_layout = <ShaderShadowsData as gpu::ShaderData>::layout();
let path_rasterization_layout = <ShaderPathRasterizationData as gpu::ShaderData>::layout();
let paths_layout = <ShaderPathsData as gpu::ShaderData>::layout();
let underlines_layout = <ShaderUnderlinesData as gpu::ShaderData>::layout();
Self { Self {
quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc { quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "quads", name: "quads",
data_layouts: &[&quads_layout], data_layouts: &[&ShaderQuadsData::layout()],
vertex: shader.at("vs_quad"), vertex: shader.at("vs_quad"),
primitive: gpu::PrimitiveState { primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip, topology: gpu::PrimitiveTopology::TriangleStrip,
@ -109,7 +125,7 @@ impl BladePipelines {
}), }),
shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc { shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "shadows", name: "shadows",
data_layouts: &[&shadows_layout], data_layouts: &[&ShaderShadowsData::layout()],
vertex: shader.at("vs_shadow"), vertex: shader.at("vs_shadow"),
primitive: gpu::PrimitiveState { primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip, topology: gpu::PrimitiveTopology::TriangleStrip,
@ -125,7 +141,7 @@ impl BladePipelines {
}), }),
path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc { path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "path_rasterization", name: "path_rasterization",
data_layouts: &[&path_rasterization_layout], data_layouts: &[&ShaderPathRasterizationData::layout()],
vertex: shader.at("vs_path_rasterization"), vertex: shader.at("vs_path_rasterization"),
primitive: gpu::PrimitiveState { primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip, topology: gpu::PrimitiveTopology::TriangleStrip,
@ -141,7 +157,7 @@ impl BladePipelines {
}), }),
paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc { paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "paths", name: "paths",
data_layouts: &[&paths_layout], data_layouts: &[&ShaderPathsData::layout()],
vertex: shader.at("vs_path"), vertex: shader.at("vs_path"),
primitive: gpu::PrimitiveState { primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip, topology: gpu::PrimitiveTopology::TriangleStrip,
@ -157,7 +173,7 @@ impl BladePipelines {
}), }),
underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc { underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "underlines", name: "underlines",
data_layouts: &[&underlines_layout], data_layouts: &[&ShaderUnderlinesData::layout()],
vertex: shader.at("vs_underline"), vertex: shader.at("vs_underline"),
primitive: gpu::PrimitiveState { primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip, topology: gpu::PrimitiveTopology::TriangleStrip,
@ -171,6 +187,38 @@ impl BladePipelines {
write_mask: gpu::ColorWrites::default(), write_mask: gpu::ColorWrites::default(),
}], }],
}), }),
mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "mono-sprites",
data_layouts: &[&ShaderMonoSpritesData::layout()],
vertex: shader.at("vs_mono_sprite"),
primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
fragment: shader.at("fs_mono_sprite"),
color_targets: &[gpu::ColorTargetState {
format: surface_format,
blend: Some(gpu::BlendState::ALPHA_BLENDING),
write_mask: gpu::ColorWrites::default(),
}],
}),
poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
name: "poly-sprites",
data_layouts: &[&ShaderPolySpritesData::layout()],
vertex: shader.at("vs_poly_sprite"),
primitive: gpu::PrimitiveState {
topology: gpu::PrimitiveTopology::TriangleStrip,
..Default::default()
},
depth_stencil: None,
fragment: shader.at("fs_poly_sprite"),
color_targets: &[gpu::ColorTargetState {
format: surface_format,
blend: Some(gpu::BlendState::ALPHA_BLENDING),
write_mask: gpu::ColorWrites::default(),
}],
}),
} }
} }
} }
@ -376,8 +424,8 @@ impl BladeRenderer {
0, 0,
&ShaderPathsData { &ShaderPathsData {
globals, globals,
t_tile: tex_info.raw_view.unwrap(), t_sprite: tex_info.raw_view.unwrap(),
s_tile: self.atlas_sampler, s_sprite: self.atlas_sampler,
b_path_sprites: instance_buf, b_path_sprites: instance_buf,
}, },
); );
@ -396,7 +444,39 @@ impl BladeRenderer {
); );
encoder.draw(0, 4, 0, underlines.len() as u32); encoder.draw(0, 4, 0, underlines.len() as u32);
} }
_ => continue, PrimitiveBatch::MonochromeSprites { texture_id, sprites } => {
let tex_info = self.atlas.get_texture_info(texture_id);
let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
let mut encoder = pass.with(&self.pipelines.mono_sprites);
encoder.bind(
0,
&ShaderMonoSpritesData {
globals,
t_sprite: tex_info.raw_view.unwrap(),
s_sprite: self.atlas_sampler,
b_mono_sprites: instance_buf,
},
);
encoder.draw(0, 4, 0, sprites.len() as u32);
}
PrimitiveBatch::PolychromeSprites { texture_id, sprites } => {
let tex_info = self.atlas.get_texture_info(texture_id);
let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
let mut encoder = pass.with(&self.pipelines.poly_sprites);
encoder.bind(
0,
&ShaderPolySpritesData {
globals,
t_sprite: tex_info.raw_view.unwrap(),
s_sprite: self.atlas_sampler,
b_poly_sprites: instance_buf,
},
);
encoder.draw(0, 4, 0, sprites.len() as u32);
}
PrimitiveBatch::Surfaces {..} => {
unimplemented!()
}
} }
} }
} }

View file

@ -4,10 +4,11 @@ struct Globals {
} }
var<uniform> globals: Globals; var<uniform> globals: Globals;
var t_tile: texture_2d<f32>; var t_sprite: texture_2d<f32>;
var s_tile: sampler; var s_sprite: sampler;
const M_PI_F: f32 = 3.1415926; const M_PI_F: f32 = 3.1415926;
const GRAYSCALE_FACTORS: vec3<f32> = vec3<f32>(0.2126, 0.7152, 0.0722);
struct ViewId { struct ViewId {
lo: u32, lo: u32,
@ -60,7 +61,7 @@ fn to_device_position(unit_vertex: vec2<f32>, bounds: Bounds) -> vec4<f32> {
} }
fn to_tile_position(unit_vertex: vec2<f32>, tile: AtlasTile) -> vec2<f32> { fn to_tile_position(unit_vertex: vec2<f32>, tile: AtlasTile) -> vec2<f32> {
let atlas_size = vec2<f32>(textureDimensions(t_tile, 0)); let atlas_size = vec2<f32>(textureDimensions(t_sprite, 0));
return (tile.bounds.origin + unit_vertex * tile.bounds.size) / atlas_size; return (tile.bounds.origin + unit_vertex * tile.bounds.size) / atlas_size;
} }
@ -153,6 +154,17 @@ fn pick_corner_radius(point: vec2<f32>, radii: Corners) -> f32 {
} }
} }
fn quad_sdf(point: vec2<f32>, bounds: Bounds, corner_radii: Corners) -> f32 {
let half_size = bounds.size / 2.0;
let center = bounds.origin + half_size;
let center_to_point = point - center;
let corner_radius = pick_corner_radius(center_to_point, corner_radii);
let rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
return length(max(vec2<f32>(0.0), rounded_edge_to_point)) +
min(0.0, max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
corner_radius;
}
// --- quads --- // // --- quads --- //
struct Quad { struct Quad {
@ -386,7 +398,7 @@ fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta
@fragment @fragment
fn fs_path(input: PathVarying) -> @location(0) vec4<f32> { fn fs_path(input: PathVarying) -> @location(0) vec4<f32> {
let sample = textureSample(t_tile, s_tile, input.tile_position).r; let sample = textureSample(t_sprite, s_sprite, input.tile_position).r;
let mask = 1.0 - abs(1.0 - sample % 2.0); let mask = 1.0 - abs(1.0 - sample % 2.0);
return input.color * mask; return input.color * mask;
} }
@ -434,7 +446,7 @@ fn fs_underline(input: UnderlineVarying) -> @location(0) vec4<f32> {
} }
let underline = b_underlines[input.underline_id]; let underline = b_underlines[input.underline_id];
if (underline.wavy == 0u) if ((underline.wavy & 0xFFu) == 0u)
{ {
return vec4<f32>(0.0); return vec4<f32>(0.0);
} }
@ -452,3 +464,94 @@ fn fs_underline(input: UnderlineVarying) -> @location(0) vec4<f32> {
let alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border)); let alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
return input.color * vec4<f32>(1.0, 1.0, 1.0, alpha); return input.color * vec4<f32>(1.0, 1.0, 1.0, alpha);
} }
// --- monochrome sprites --- //
struct MonochromeSprite {
view_id: ViewId,
layer_id: u32,
order: u32,
bounds: Bounds,
content_mask: Bounds,
color: Hsla,
tile: AtlasTile,
}
var<storage, read> b_mono_sprites: array<MonochromeSprite>;
struct MonoSpriteVarying {
@builtin(position) position: vec4<f32>,
@location(0) tile_position: vec2<f32>,
@location(1) @interpolate(flat) color: vec4<f32>,
@location(3) clip_distances: vec4<f32>,
}
@vertex
fn vs_mono_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> MonoSpriteVarying {
let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
let sprite = b_mono_sprites[instance_id];
var out = MonoSpriteVarying();
out.position = to_device_position(unit_vertex, sprite.bounds);
out.tile_position = to_tile_position(unit_vertex, sprite.tile);
out.color = hsla_to_rgba(sprite.color);
out.clip_distances = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask);
return out;
}
@fragment
fn fs_mono_sprite(input: MonoSpriteVarying) -> @location(0) vec4<f32> {
let sample = textureSample(t_sprite, s_sprite, input.tile_position).r;
return input.color * vec4<f32>(1.0, 1.0, 1.0, sample);
}
// --- polychrome sprites --- //
struct PolychromeSprite {
view_id: ViewId,
layer_id: u32,
order: u32,
bounds: Bounds,
content_mask: Bounds,
corner_radii: Corners,
tile: AtlasTile,
grayscale: u32,
pad: u32,
}
var<storage, read> b_poly_sprites: array<PolychromeSprite>;
struct PolySpriteVarying {
@builtin(position) position: vec4<f32>,
@location(0) tile_position: vec2<f32>,
@location(1) @interpolate(flat) sprite_id: u32,
@location(3) clip_distances: vec4<f32>,
}
@vertex
fn vs_poly_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> PolySpriteVarying {
let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
let sprite = b_poly_sprites[instance_id];
var out = PolySpriteVarying();
out.position = to_device_position(unit_vertex, sprite.bounds);
out.tile_position = to_tile_position(unit_vertex, sprite.tile);
out.sprite_id = instance_id;
out.clip_distances = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask);
return out;
}
@fragment
fn fs_poly_sprite(input: PolySpriteVarying) -> @location(0) vec4<f32> {
let sample = textureSample(t_sprite, s_sprite, input.tile_position);
let sprite = b_poly_sprites[input.sprite_id];
let distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
var color = sample;
if ((sprite.grayscale & 0xFFu) != 0u) {
let grayscale = dot(color.rgb, GRAYSCALE_FACTORS);
color = vec4<f32>(vec3<f32>(grayscale), sample.a);
}
color.a *= saturate(0.5 - distance);
return color;;
}
// --- surface sprites --- //

View file

@ -642,6 +642,7 @@ pub(crate) struct PolychromeSprite {
pub corner_radii: Corners<ScaledPixels>, pub corner_radii: Corners<ScaledPixels>,
pub tile: AtlasTile, pub tile: AtlasTile,
pub grayscale: bool, pub grayscale: bool,
pub pad: u32, // align to 8 bytes
} }
impl Ord for PolychromeSprite { impl Ord for PolychromeSprite {

View file

@ -874,6 +874,7 @@ impl<'a> ElementContext<'a> {
content_mask, content_mask,
tile, tile,
grayscale: false, grayscale: false,
pad: 0,
}, },
); );
} }
@ -958,6 +959,7 @@ impl<'a> ElementContext<'a> {
corner_radii, corner_radii,
tile, tile,
grayscale, grayscale,
pad: 0,
}, },
); );
Ok(()) Ok(())