Public encoding API

This exposes significantly more of the previously internal encoding guts.

This is beneficial for its own sake as there always seem to be new and interesting things that are supported by the encoding and GPU pipeline that are not directly exposed by the scene builder.

The secondary purpose is laying the groundwork for scene *de*coding and processing. This will enable us to implement various stages of the pipeline on the CPU.
This commit is contained in:
Chad Brokaw 2023-01-06 16:52:41 -05:00
parent 6cfd358f80
commit dbe7f27768
12 changed files with 1274 additions and 582 deletions

View file

@ -138,7 +138,7 @@ fn fill_path(tile: Tile, xy: vec2<f32>) -> array<f32, PIXELS_PER_THREAD> {
}
// nonzero winding rule
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
area[i] = abs(area[i]);
area[i] = min(abs(area[i]), 1.0);
}
return area;
}

36
src/encoding.rs Normal file
View file

@ -0,0 +1,36 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Also licensed under MIT license, at your choice.
//! Raw scene encoding.
mod draw;
mod encoding;
mod math;
mod monoid;
mod packed;
mod path;
pub mod resource;
pub use draw::{
DrawBeginClip, DrawColor, DrawImage, DrawLinearGradient, DrawMonoid, DrawRadialGradient,
DrawTag,
};
pub use encoding::Encoding;
pub use math::Transform;
pub use monoid::Monoid;
pub use packed::{Config, Layout, PackedEncoding};
pub use path::{PathBbox, PathEncoder, PathMonoid, PathSegment, PathSegmentType, PathTag};

164
src/encoding/draw.rs Normal file
View file

@ -0,0 +1,164 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Also licensed under MIT license, at your choice.
use bytemuck::{Pod, Zeroable};
use peniko::{BlendMode, Color};
use super::Monoid;
/// Draw tag representation.
#[derive(Copy, Clone, PartialEq, Eq, Pod, Zeroable)]
#[repr(C)]
pub struct DrawTag(pub u32);
impl DrawTag {
/// No operation.
pub const NOP: Self = Self(0);
/// Color fill.
pub const COLOR: Self = Self(0x44);
/// Linear gradient fill.
pub const LINEAR_GRADIENT: Self = Self(0x114);
/// Radial gradient fill.
pub const RADIAL_GRADIENT: Self = Self(0x2dc);
/// Image fill.
pub const IMAGE: Self = Self(0x48);
/// Begin layer/clip.
pub const BEGIN_CLIP: Self = Self(0x9);
/// End layer/clip.
pub const END_CLIP: Self = Self(0x21);
}
impl DrawTag {
/// Returns the size of the info buffer used by this tag.
pub fn info_size(self) -> u32 {
(self.0 >> 6) & 0xf
}
}
/// Draw data for a solid color.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawColor {
/// Packed RGBA color.
pub rgba: u32,
}
impl DrawColor {
/// Creates new solid color draw data.
pub fn new(color: Color) -> Self {
Self {
rgba: color.to_premul_u32(),
}
}
}
/// Draw data for a linear gradient.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawLinearGradient {
/// Ramp index.
pub index: u32,
/// Start point.
pub p0: [f32; 2],
/// End point.
pub p1: [f32; 2],
}
/// Draw data for a radial gradient.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawRadialGradient {
/// Ramp index.
pub index: u32,
/// Start point.
pub p0: [f32; 2],
/// End point.
pub p1: [f32; 2],
/// Start radius.
pub r0: f32,
/// End radius.
pub r1: f32,
}
/// Draw data for an image.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawImage {
/// Image index.
pub index: u32,
/// Packed image offset.
pub offset: u32,
}
/// Draw data for a clip or layer.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawBeginClip {
/// Blend mode.
pub blend_mode: u32,
/// Group alpha.
pub alpha: f32,
}
impl DrawBeginClip {
/// Creates new clip draw data.
pub fn new(blend_mode: BlendMode, alpha: f32) -> Self {
Self {
blend_mode: (blend_mode.mix as u32) << 8 | blend_mode.compose as u32,
alpha,
}
}
}
/// Monoid for the draw tag stream.
#[derive(Copy, Clone, PartialEq, Eq, Pod, Zeroable, Default)]
#[repr(C)]
pub struct DrawMonoid {
// The number of paths preceding this draw object.
pub path_ix: u32,
// The number of clip operations preceding this draw object.
pub clip_ix: u32,
// The offset of the encoded draw object in the scene (u32s).
pub scene_offset: u32,
// The offset of the associated info.
pub info_offset: u32,
}
impl Monoid<DrawTag> for DrawMonoid {
fn new(tag: DrawTag) -> Self {
Self {
path_ix: (tag != DrawTag::NOP) as u32,
clip_ix: tag.0 & 1,
scene_offset: (tag.0 >> 2) & 0x7,
info_offset: (tag.0 >> 6) & 0xf,
}
}
fn combine(&self, other: &Self) -> Self {
Self {
path_ix: self.path_ix + other.path_ix,
clip_ix: self.clip_ix + other.clip_ix,
scene_offset: self.scene_offset + other.scene_offset,
info_offset: self.info_offset + other.info_offset,
}
}
}

235
src/encoding/encoding.rs Normal file
View file

