Skip to content

Commit

Permalink
Merge pull request #389 from linebender/gpu-stroke-rework
Browse files Browse the repository at this point in the history
New "style" encoding to replace "linewidth"
  • Loading branch information
armansito committed Oct 27, 2023
2 parents 1aed7fb + a6b3f23 commit b5e3ae5
Show file tree
Hide file tree
Showing 11 changed files with 434 additions and 55 deletions.
33 changes: 15 additions & 18 deletions crates/encoding/src/encoding.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2022 The Vello authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use super::{DrawColor, DrawTag, PathEncoder, PathTag, Transform};
use super::{DrawColor, DrawTag, PathEncoder, PathTag, Style, Transform};

use peniko::{kurbo::Shape, BlendMode, BrushRef, Color, Fill};

Expand All @@ -25,8 +25,8 @@ pub struct Encoding {
pub draw_data: Vec<u8>,
/// The transform stream.
pub transforms: Vec<Transform>,
/// The line width stream.
pub linewidths: Vec<f32>,
/// The style stream
pub styles: Vec<Style>,
/// Late bound resource data.
#[cfg(feature = "full")]
pub resources: Resources,
Expand Down Expand Up @@ -56,7 +56,7 @@ impl Encoding {
self.transforms.clear();
self.path_tags.clear();
self.path_data.clear();
self.linewidths.clear();
self.styles.clear();
self.draw_data.clear();
self.draw_tags.clear();
self.n_paths = 0;
Expand All @@ -67,7 +67,7 @@ impl Encoding {
self.resources.reset();
if !is_fragment {
self.transforms.push(Transform::IDENTITY);
self.linewidths.push(-1.0);
self.styles.push(Style::from_fill(Fill::NonZero));
}
}

Expand Down Expand Up @@ -96,7 +96,7 @@ impl Encoding {
run.stream_offsets.draw_tags += offsets.draw_tags;
run.stream_offsets.draw_data += offsets.draw_data;
run.stream_offsets.transforms += offsets.transforms;
run.stream_offsets.linewidths += offsets.linewidths;
run.stream_offsets.styles += offsets.styles;
run
}));
self.resources
Expand Down Expand Up @@ -148,7 +148,7 @@ impl Encoding {
} else {
self.transforms.extend_from_slice(&other.transforms);
}
self.linewidths.extend_from_slice(&other.linewidths);
self.styles.extend_from_slice(&other.styles);
}

/// Returns a snapshot of the current stream offsets.
Expand All @@ -159,19 +159,16 @@ impl Encoding {
draw_tags: self.draw_tags.len(),
draw_data: self.draw_data.len(),
transforms: self.transforms.len(),
linewidths: self.linewidths.len(),
styles: self.styles.len(),
}
}

