diff --git a/shader/fine.wgsl b/shader/fine.wgsl index 7b298ca..edce31e 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -138,7 +138,7 @@ fn fill_path(tile: Tile, xy: vec2) -> array { } // 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; } diff --git a/src/encoding.rs b/src/encoding.rs new file mode 100644 index 0000000..f24c3bf --- /dev/null +++ b/src/encoding.rs @@ -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}; diff --git a/src/encoding/draw.rs b/src/encoding/draw.rs new file mode 100644 index 0000000..acb9b1c --- /dev/null +++ b/src/encoding/draw.rs @@ -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 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, + } + } +} diff --git a/src/encoding/encoding.rs b/src/encoding/encoding.rs new file mode 100644 index 0000000..4ba6fdb --- /dev/null +++ b/src/encoding/encoding.rs @@ -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, + /// The path data stream. + pub path_data: Vec, + /// The draw tag stream. + pub draw_tags: Vec, + /// The draw data stream. + pub draw_data: Vec, + /// Draw data patches for late bound resources. + pub patches: Vec, + /// Color stop collection for gradients. + pub color_stops: Vec, + /// The transform stream. + pub transforms: Vec, + /// The line width stream. + pub linewidths: Vec, + /// 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) { + 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>, 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 +} diff --git a/src/encoding/math.rs b/src/encoding/math.rs new file mode 100644 index 0000000..1f92cb8 --- /dev/null +++ b/src/encoding/math.rs @@ -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] +} diff --git a/src/encoding/monoid.rs b/src/encoding/monoid.rs new file mode 100644 index 0000000..ce3bd8a --- /dev/null +++ b/src/encoding/monoid.rs @@ -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: 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; +} diff --git a/src/encoding/packed.rs b/src/encoding/packed.rs new file mode 100644 index 0000000..7004477 --- /dev/null +++ b/src/encoding/packed.rs @@ -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, + /// 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(slice: &[T]) -> usize { + slice.len() * std::mem::size_of::() +} + +fn size_to_words(byte_size: usize) -> u32 { + (byte_size / std::mem::size_of::()) as u32 +} + +fn align_up(len: usize, alignment: u32) -> usize { + len + (len.wrapping_neg() & alignment as usize - 1) +} diff --git a/src/encoding/path.rs b/src/encoding/path.rs new file mode 100644 index 0000000..a0a01eb --- /dev/null +++ b/src/encoding/path.rs @@ -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 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, + data: &'a mut Vec, + 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, + data: &'a mut Vec, + 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 + } +} diff --git a/src/ramp.rs b/src/encoding/resource.rs similarity index 66% rename from src/ramp.rs rename to src/encoding/resource.rs index 0d83881..c980d7b 100644 --- a/src/ramp.rs +++ b/src/encoding/resource.rs @@ -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, + }, +} + +#[derive(Default)] +struct RampCache { epoch: u64, map: HashMap, data: Vec, diff --git a/src/lib.rs b/src/lib.rs index d468bd7..dd967ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/render.rs b/src/render.rs index 97fd53c..ab751e1 100644 --- a/src/render.rs +++ b/src/render.rs @@ -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::()) 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 = 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 = 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), diff --git a/src/scene.rs b/src/scene.rs index 06201f6..a271009 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -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, - pub pathseg_stream: Vec, - pub linewidth_stream: Vec, - pub drawtag_stream: Vec, - pub drawdata_stream: Vec, - 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) { - 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, - /// Cache of color stops, referenced by range from the patches. - pub stops: Vec, -} - -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, - }, -} - /// 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, 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, 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) { - 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>) { - 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, - // If we're never going to use the i16 encoding, it might be - // slightly faster to store this as Vec, we'd get aligned - // stores on ARM etc. - pathseg_stream: &'a mut Vec, - 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, pathsegs: &'a mut Vec, 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] -}