@ -0,0 +1,235 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Also licensed under MIT license, at your choice.
use super::resource::Patch;
use super::{DrawTag, PathEncoder, PathTag, Transform};
use peniko::{kurbo::Shape, BlendMode, BrushRef, Color, ColorStop};
/// Encoded data streams for a scene.
#[derive(Default)]
pub struct Encoding {
/// The path tag stream.
pub path_tags: Vec<PathTag>,
/// The path data stream.
pub path_data: Vec<u8>,
/// The draw tag stream.
pub draw_tags: Vec<DrawTag>,
/// The draw data stream.
pub draw_data: Vec<u8>,
/// Draw data patches for late bound resources.
pub patches: Vec<Patch>,
/// Color stop collection for gradients.
pub color_stops: Vec<ColorStop>,
/// The transform stream.
pub transforms: Vec<Transform>,
/// The line width stream.
pub linewidths: Vec<f32>,
/// Number of encoded paths.
pub n_paths: u32,
/// Number of encoded path segments.
pub n_path_segments: u32,
/// Number of encoded clips/layers.
pub n_clips: u32,
}
impl Encoding {
/// Creates a new encoding.
pub fn new() -> Self {
Self::default()
}
/// Returns true if the encoding is empty.
pub fn is_empty(&self) -> bool {
self.path_tags.is_empty()
}
/// Clears the encoding.
pub fn reset(&mut self, is_fragment: bool) {
self.transforms.clear();
self.path_tags.clear();
self.path_data.clear();
self.linewidths.clear();
self.draw_data.clear();
self.draw_tags.clear();
self.n_paths = 0;
self.n_path_segments = 0;
self.n_clips = 0;
self.patches.clear();
self.color_stops.clear();
if !is_fragment {
self.transforms.push(Transform::IDENTITY);
self.linewidths.push(-1.0);
}
}
/// Appends another encoding to this one with an optional transform.
pub fn append(&mut self, other: &Self, transform: &Option<Transform>) {
let stops_base = self.color_stops.len();
let draw_data_base = self.draw_data.len();
self.path_tags.extend_from_slice(&other.path_tags);
self.path_data.extend_from_slice(&other.path_data);
self.draw_tags.extend_from_slice(&other.draw_tags);
self.draw_data.extend_from_slice(&other.draw_data);
self.n_paths += other.n_paths;
self.n_path_segments += other.n_path_segments;
self.n_clips += other.n_clips;
self.patches
.extend(other.patches.iter().map(|patch| match patch {
Patch::Ramp { offset, stops } => {
let stops = stops.start + stops_base..stops.end + stops_base;
Patch::Ramp {
offset: draw_data_base + offset,
stops,
}
}
}));
self.color_stops.extend_from_slice(&other.color_stops);
if let Some(transform) = *transform {
self.transforms
.extend(other.transforms.iter().map(|x| transform * *x));
} else {
self.transforms.extend_from_slice(&other.transforms);
}
self.linewidths.extend_from_slice(&other.linewidths);
}
}
impl Encoding {
/// Encodes a linewidth.
pub fn encode_linewidth(&mut self, linewidth: f32) {
if self.linewidths.last() != Some(&linewidth) {
self.path_tags.push(PathTag::LINEWIDTH);
self.linewidths.push(linewidth);
}
}
/// Encodes a transform.
pub fn encode_transform(&mut self, transform: Transform) {
if self.transforms.last() != Some(&transform) {
self.path_tags.push(PathTag::TRANSFORM);
self.transforms.push(transform);
}
}
/// Returns an encoder for encoding a path. If `is_fill` is true, all subpaths will
/// be automatically closed.
pub fn encode_path(&mut self, is_fill: bool) -> PathEncoder {
PathEncoder::new(
&mut self.path_tags,
&mut self.path_data,
&mut self.n_path_segments,
&mut self.n_paths,
is_fill,
)
}
/// Encodes a shape. If `is_fill` is true, all subpaths will be automatically closed.
/// Returns true if a non-zero number of segments were encoded.
pub fn encode_shape(&mut self, shape: &impl Shape, is_fill: bool) -> bool {
let mut encoder = self.encode_path(is_fill);
encoder.shape(shape);
encoder.finish(true) != 0
}
/// Encodes a brush with an optional alpha modifier.
pub fn encode_brush<'b>(&mut self, brush: impl Into<BrushRef<'b>>, alpha: f32) {
use super::math::point_to_f32;
use super::{DrawColor, DrawLinearGradient, DrawRadialGradient};
match brush.into() {
BrushRef::Solid(color) => {
self.draw_tags.push(DrawTag::COLOR);
let color = if alpha != 1.0 {
color_with_alpha(color, alpha)
} else {
color
};
self.draw_data
.extend_from_slice(bytemuck::bytes_of(&DrawColor::new(color)));
}
BrushRef::LinearGradient(gradient) => {
self.add_ramp(&gradient.stops, alpha);
self.draw_tags.push(DrawTag::LINEAR_GRADIENT);
self.draw_data
.extend_from_slice(bytemuck::bytes_of(&DrawLinearGradient {
index: 0,
p0: point_to_f32(gradient.start),
p1: point_to_f32(gradient.end),
}));
}
BrushRef::RadialGradient(gradient) => {
self.add_ramp(&gradient.stops, alpha);
self.draw_tags.push(DrawTag::RADIAL_GRADIENT);
self.draw_data
.extend_from_slice(bytemuck::bytes_of(&DrawRadialGradient {
index: 0,
p0: point_to_f32(gradient.start_center),
p1: point_to_f32(gradient.end_center),
r0: gradient.start_radius,
r1: gradient.end_radius,
}));
}
BrushRef::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"),
}
}
/// Encodes a begin clip command.
pub fn encode_begin_clip(&mut self, blend_mode: BlendMode, alpha: f32) {
use super::DrawBeginClip;
self.draw_tags.push(DrawTag::BEGIN_CLIP);
self.draw_data
.extend_from_slice(bytemuck::bytes_of(&DrawBeginClip::new(blend_mode, alpha)));
self.n_clips += 1;
}
/// Encodes an end clip command.
pub fn encode_end_clip(&mut self) {
self.draw_tags.push(DrawTag::END_CLIP);
// This is a dummy path, and will go away with the new clip impl.
self.path_tags.push(PathTag::PATH);
self.n_paths += 1;
self.n_clips += 1;
}
// Swap the last two tags in the path tag stream; used for transformed
// gradients.
pub fn swap_last_path_tags(&mut self) {
let len = self.path_tags.len();
self.path_tags.swap(len - 1, len - 2);
}
fn add_ramp(&mut self, stops: &[ColorStop], alpha: f32) {
let offset = self.draw_data.len();
let stops_start = self.color_stops.len();
if alpha != 1.0 {
self.color_stops.extend(stops.iter().map(|s| ColorStop {
offset: s.offset,
color: color_with_alpha(s.color, alpha),
}));
} else {
self.color_stops.extend_from_slice(stops);
}
self.patches.push(Patch::Ramp {
offset,
stops: stops_start..self.color_stops.len(),
});
}
}
fn color_with_alpha(mut color: Color, alpha: f32) -> Color {
color.a = ((color.a as f32) * alpha) as u8;
color
}

90
src/encoding/math.rs Normal file
View file

@ -0,0 +1,90 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Also licensed under MIT license, at your choice.
use std::ops::Mul;
use bytemuck::{Pod, Zeroable};
use peniko::kurbo;
/// Affine transformation matrix.
#[derive(Copy, Clone, PartialEq, Pod, Zeroable)]
#[repr(C)]
pub struct Transform {
/// 2x2 matrix.
pub matrix: [f32; 4],
/// Translation.
pub translation: [f32; 2],
}
impl Transform {
/// Identity transform.
pub const IDENTITY: Self = Self {
matrix: [1.0, 0.0, 0.0, 1.0],
translation: [0.0; 2],
};
/// Creates a transform from a kurbo affine matrix.
pub fn from_kurbo(transform: &kurbo::Affine) -> Self {
let c = transform.as_coeffs().map(|x| x as f32);
Self {
matrix: [c[0], c[1], c[2], c[3]],
translation: [c[4], c[5]],
}
}
/// Converts the transform to a kurbo affine matrix.
pub fn to_kurbo(&self) -> kurbo::Affine {
kurbo::Affine::new(
[
self.matrix[0],
self.matrix[1],
self.matrix[2],
self.matrix[3],
self.translation[0],
self.translation[1],
]
.map(|x| x as f64),
)
}
}
impl Mul for Transform {
type Output = Self;
#[inline]
fn mul(self, other: Self) -> Self {
Self {
matrix: [
self.matrix[0] * other.matrix[0] + self.matrix[2] * other.matrix[1],
self.matrix[1] * other.matrix[0] + self.matrix[3] * other.matrix[1],
self.matrix[0] * other.matrix[2] + self.matrix[2] * other.matrix[3],
self.matrix[1] * other.matrix[2] + self.matrix[3] * other.matrix[3],
],
translation: [
self.matrix[0] * other.translation[0]
+ self.matrix[2] * other.translation[1]
+ self.translation[0],
self.matrix[1] * other.translation[0]
+ self.matrix[3] * other.translation[1]
+ self.translation[1],
],
}
}
}
pub fn point_to_f32(point: kurbo::Point) -> [f32; 2] {
[point.x as f32, point.y as f32]
}

23
src/encoding/monoid.rs Normal file
View file

@ -0,0 +1,23 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Also licensed under MIT license, at your choice.
/// Interface for a monoid.
pub trait Monoid<T>: Default {
/// Creates a monoid from a given value.
fn new(value: T) -> Self;
/// Combines two monoids. This operation must be associative.
fn combine(&self, other: &Self) -> Self;
}

212
src/encoding/packed.rs Normal file
View file