/// Encodes a fill style.
pub fn encode_fill_style(&mut self, fill: Fill) {
let linewidth = match fill {
Fill::NonZero => -1.0,
Fill::EvenOdd => -2.0,
};
if self.linewidths.last() != Some(&linewidth) {
self.path_tags.push(PathTag::LINEWIDTH);
self.linewidths.push(linewidth);
let style = Style::from_fill(fill);
if self.styles.last() != Some(&style) {
self.path_tags.push(PathTag::STYLE);
self.styles.push(style);
}
}

Expand Down Expand Up @@ -449,8 +446,8 @@ pub struct StreamOffsets {
pub draw_data: usize,
/// Current length of transform stream.
pub transforms: usize,
/// Current length of linewidth stream.
pub linewidths: usize,
/// Current length of style stream.
pub styles: usize,
}

impl StreamOffsets {
Expand All @@ -461,6 +458,6 @@ impl StreamOffsets {
self.draw_tags += other.draw_tags;
self.draw_data += other.draw_data;
self.transforms += other.transforms;
self.linewidths += other.linewidths;
self.styles += other.styles;
}
}
2 changes: 1 addition & 1 deletion crates/encoding/src/glyph_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ impl CachedRange {
draw_tags: self.end.draw_tags - self.start.draw_tags,
draw_data: self.end.draw_data - self.start.draw_data,
transforms: self.end.transforms - self.start.transforms,
linewidths: self.end.linewidths - self.start.linewidths,
styles: self.end.styles - self.start.styles,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/encoding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub use math::Transform;
pub use monoid::Monoid;
pub use path::{
Cubic, LineSoup, Path, PathBbox, PathEncoder, PathMonoid, PathSegment, PathSegmentType,
PathTag, SegmentCount, Tile,
PathTag, SegmentCount, Style, Tile,
};
pub use resolve::{resolve_solid_paths_only, Layout};

Expand Down
97 changes: 97 additions & 0 deletions crates/encoding/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,100 @@ impl Mul for Transform {
pub fn point_to_f32(point: kurbo::Point) -> [f32; 2] {
[point.x as f32, point.y as f32]
}

/// Converts an f32 to IEEE-754 binary16 format represented as the bits of a u16.
/// This implementation was adapted from Fabian Giesen's float_to_half_fast3()
/// function which can be found at https://gist.github.com/rygorous/2156668#file-gistfile1-cpp-L285
///
/// TODO: We should consider adopting https://crates.io/crates/half as a dependency since it nicely
/// wraps native ARM and x86 instructions for floating-point conversion.
#[allow(unused)] // for now
pub(crate) fn f32_to_f16(val: f32) -> u16 {
const INF_32: u32 = 255 << 23;
const INF_16: u32 = 31 << 23;
const MAGIC: u32 = 15 << 23;
const SIGN_MASK: u32 = 0x8000_0000u32;
const ROUND_MASK: u32 = !0xFFFu32;

let u = val.to_bits();
let sign = u & SIGN_MASK;
let u = u ^ sign;

// NOTE all the integer compares in this function can be safely
// compiled into signed compares since all operands are below
// 0x80000000. Important if you want fast straight SSE2 code
// (since there's no unsigned PCMPGTD).

// Inf or NaN (all exponent bits set)
let output: u16 = if u >= INF_32 {
// NaN -> qNaN and Inf->Inf
if u > INF_32 {
0x7E00
} else {
0x7C00
}
} else {
// (De)normalized number or zero
let mut u = u & ROUND_MASK;
u = (f32::from_bits(u) * f32::from_bits(MAGIC)).to_bits();
u = u.overflowing_sub(ROUND_MASK).0;

// Clamp to signed infinity if exponent overflowed
if u > INF_16 {
u = INF_16;
}
(u >> 13) as u16 // Take the bits!
};
output | (sign >> 16) as u16
}

#[cfg(test)]
mod tests {
use super::f32_to_f16;

#[test]
fn test_f32_to_f16_simple() {
let input: f32 = std::f32::consts::PI;
let output: u16 = f32_to_f16(input);
assert_eq!(0x4248u16, output) // 3.141
}

#[test]
fn test_f32_to_f16_nan_overflow() {
// A signaling NaN with unset high bits but a low bit that could get accidentally masked
// should get converted to a quiet NaN and not infinity.
let input: f32 = f32::from_bits(0x7F800001u32);
assert!(input.is_nan());
let output: u16 = f32_to_f16(input);
assert_eq!(0x7E00, output);
}

#[test]
fn test_f32_to_16_inf() {
let input: f32 = f32::from_bits(0x7F800000u32);
assert!(input.is_infinite());
let output: u16 = f32_to_f16(input);
assert_eq!(0x7C00, output);
}

#[test]
fn test_f32_to_16_exponent_rebias() {
let input: f32 = 0.00003051758;
let output: u16 = f32_to_f16(input);
assert_eq!(0x0200, output); // 0.00003052
}

#[test]
fn test_f32_to_16_exponent_overflow() {
let input: f32 = 1.701412e38;
let output: u16 = f32_to_f16(input);
assert_eq!(0x7C00, output); // +inf
}

#[test]
fn test_f32_to_16_exponent_overflow_neg_inf() {
let input: f32 = -1.701412e38;
let output: u16 = f32_to_f16(input);
assert_eq!(0xFC00, output); // -inf
}
}
Loading

0 comments on commit b5e3ae5

Please sign in to comment.