@ -0,0 +1,212 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Also licensed under MIT license, at your choice.
use bytemuck::{Pod, Zeroable};
use super::{
resource::{Patch, ResourceCache, Token},
DrawTag, Encoding, PathTag, Transform,
};
use crate::shaders;
/// Layout of a packed encoding.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct Layout {
/// Number of draw objects.
pub n_draw_objects: u32,
/// Number of paths.
pub n_paths: u32,
/// Number of clips.
pub n_clips: u32,
/// Start of binning data.
pub bin_data_start: u32,
/// Start of path tag stream.
pub path_tag_base: u32,
/// Start of path data stream.
pub path_data_base: u32,
/// Start of draw tag stream.
pub draw_tag_base: u32,
/// Start of draw data stream.
pub draw_data_base: u32,
/// Start of transform stream.
pub transform_base: u32,
/// Start of linewidth stream.
pub linewidth_base: u32,
}
/// Scene configuration.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct Config {
/// Width of the scene in tiles.
pub width_in_tiles: u32,
/// Height of the scene in tiles.
pub height_in_tiles: u32,
/// Width of the target in pixels.
pub target_width: u32,
/// Height of the target in pixels.
pub target_height: u32,
/// Layout of packed scene data.
pub layout: Layout,
}
/// Packed encoding of scene data.
#[derive(Default)]
pub struct PackedEncoding {
/// Layout of the packed scene data.
pub layout: Layout,
/// Packed scene data.
pub data: Vec<u8>,
/// Token for current cached resource state.
pub resources: Token,
}
impl PackedEncoding {
/// Creates a new packed encoding.
pub fn new() -> Self {
Self::default()
}
/// Returns the path tag stream.
pub fn path_tags(&self) -> &[PathTag] {
let start = self.layout.path_tag_base as usize * 4;
let end = self.layout.path_data_base as usize * 4;
bytemuck::cast_slice(&self.data[start..end])
}
/// Returns the path tag stream in chunks of 4.
pub fn path_tags_chunked(&self) -> &[u32] {
let start = self.layout.path_tag_base as usize * 4;
let end = self.layout.path_data_base as usize * 4;
bytemuck::cast_slice(&self.data[start..end])
}
/// Returns the path data stream.
pub fn path_data(&self) -> &[[f32; 2]] {
let start = self.layout.path_data_base as usize * 4;
let end = self.layout.draw_tag_base as usize * 4;
bytemuck::cast_slice(&self.data[start..end])
}
/// Returns the draw tag stream.
pub fn draw_tags(&self) -> &[DrawTag] {
let start = self.layout.draw_tag_base as usize * 4;
let end = self.layout.draw_data_base as usize * 4;
bytemuck::cast_slice(&self.data[start..end])
}
/// Returns the draw data stream.
pub fn draw_data(&self) -> &[u32] {
let start = self.layout.draw_data_base as usize * 4;
let end = self.layout.transform_base as usize * 4;
bytemuck::cast_slice(&self.data[start..end])
}
/// Returns the transform stream.
pub fn transforms(&self) -> &[Transform] {
let start = self.layout.transform_base as usize * 4;
let end = self.layout.linewidth_base as usize * 4;
bytemuck::cast_slice(&self.data[start..end])
}
/// Returns the linewidth stream.
pub fn linewidths(&self) -> &[f32] {
let start = self.layout.linewidth_base as usize * 4;
bytemuck::cast_slice(&self.data[start..])
}
}
impl PackedEncoding {
/// Packs the given encoding into self using the specified cache to handle
/// late bound resources.
pub fn pack(&mut self, encoding: &Encoding, resource_cache: &mut ResourceCache) {
// Advance the resource cache epoch.
self.resources = resource_cache.advance();
// Pack encoded data.
let layout = &mut self.layout;
*layout = Layout::default();
layout.n_paths = encoding.n_paths;
layout.n_draw_objects = encoding.n_paths;
layout.n_clips = encoding.n_clips;
let data = &mut self.data;
data.clear();
// Path tag stream
let n_path_tags = encoding.path_tags.len();
let path_tag_padded = align_up(n_path_tags, 4 * shaders::PATHTAG_REDUCE_WG);
let capacity = path_tag_padded
+ slice_size_in_bytes(&encoding.path_data)
+ slice_size_in_bytes(&encoding.draw_tags)
+ slice_size_in_bytes(&encoding.draw_data)
+ slice_size_in_bytes(&encoding.transforms)
+ slice_size_in_bytes(&encoding.linewidths);
data.reserve(capacity);
layout.path_tag_base = size_to_words(data.len());
data.extend_from_slice(bytemuck::cast_slice(&encoding.path_tags));
data.resize(path_tag_padded, 0);
// Path data stream
layout.path_data_base = size_to_words(data.len());
data.extend_from_slice(&encoding.path_data);
// Draw tag stream
layout.draw_tag_base = size_to_words(data.len());
data.extend_from_slice(bytemuck::cast_slice(&encoding.draw_tags));
// Bin data follows draw info
layout.bin_data_start = encoding.draw_tags.iter().map(|tag| tag.info_size()).sum();
// Draw data stream
layout.draw_data_base = size_to_words(data.len());
// Handle patches, if any
if !encoding.patches.is_empty() {
let stop_data = &encoding.color_stops;
let mut pos = 0;
for patch in &encoding.patches {
let (offset, value) = match patch {
Patch::Ramp { offset, stops } => {
let ramp_id = resource_cache.add_ramp(&stop_data[stops.clone()]);
(*offset, ramp_id)
}
};
if pos < offset {
data.extend_from_slice(&encoding.draw_data[pos..offset]);
}
data.extend_from_slice(bytemuck::bytes_of(&value));
pos = offset + 4;
}
if pos < encoding.draw_data.len() {
data.extend_from_slice(&encoding.draw_data[pos..])
}
} else {
data.extend_from_slice(&encoding.draw_data);
}
// Transform stream
layout.transform_base = size_to_words(data.len());
data.extend_from_slice(bytemuck::cast_slice(&encoding.transforms));
// Linewidth stream
layout.linewidth_base = size_to_words(data.len());
data.extend_from_slice(bytemuck::cast_slice(&encoding.linewidths));
}
}
fn slice_size_in_bytes<T: Sized>(slice: &[T]) -> usize {
slice.len() * std::mem::size_of::<T>()
}
fn size_to_words(byte_size: usize) -> u32 {
(byte_size / std::mem::size_of::<u32>()) as u32
}
fn align_up(len: usize, alignment: u32) -> usize {
len + (len.wrapping_neg() & alignment as usize - 1)
}

368
src/encoding/path.rs Normal file
View file

@ -0,0 +1,368 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Also licensed under MIT license, at your choice.
use bytemuck::{Pod, Zeroable};
use peniko::kurbo::Shape;
use super::Monoid;
/// Path segment.
#[derive(Clone, Copy, Debug, Zeroable, Pod)]
#[repr(C)]
pub struct PathSegment {
pub origin: [f32; 2],
pub delta: [f32; 2],
pub y_edge: f32,
pub next: u32,
}
/// Path segment type.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Pod, Zeroable)]
#[repr(C)]
pub struct PathSegmentType(pub u8);
impl PathSegmentType {
/// Line segment.
pub const LINE_TO: Self = Self(0x1);
/// Quadratic segment.
pub const QUAD_TO: Self = Self(0x2);
/// Cubic segment.
pub const CUBIC_TO: Self = Self(0x3);
}
/// Path tag representation.
#[derive(Copy, Clone, PartialEq, Eq, Pod, Zeroable)]
#[repr(C)]
pub struct PathTag(pub u8);
impl PathTag {
/// 32-bit floating point line segment.
pub const LINE_TO_F32: Self = Self(0x9);
/// 32-bit floating point quadratic segment.
pub const QUAD_TO_F32: Self = Self(0xa);
/// 32-bit floating point cubic segment.
pub const CUBIC_TO_F32: Self = Self(0xb);
/// 16-bit integral line segment.
pub const LINE_TO_I16: Self = Self(0x1);
/// 16-bit integral quadratic segment.
pub const QUAD_TO_I16: Self = Self(0x2);
/// 16-bit integral cubic segment.
pub const CUBIC_TO_I16: Self = Self(0x3);
/// Transform marker.
pub const TRANSFORM: Self = Self(0x20);
/// Path marker.
pub const PATH: Self = Self(0x10);
/// Line width setting.
pub const LINEWIDTH: Self = Self(0x40);
const F32_BIT: u8 = 0x8;
const SUBPATH_END_BIT: u8 = 0x4;
const SEGMENT_MASK: u8 = 0x3;
/// Returns true if the tag is a segment.
pub fn is_path_segment(self) -> bool {
self.path_segment_type().0 != 0
}
/// Returns true if this is a 32-bit floating point segment.
pub fn is_f32(self) -> bool {
self.0 & Self::F32_BIT != 0
}
/// Returns true if this segment ends a subpath.
pub fn is_subpath_end(self) -> bool {
(self.0 & Self::SUBPATH_END_BIT) != 0
}
/// Sets the subpath end bit.
pub fn set_subpath_end(&mut self) {
self.0 |= Self::SUBPATH_END_BIT;
}
/// Returns the segment type.
pub fn path_segment_type(self) -> PathSegmentType {
PathSegmentType(self.0 & Self::SEGMENT_MASK)
}
}
/// Monoid for the path tag stream.
#[derive(Copy, Clone, Pod, Zeroable, Default, Debug)]
#[repr(C)]
pub struct PathMonoid {
/// Index into transform stream.
pub trans_ix: u32,
/// Path segment index.
pub pathseg_ix: u32,
/// Offset into path segment stream.
pub pathseg_offset: u32,
/// Index into linewidth stream.
pub linewidth_ix: u32,
/// Index of containing path.
pub path_ix: u32,
}
impl Monoid<u32> for PathMonoid {
/// Reduces a packed 32-bit word containing 4 tags.
fn new(tag_word: u32) -> Self {
let mut c = Self::default();
let point_count = tag_word & 0x3030303;
c.pathseg_ix = ((point_count * 7) & 0x4040404).count_ones();
c.trans_ix = (tag_word & (PathTag::TRANSFORM.0 as u32 * 0x1010101)).count_ones();
let n_points = point_count + ((tag_word >> 2) & 0x1010101);
let mut a = n_points + (n_points & (((tag_word >> 3) & 0x1010101) * 15));
a += a >> 8;
a += a >> 16;
c.pathseg_offset = a & 0xff;
c.path_ix = (tag_word & (PathTag::PATH.0 as u32 * 0x1010101)).count_ones();
c.linewidth_ix = (tag_word & (PathTag::LINEWIDTH.0 as u32 * 0x1010101)).count_ones();
return c;
}
/// Monoid combination.
fn combine(&self, other: &Self) -> Self {
Self {
trans_ix: self.trans_ix + other.trans_ix,
pathseg_ix: self.pathseg_ix + other.pathseg_ix,
pathseg_offset: self.pathseg_offset + other.pathseg_offset,
linewidth_ix: self.linewidth_ix + other.linewidth_ix,
path_ix: self.path_ix + other.path_ix,
}
}
}
/// Path bounding box.
#[derive(Copy, Clone, Pod, Zeroable, Default, Debug)]
#[repr(C)]
pub struct PathBbox {
/// Minimum x value.
pub x0: i32,
/// Minimum y value.
pub y0: i32,
/// Maximum x value.
pub x1: i32,
/// Maximum y value.
pub y1: i32,
/// Line width.
pub linewidth: f32,
/// Index into the transform stream.
pub trans_ix: u32,
}
/// Encoder for path segments.
pub struct PathEncoder<'a> {
tags: &'a mut Vec<PathTag>,
data: &'a mut Vec<u8>,
n_segments: &'a mut u32,
n_paths: &'a mut u32,
first_point: [f32; 2],
state: PathState,
n_encoded_segments: u32,
is_fill: bool,
}
#[derive(PartialEq)]
enum PathState {
Start,
MoveTo,
NonemptySubpath,
}
impl<'a> PathEncoder<'a> {
/// Creates a new path encoder for the specified path tags and data. If `is_fill` is true,
/// ensures that all subpaths are closed.
pub fn new(
tags: &'a mut Vec<PathTag>,
data: &'a mut Vec<u8>,
n_segments: &'a mut u32,
n_paths: &'a mut u32,
is_fill: bool,
) -> Self {
Self {
tags,
data,
n_segments,
n_paths,
first_point: [0.0, 0.0],
state: PathState::Start,
n_encoded_segments: 0,
is_fill,
}
}
/// Encodes a move, starting a new subpath.
pub fn move_to(&mut self, x: f32, y: f32) {
if self.is_fill {
self.close();
}
let buf = [x, y];
let bytes = bytemuck::bytes_of(&buf);
self.first_point = buf;
if self.state == PathState::MoveTo {
let new_len = self.data.len() - 8;
self.data.truncate(new_len);
} else if self.state == PathState::NonemptySubpath {
if let Some(tag) = self.tags.last_mut() {
tag.set_subpath_end();
}
}
self.data.extend_from_slice(bytes);
self.state = PathState::MoveTo;
}
/// Encodes a line.
pub fn line_to(&mut self, x: f32, y: f32) {
if self.state == PathState::Start {
if self.n_encoded_segments == 0 {
// This copies the behavior of kurbo which treats an initial line, quad
// or curve as a move.
self.move_to(x, y);
return;
}
self.move_to(self.first_point[0], self.first_point[1]);
}
let buf = [x, y];
let bytes = bytemuck::bytes_of(&buf);
self.data.extend_from_slice(bytes);
self.tags.push(PathTag::LINE_TO_F32);
self.state = PathState::NonemptySubpath;
self.n_encoded_segments += 1;
}
/// Encodes a quadratic bezier.
pub fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
if self.state == PathState::Start {
if self.n_encoded_segments == 0 {
self.move_to(x2, y2);
return;
}
self.move_to(self.first_point[0], self.first_point[1]);
}
let buf = [x1, y1, x2, y2];
let bytes = bytemuck::bytes_of(&buf);
self.data.extend_from_slice(bytes);
self.tags.push(PathTag::QUAD_TO_F32);
self.state = PathState::NonemptySubpath;
self.n_encoded_segments += 1;
}
/// Encodes a cubic bezier.
pub fn cubic_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {
if self.state == PathState::Start {
if self.n_encoded_segments == 0 {
self.move_to(x3, y3);
return;
}
self.move_to(self.first_point[0], self.first_point[1]);
}
let buf = [x1, y1, x2, y2, x3, y3];
let bytes = bytemuck::bytes_of(&buf);
self.data.extend_from_slice(bytes);
self.tags.push(PathTag::CUBIC_TO_F32);
self.state = PathState::NonemptySubpath;
self.n_encoded_segments += 1;
}
/// Closes the current subpath.
pub fn close(&mut self) {
match self.state {
PathState::Start => return,
PathState::MoveTo => {
let new_len = self.data.len() - 8;
self.data.truncate(new_len);
self.state = PathState::Start;
return;
}
PathState::NonemptySubpath => (),
}
let len = self.data.len();
if len < 8 {
// can't happen
return;
}
let first_bytes = bytemuck::bytes_of(&self.first_point);
if &self.data[len - 8..len] != first_bytes {
self.data.extend_from_slice(first_bytes);
let mut tag = PathTag::LINE_TO_F32;
tag.set_subpath_end();
self.tags.push(tag);
self.n_encoded_segments += 1;
} else {
if let Some(tag) = self.tags.last_mut() {
tag.set_subpath_end();
}
}
self.state = PathState::Start;
}
/// Encodes a shape.
pub fn shape(&mut self, shape: &impl Shape) {
use peniko::kurbo::PathEl;
for el in shape.path_elements(0.1) {
match el {
PathEl::MoveTo(p0) => self.move_to(p0.x as f32, p0.y as f32),
PathEl::LineTo(p0) => self.line_to(p0.x as f32, p0.y as f32),
PathEl::QuadTo(p0, p1) => {
self.quad_to(p0.x as f32, p0.y as f32, p1.x as f32, p1.y as f32)
}
PathEl::CurveTo(p0, p1, p2) => self.cubic_to(
p0.x as f32,
p0.y as f32,
p1.x as f32,
p1.y as f32,
p2.x as f32,
p2.y as f32,
),
PathEl::ClosePath => self.close(),
}
}
}
/// Completes path encoding and returns the actual number of encoded segments.
///
/// If `insert_path_marker` is true, encodes the [PathTag::PATH] tag to signify
/// the end of a complete path object. Setting this to false allows encoding
/// multiple paths with differing transforms for a single draw object.
pub fn finish(mut self, insert_path_marker: bool) -> u32 {
if self.is_fill {
self.close();
}
if self.state == PathState::MoveTo {
let new_len = self.data.len() - 8;
self.data.truncate(new_len);
}
if self.n_encoded_segments != 0 {
if let Some(tag) = self.tags.last_mut() {
tag.set_subpath_end();
}
*self.n_segments += self.n_encoded_segments;
if insert_path_marker {
self.tags.push(PathTag::PATH);
*self.n_paths += 1;
}
}
self.n_encoded_segments
}
}

View file

@ -1,12 +1,80 @@
use peniko::{Color, ColorStop, ColorStops};
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Also licensed under MIT license, at your choice.
//! Late bound resource management.
use std::collections::HashMap;
use std::ops::Range;
use peniko::{Color, ColorStop, ColorStops};
const N_SAMPLES: usize = 512;
const RETAINED_COUNT: usize = 64;
/// Token for ensuring that an encoded scene matches the current state
/// of a resource cache.
#[derive(Copy, Clone, PartialEq, Eq, Default)]
pub struct Token(u64);
/// Cache for late bound resources.
#[derive(Default)]
pub struct RampCache {
pub struct ResourceCache {
ramps: RampCache,
}
impl ResourceCache {
/// Creates a new resource cache.
pub fn new() -> Self {
Self::default()
}
/// Returns the ramp data, width and height. Returns `None` if the
/// given token does not match the current state of the cache.
pub fn ramps(&self, token: Token) -> Option<(&[u32], u32, u32)> {
if token.0 == self.ramps.epoch {
Some((self.ramps.data(), self.ramps.width(), self.ramps.height()))
} else {
None
}
}
pub(crate) fn advance(&mut self) -> Token {
self.ramps.advance();
Token(self.ramps.epoch)
}
pub(crate) fn add_ramp(&mut self, stops: &[ColorStop]) -> u32 {
self.ramps.add(stops)
}
}
#[derive(Clone)]
/// Patch for a late bound resource.
pub enum Patch {
/// Gradient ramp resource.
Ramp {
/// Byte offset to the ramp id in the draw data stream.
offset: usize,
/// Range of the gradient stops in the resource set.
stops: Range<usize>,
},
}
#[derive(Default)]
struct RampCache {
epoch: u64,
map: HashMap<ColorStops, (u32, u64)>,
data: Vec<u32>,

View file

@ -15,7 +15,6 @@
// Also licensed under MIT license, at your choice.
mod engine;
mod ramp;
mod render;
mod scene;
mod shaders;
@ -25,10 +24,12 @@ pub use peniko;
/// 2D geometry, with a focus on curves.
pub use peniko::kurbo;
pub mod encoding;
pub mod glyph;
pub mod util;
pub use scene::{ResourceBundle, ResourcePatch, Scene, SceneBuilder, SceneData, SceneFragment};
pub use scene::{Scene, SceneBuilder, SceneFragment};
use engine::{Engine, ExternalResource};
use shaders::FullShaders;

View file

@ -5,7 +5,7 @@ use bytemuck::{Pod, Zeroable};
use crate::{
engine::{BufProxy, ImageFormat, ImageProxy, Recording, ResourceProxy},
shaders::{self, FullShaders, Shaders},
ResourcePatch, Scene,
Scene,
};
const TAG_MONOID_SIZE: u64 = 12;
@ -42,15 +42,6 @@ struct Config {
linewidth_base: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Zeroable, Pod)]
pub struct PathSegment {
origin: [f32; 2],
delta: [f32; 2],
y_edge: f32,
next: u32,
}
fn size_to_words(byte_size: usize) -> u32 {
(byte_size / std::mem::size_of::<u32>()) as u32
}
@ -66,15 +57,15 @@ pub const fn next_multiple_of(val: u32, rhs: u32) -> u32 {
fn render(scene: &Scene, shaders: &Shaders) -> (Recording, BufProxy) {
let mut recording = Recording::default();
let data = scene.data();
let n_pathtag = data.tag_stream.len();
let n_pathtag = data.path_tags.len();
let pathtag_padded = align_up(n_pathtag, 4 * shaders::PATHTAG_REDUCE_WG);
let pathtag_wgs = pathtag_padded / (4 * shaders::PATHTAG_REDUCE_WG as usize);
let mut scene: Vec<u8> = Vec::with_capacity(pathtag_padded);
let pathtag_base = size_to_words(scene.len());
scene.extend(&data.tag_stream);
scene.extend(bytemuck::cast_slice(&data.path_tags));
scene.resize(pathtag_padded, 0);
let pathdata_base = size_to_words(scene.len());
scene.extend(&data.pathseg_stream);
scene.extend(&data.path_data);
let config = Config {
width_in_tiles: 64,
@ -144,95 +135,43 @@ pub fn render_full(
width: u32,
height: u32,
) -> (Recording, ResourceProxy) {
use crate::encoding::{resource::ResourceCache, PackedEncoding};
let mut recording = Recording::default();
let mut ramps = crate::ramp::RampCache::default();
let mut drawdata_patches: Vec<(usize, u32)> = vec![];
let mut resources = ResourceCache::new();
let mut packed_scene = PackedEncoding::default();
let data = scene.data();
let stop_data = &data.resources.stops;
for patch in &data.resources.patches {
match patch {
ResourcePatch::Ramp { offset, stops } => {
let ramp_id = ramps.add(&stop_data[stops.clone()]);
drawdata_patches.push((*offset, ramp_id));
}
}
}
let gradient_image = if drawdata_patches.is_empty() {
packed_scene.pack(&data, &mut resources);
let (ramp_data, ramps_width, ramps_height) = resources.ramps(packed_scene.resources).unwrap();
let gradient_image = if data.patches.is_empty() {
ResourceProxy::new_image(1, 1, ImageFormat::Rgba8)
} else {
let data = ramps.data();
let width = ramps.width();
let height = ramps.height();
let data: &[u8] = bytemuck::cast_slice(data);
// println!(
// "gradient image: {}x{} ({} bytes)",
// width,
// height,
// data.len()
// );
ResourceProxy::Image(recording.upload_image(width, height, ImageFormat::Rgba8, data))
let data: &[u8] = bytemuck::cast_slice(ramp_data);
ResourceProxy::Image(recording.upload_image(
ramps_width,
ramps_height,
ImageFormat::Rgba8,
data,
))
};
let n_pathtag = data.tag_stream.len();
let pathtag_padded = align_up(n_pathtag, 4 * shaders::PATHTAG_REDUCE_WG);
// TODO: can compute size accurately, avoid reallocation
let mut scene: Vec<u8> = Vec::with_capacity(pathtag_padded);
let pathtag_base = size_to_words(scene.len());
scene.extend(&data.tag_stream);
scene.resize(pathtag_padded, 0);
let pathdata_base = size_to_words(scene.len());
scene.extend(&data.pathseg_stream);
let drawtag_base = size_to_words(scene.len());
scene.extend(bytemuck::cast_slice(&data.drawtag_stream));
let drawdata_base = size_to_words(scene.len());
if !drawdata_patches.is_empty() {
let mut pos = 0;
for patch in drawdata_patches {
let offset = patch.0;
let value = patch.1;
if pos < offset {
scene.extend_from_slice(&data.drawdata_stream[pos..offset]);
}
scene.extend_from_slice(bytemuck::bytes_of(&value));
pos = offset + 4;
}
if pos < data.drawdata_stream.len() {
scene.extend_from_slice(&data.drawdata_stream[pos..])
}
} else {
scene.extend(&data.drawdata_stream);
}
let transform_base = size_to_words(scene.len());
scene.extend(bytemuck::cast_slice(&data.transform_stream));
let linewidth_base = size_to_words(scene.len());
scene.extend(bytemuck::cast_slice(&data.linewidth_stream));
let n_path = data.n_path;
// TODO: calculate for real when we do rectangles
let n_drawobj = n_path;
let n_clip = data.n_clip;
let bin_data_start = n_drawobj * MAX_DRAWINFO_SIZE as u32;
let n_pathtag = data.path_tags.len();
let pathtag_padded = align_up(data.path_tags.len(), 4 * shaders::PATHTAG_REDUCE_WG);
let n_paths = data.n_paths;
let n_drawobj = n_paths;
let n_clip = data.n_clips;
let new_width = next_multiple_of(width, 16);
let new_height = next_multiple_of(height, 16);
let config = Config {
// TODO: Replace with div_ceil once stable
let config = crate::encoding::Config {
width_in_tiles: new_width / 16,
height_in_tiles: new_height / 16,
target_width: width,
target_height: height,
n_drawobj,
n_path,
n_clip,
bin_data_start,
pathtag_base,
pathdata_base,
drawtag_base,
drawdata_base,
transform_base,
linewidth_base,
layout: packed_scene.layout,
};
// println!("{:?}", config);
let scene_buf = ResourceProxy::Buf(recording.upload(scene));
let scene_buf = ResourceProxy::Buf(recording.upload(packed_scene.data));
let config_buf = ResourceProxy::Buf(recording.upload_uniform(bytemuck::bytes_of(&config)));
let pathtag_wgs = pathtag_padded / (4 * shaders::PATHTAG_REDUCE_WG as usize);
@ -253,7 +192,7 @@ pub fn render_full(
[config_buf, scene_buf, reduced_buf, tagmonoid_buf],
);
let drawobj_wgs = (n_drawobj + shaders::PATH_BBOX_WG - 1) / shaders::PATH_BBOX_WG;
let path_bbox_buf = ResourceProxy::new_buf(n_path as u64 * PATH_BBOX_SIZE);
let path_bbox_buf = ResourceProxy::new_buf(n_paths as u64 * PATH_BBOX_SIZE);
recording.dispatch(
shaders.bbox_clear,
(drawobj_wgs, 1, 1),
@ -281,7 +220,7 @@ pub fn render_full(
);
let draw_monoid_buf = ResourceProxy::new_buf(n_drawobj as u64 * DRAWMONOID_SIZE);
let info_bin_data_buf = ResourceProxy::new_buf(1 << 20);
let clip_inp_buf = ResourceProxy::new_buf(data.n_clip as u64 * CLIP_INP_SIZE);
let clip_inp_buf = ResourceProxy::new_buf(data.n_clips as u64 * CLIP_INP_SIZE);
recording.dispatch(
shaders.draw_leaf,
(drawobj_wgs, 1, 1),
@ -295,7 +234,7 @@ pub fn render_full(
clip_inp_buf,
],
);
let clip_el_buf = ResourceProxy::new_buf(data.n_clip as u64 * CLIP_EL_SIZE);
let clip_el_buf = ResourceProxy::new_buf(data.n_clips as u64 * CLIP_EL_SIZE);
let clip_bic_buf =
ResourceProxy::new_buf((n_clip / shaders::CLIP_REDUCE_WG) as u64 * CLIP_BIC_SIZE);
let clip_wg_reduce = n_clip.saturating_sub(1) / shaders::CLIP_REDUCE_WG;
@ -329,7 +268,7 @@ pub fn render_full(
],
);
}
let draw_bbox_buf = ResourceProxy::new_buf(n_path as u64 * DRAW_BBOX_SIZE);
let draw_bbox_buf = ResourceProxy::new_buf(n_paths as u64 * DRAW_BBOX_SIZE);
let bump_buf = BufProxy::new(BUMP_SIZE);
let width_in_bins = (config.width_in_tiles + 15) / 16;
let height_in_bins = (config.height_in_tiles + 15) / 16;
@ -352,10 +291,10 @@ pub fn render_full(
);
// Note: this only needs to be rounded up because of the workaround to store the tile_offset
// in storage rather than workgroup memory.
let n_path_aligned = align_up(n_path as usize, 256);
let n_path_aligned = align_up(n_paths as usize, 256);
let path_buf = ResourceProxy::new_buf(n_path_aligned as u64 * PATH_SIZE);
let tile_buf = ResourceProxy::new_buf(1 << 20);
let path_wgs = (n_path + shaders::PATH_BBOX_WG - 1) / shaders::PATH_BBOX_WG;
let path_wgs = (n_paths + shaders::PATH_BBOX_WG - 1) / shaders::PATH_BBOX_WG;
recording.dispatch(
shaders.tile_alloc,
(path_wgs, 1, 1),

View file

@ -14,94 +14,15 @@
//
// Also licensed under MIT license, at your choice.
use peniko::kurbo::{Affine, PathEl, Point, Rect, Shape};
use peniko::{BlendMode, BrushRef, ColorStop, Fill, Stroke};
use peniko::kurbo::{Affine, Rect, Shape};
use peniko::{BlendMode, BrushRef, Fill, Stroke};
use bytemuck::{Pod, Zeroable};
use std::ops::Range;
/// Raw data streams describing an encoded scene.
#[derive(Default)]
pub struct SceneData {
pub transform_stream: Vec<[f32; 6]>,
pub tag_stream: Vec<u8>,
pub pathseg_stream: Vec<u8>,
pub linewidth_stream: Vec<f32>,
pub drawtag_stream: Vec<u32>,
pub drawdata_stream: Vec<u8>,
pub n_path: u32,
pub n_pathseg: u32,
pub n_clip: u32,
pub resources: ResourceBundle,
}
impl SceneData {
fn is_empty(&self) -> bool {
self.pathseg_stream.is_empty()
}
fn reset(&mut self, is_fragment: bool) {
self.transform_stream.clear();
self.tag_stream.clear();
self.pathseg_stream.clear();
self.linewidth_stream.clear();
self.drawtag_stream.clear();
self.drawdata_stream.clear();
self.n_path = 0;
self.n_pathseg = 0;
self.n_clip = 0;
self.resources.clear();
if !is_fragment {
self.transform_stream.push([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
self.linewidth_stream.push(-1.0);
}
}
fn append(&mut self, other: &SceneData, transform: &Option<Affine>) {
let stops_base = self.resources.stops.len();
let drawdata_base = self.drawdata_stream.len();
if let Some(transform) = *transform {
self.transform_stream.extend(
other
.transform_stream
.iter()
.map(|x| affine_to_f32(&(transform * affine_from_f32(x)))),
);
} else {
self.transform_stream
.extend_from_slice(&other.transform_stream);
}
self.tag_stream.extend_from_slice(&other.tag_stream);
self.pathseg_stream.extend_from_slice(&other.pathseg_stream);
self.linewidth_stream
.extend_from_slice(&other.linewidth_stream);
self.drawtag_stream.extend_from_slice(&other.drawtag_stream);
self.drawdata_stream
.extend_from_slice(&other.drawdata_stream);
self.n_path += other.n_path;
self.n_pathseg += other.n_pathseg;
self.n_clip += other.n_clip;
self.resources
.stops
.extend_from_slice(&other.resources.stops);
self.resources
.patches
.extend(other.resources.patches.iter().map(|patch| match patch {
ResourcePatch::Ramp { offset, stops } => {
let stops = stops.start + stops_base..stops.end + stops_base;
ResourcePatch::Ramp {
offset: drawdata_base + offset,
stops,
}
}
}));
}
}
use crate::encoding::{Encoding, Transform};
/// Encoded definition of a scene and associated resources.
#[derive(Default)]
pub struct Scene {
data: SceneData,
data: Encoding,
}
impl Scene {
@ -111,7 +32,7 @@ impl Scene {
}
/// Returns the raw encoded scene data streams.
pub fn data(&self) -> &SceneData {
pub fn data(&self) -> &Encoding {
&self.data
}
}
@ -119,7 +40,7 @@ impl Scene {
/// Encoded definition of a scene fragment and associated resources.
#[derive(Default)]
pub struct SceneFragment {
data: SceneData,
data: Encoding,
}
impl SceneFragment {
@ -138,43 +59,14 @@ impl SceneFragment {
if self.is_empty() {
&[]
} else {
bytemuck::cast_slice(&self.data.pathseg_stream)
bytemuck::cast_slice(&self.data.path_data)
}
}
}
#[derive(Default)]
/// Collection of late bound resources for a scene or scene fragment.
pub struct ResourceBundle {
/// Sequence of resource patches.
pub patches: Vec<ResourcePatch>,
/// Cache of color stops, referenced by range from the patches.
pub stops: Vec<ColorStop>,
}
impl ResourceBundle {
/// Clears the resource set.
pub(crate) fn clear(&mut self) {
self.patches.clear();
self.stops.clear();
}
}
#[derive(Clone)]
/// Description of a late bound resource.
pub enum ResourcePatch {
/// Gradient ramp resource.
Ramp {
/// Byte offset to the ramp id in the draw data stream.
offset: usize,
/// Range of the gradient stops in the resource set.
stops: Range<usize>,
},
}
/// Builder for constructing a scene or scene fragment.
pub struct SceneBuilder<'a> {
scene: &'a mut SceneData,
scene: &'a mut Encoding,
layer_depth: u32,
}
@ -192,7 +84,7 @@ impl<'a> SceneBuilder<'a> {
}
/// Creates a new builder for constructing a scene.
fn new(scene: &'a mut SceneData, is_fragment: bool) -> Self {
fn new(scene: &'a mut Encoding, is_fragment: bool) -> Self {
scene.reset(is_fragment);
Self {
scene,
@ -210,21 +102,23 @@ impl<'a> SceneBuilder<'a> {
shape: &impl Shape,
) {
let blend = blend.into();
self.maybe_encode_transform(transform);
self.linewidth(-1.0);
if !self.encode_path(shape, true) {
self.scene
.encode_transform(Transform::from_kurbo(&transform));
self.scene.encode_linewidth(-1.0);
if !self.scene.encode_shape(shape, true) {
// If the layer shape is invalid, encode a valid empty path. This suppresses
// all drawing until the layer is popped.
self.encode_path(&Rect::new(0.0, 0.0, 0.0, 0.0), true);
self.scene
.encode_shape(&Rect::new(0.0, 0.0, 0.0, 0.0), true);
}
self.begin_clip(blend, alpha.clamp(0.0, 1.0));
self.scene.encode_begin_clip(blend, alpha.clamp(0.0, 1.0));
self.layer_depth += 1;
}
/// Pops the current layer.
pub fn pop_layer(&mut self) {
if self.layer_depth > 0 {
self.end_clip();
self.scene.encode_end_clip();
self.layer_depth -= 1;
}
}
@ -238,15 +132,17 @@ impl<'a> SceneBuilder<'a> {
brush_transform: Option<Affine>,
shape: &impl Shape,
) {
self.maybe_encode_transform(transform);
self.linewidth(-1.0);
if self.encode_path(shape, true) {
self.scene
.encode_transform(Transform::from_kurbo(&transform));
self.scene.encode_linewidth(-1.0);
if self.scene.encode_shape(shape, true) {
if let Some(brush_transform) = brush_transform {
self.encode_transform(transform * brush_transform);
self.swap_last_tags();
self.encode_brush(brush);
self.scene
.encode_transform(Transform::from_kurbo(&(transform * brush_transform)));
self.scene.swap_last_path_tags();
self.scene.encode_brush(brush, 1.0);
} else {
self.encode_brush(brush);
self.scene.encode_brush(brush, 1.0);
}
}
}
@ -260,373 +156,33 @@ impl<'a> SceneBuilder<'a> {
brush_transform: Option<Affine>,
shape: &impl Shape,
) {
self.maybe_encode_transform(transform);
self.linewidth(style.width);
if self.encode_path(shape, false) {
self.scene
.encode_transform(Transform::from_kurbo(&transform));
self.scene.encode_linewidth(style.width);
if self.scene.encode_shape(shape, false) {
if let Some(brush_transform) = brush_transform {
self.encode_transform(transform * brush_transform);
self.swap_last_tags();
self.encode_brush(brush);
self.scene
.encode_transform(Transform::from_kurbo(&(transform * brush_transform)));
self.scene.swap_last_path_tags();
self.scene.encode_brush(brush, 1.0);
} else {
self.encode_brush(brush);
self.scene.encode_brush(brush, 1.0);
}
}
}
/// Appends a fragment to the scene.
pub fn append(&mut self, fragment: &SceneFragment, transform: Option<Affine>) {
self.scene.append(&fragment.data, &transform);
self.scene.append(
&fragment.data,
&transform.map(|xform| Transform::from_kurbo(&xform)),
);
}
/// Completes construction and finalizes the underlying scene.
pub fn finish(mut self) {
pub fn finish(self) {
for _ in 0..self.layer_depth {
self.end_clip();
self.scene.encode_end_clip();
}
}
}
impl<'a> SceneBuilder<'a> {
/// Encodes a path for the specified shape.
///
/// When the `is_fill` parameter is true, closes any open subpaths by inserting
/// a line to the start point of the subpath with the end segment bit set.
fn encode_path(&mut self, shape: &impl Shape, is_fill: bool) -> bool {
let mut b = PathBuilder::new(
&mut self.scene.tag_stream,
&mut self.scene.pathseg_stream,
is_fill,
);
for el in shape.path_elements(0.1) {
match el {
PathEl::MoveTo(p0) => b.move_to(p0.x as f32, p0.y as f32),
PathEl::LineTo(p0) => b.line_to(p0.x as f32, p0.y as f32),
PathEl::QuadTo(p0, p1) => {
b.quad_to(p0.x as f32, p0.y as f32, p1.x as f32, p1.y as f32)
}
PathEl::CurveTo(p0, p1, p2) => b.cubic_to(
p0.x as f32,
p0.y as f32,
p1.x as f32,
p1.y as f32,
p2.x as f32,
p2.y as f32,
),
PathEl::ClosePath => b.close_path(),
}
}
b.finish();
if b.n_pathseg != 0 {
self.scene.n_path += 1;
self.scene.n_pathseg += b.n_pathseg;
true
} else {
false
}
}
fn maybe_encode_transform(&mut self, transform: Affine) {
if self.scene.transform_stream.last() != Some(&affine_to_f32(&transform)) {
self.encode_transform(transform);
}
}
fn encode_transform(&mut self, transform: Affine) {
self.scene.tag_stream.push(0x20);
self.scene.transform_stream.push(affine_to_f32(&transform));
}
// Swap the last two tags in the tag stream; used for transformed
// gradients.
fn swap_last_tags(&mut self) {
let len = self.scene.tag_stream.len();
self.scene.tag_stream.swap(len - 1, len - 2);
}
// -1.0 means "fill"
fn linewidth(&mut self, linewidth: f32) {
if self.scene.linewidth_stream.last() != Some(&linewidth) {
self.scene.tag_stream.push(0x40);
self.scene.linewidth_stream.push(linewidth);
}
}
fn encode_brush<'b>(&mut self, brush: impl Into<BrushRef<'b>>) {
match brush.into() {
BrushRef::Solid(color) => {
self.scene.drawtag_stream.push(DRAWTAG_FILLCOLOR);
let rgba_color = color.to_premul_u32();
self.scene
.drawdata_stream
.extend(bytemuck::bytes_of(&FillColor { rgba_color }));
}
BrushRef::LinearGradient(gradient) => {
let index = self.add_ramp(&gradient.stops);
self.scene.drawtag_stream.push(DRAWTAG_FILLLINGRADIENT);
self.scene
.drawdata_stream
.extend(bytemuck::bytes_of(&FillLinGradient {
index,
p0: point_to_f32(gradient.start),
p1: point_to_f32(gradient.end),
}));
}
BrushRef::RadialGradient(gradient) => {
let index = self.add_ramp(&gradient.stops);
self.scene.drawtag_stream.push(DRAWTAG_FILLRADGRADIENT);
self.scene
.drawdata_stream
.extend(bytemuck::bytes_of(&FillRadGradient {
index,
p0: point_to_f32(gradient.start_center),
p1: point_to_f32(gradient.end_center),
r0: gradient.start_radius,
r1: gradient.end_radius,
}));
}
BrushRef::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"),
}
}
fn add_ramp(&mut self, stops: &[ColorStop]) -> u32 {
let offset = self.scene.drawdata_stream.len();
let resources = &mut self.scene.resources;
let stops_start = resources.stops.len();
resources.stops.extend_from_slice(stops);
resources.patches.push(ResourcePatch::Ramp {
offset,
stops: stops_start..stops_start + stops.len(),
});
0
}
/// Start a clip.
fn begin_clip(&mut self, blend: BlendMode, alpha: f32) {
self.scene.drawtag_stream.push(DRAWTAG_BEGINCLIP);
let element = Clip {
blend: encode_blend_mode(blend),
alpha,
};
self.scene
.drawdata_stream
.extend(bytemuck::bytes_of(&element));
self.scene.n_clip += 1;
}
fn end_clip(&mut self) {
self.scene.drawtag_stream.push(DRAWTAG_ENDCLIP);
// This is a dummy path, and will go away with the new clip impl.
self.scene.tag_stream.push(0x10);
self.scene.n_path += 1;
self.scene.n_clip += 1;
}
}
fn encode_blend_mode(mode: BlendMode) -> u32 {
(mode.mix as u32) << 8 | mode.compose as u32
}
// Tags for draw objects. See shader/shared/drawtag.wgsl for the authoritative source.
const DRAWTAG_FILLCOLOR: u32 = 0x44;
const DRAWTAG_FILLLINGRADIENT: u32 = 0x114;
const DRAWTAG_FILLRADGRADIENT: u32 = 0x2dc;
const DRAWTAG_BEGINCLIP: u32 = 0x9;
const DRAWTAG_ENDCLIP: u32 = 0x21;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
pub struct FillColor {
rgba_color: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
pub struct FillLinGradient {
index: u32,
p0: [f32; 2],
p1: [f32; 2],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
pub struct FillRadGradient {
index: u32,
p0: [f32; 2],
p1: [f32; 2],
r0: f32,
r1: f32,
}
#[allow(unused)]
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
pub struct FillImage {
index: u32,
// [i16; 2]
offset: u32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
pub struct Clip {
blend: u32,
alpha: f32,
}
struct PathBuilder<'a> {
tag_stream: &'a mut Vec<u8>,
// If we're never going to use the i16 encoding, it might be
// slightly faster to store this as Vec<u32>, we'd get aligned
// stores on ARM etc.
pathseg_stream: &'a mut Vec<u8>,
first_pt: [f32; 2],
state: PathState,
n_pathseg: u32,
is_fill: bool,
}
#[derive(PartialEq)]
enum PathState {
Start,
MoveTo,
NonemptySubpath,
}
impl<'a> PathBuilder<'a> {
pub fn new(tags: &'a mut Vec<u8>, pathsegs: &'a mut Vec<u8>, is_fill: bool) -> PathBuilder<'a> {
PathBuilder {
tag_stream: tags,
pathseg_stream: pathsegs,
first_pt: [0.0, 0.0],
state: PathState::Start,
n_pathseg: 0,
is_fill,
}
}
pub fn move_to(&mut self, x: f32, y: f32) {
if self.is_fill {
self.close_path();
}
let buf = [x, y];
let bytes = bytemuck::bytes_of(&buf);
self.first_pt = buf;
if self.state == PathState::MoveTo {
let new_len = self.pathseg_stream.len() - 8;
self.pathseg_stream.truncate(new_len);
} else if self.state == PathState::NonemptySubpath {
if let Some(tag) = self.tag_stream.last_mut() {
*tag |= 4;
}
}
self.pathseg_stream.extend_from_slice(bytes);
self.state = PathState::MoveTo;
}
pub fn line_to(&mut self, x: f32, y: f32) {
if self.state == PathState::Start {
if self.n_pathseg == 0 {
// This copies the behavior of kurbo which treats an initial line, quad
// or curve as a move.
self.move_to(x, y);
return;
}
self.move_to(self.first_pt[0], self.first_pt[1]);
}
let buf = [x, y];
let bytes = bytemuck::bytes_of(&buf);
self.pathseg_stream.extend_from_slice(bytes);
self.tag_stream.push(9);
self.state = PathState::NonemptySubpath;
self.n_pathseg += 1;
}
pub fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
if self.state == PathState::Start {
if self.n_pathseg == 0 {
self.move_to(x2, y2);
return;
}
self.move_to(self.first_pt[0], self.first_pt[1]);
}
let buf = [x1, y1, x2, y2];
let bytes = bytemuck::bytes_of(&buf);
self.pathseg_stream.extend_from_slice(bytes);
self.tag_stream.push(10);
self.state = PathState::NonemptySubpath;
self.n_pathseg += 1;
}
pub fn cubic_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {
if self.state == PathState::Start {
if self.n_pathseg == 0 {
self.move_to(x3, y3);
return;
}
self.move_to(self.first_pt[0], self.first_pt[1]);
}
let buf = [x1, y1, x2, y2, x3, y3];
let bytes = bytemuck::bytes_of(&buf);
self.pathseg_stream.extend_from_slice(bytes);
self.tag_stream.push(11);
self.state = PathState::NonemptySubpath;
self.n_pathseg += 1;
}
pub fn close_path(&mut self) {
match self.state {
PathState::Start => return,
PathState::MoveTo => {
let new_len = self.pathseg_stream.len() - 8;
self.pathseg_stream.truncate(new_len);
self.state = PathState::Start;
return;
}
PathState::NonemptySubpath => (),
}
let len = self.pathseg_stream.len();
if len < 8 {
// can't happen
return;
}
let first_bytes = bytemuck::bytes_of(&self.first_pt);
if &self.pathseg_stream[len - 8..len] != first_bytes {
self.pathseg_stream.extend_from_slice(first_bytes);
self.tag_stream.push(13);
self.n_pathseg += 1;
} else {
if let Some(tag) = self.tag_stream.last_mut() {
*tag |= 4;
}
}
self.state = PathState::Start;
}
pub fn finish(&mut self) {
if self.is_fill {
self.close_path();
}
if self.state == PathState::MoveTo {
let new_len = self.pathseg_stream.len() - 8;
self.pathseg_stream.truncate(new_len);
}
if self.n_pathseg != 0 {
if let Some(tag) = self.tag_stream.last_mut() {
*tag |= 4;
}
self.tag_stream.push(0x10);
}
}
}
fn affine_to_f32(affine: &Affine) -> [f32; 6] {
affine.as_coeffs().map(|value| value as f32)
}
fn affine_from_f32(coeffs: &[f32; 6]) -> Affine {
Affine::new(coeffs.map(|value| value as f64))
}
fn point_to_f32(point: Point) -> [f32; 2] {
[point.x as f32, point.y as f32]
}