blob: 3b61aeb5f5e3d81a3ac1e30156d84bc200e85d49 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use rayon::prelude::*;
use crate::{Lines, PIXEL_SHIFT, PIXEL_WIDTH};
mod grouped_iter;
mod pixel_segment;
use grouped_iter::GroupedIter;
pub use pixel_segment::{bit_field_lens, search_last_by_key, PixelSegment, PixelSegmentUnpacked};
// This finds the ith term in the ordered union of two sequences:
//
// 1. a * x + c
// 2. b * x + d
//
// It works by estimating the amount of items that came from sequence 1 and
// sequence 2 such that the next item will be the ith. This results in two
// indices from each sequence. The final step is to simply pick the smaller one
// which naturally comes next.
fn find(i: i32, sum_recip: f32, cd: f32, a: f32, b: f32, c: f32, d: f32) -> f32 {
const BIAS: f32 = -0.000_000_5;
let i = i as f32;
let ja = if b.is_finite() { (b.mul_add(i, -cd).mul_add(sum_recip, BIAS)).ceil() } else { i };
let jb = if a.is_finite() { (a.mul_add(i, cd).mul_add(sum_recip, BIAS)).ceil() } else { i };
let guess_a = a.mul_add(ja, c);
let guess_b = b.mul_add(jb, d);
guess_a.min(guess_b)
}
fn round(v: f32) -> i32 {
unsafe { (v + 0.5).floor().to_int_unchecked() }
}
#[derive(Debug, Default)]
pub struct Rasterizer<const TW: usize, const TH: usize> {
segments: Vec<PixelSegment<TW, TH>>,
}
impl<const TW: usize, const TH: usize> Rasterizer<TW, TH> {
pub fn new() -> Self {
Self::default()
}
pub fn segments(&self) -> &[PixelSegment<TW, TH>] {
self.segments.as_slice()
}
#[inline(never)]
pub fn rasterize(&mut self, lines: &Lines) {
// Shard the workload into set of similar output size in PixelSegment.
let iter = GroupedIter::new(&lines.lengths);
iter.into_par_iter()
.with_min_len(256)
.map(|(li, pi)| {
let a = lines.a[li as usize];
let b = lines.b[li as usize];
let c = lines.c[li as usize];
let d = lines.d[li as usize];
let i = pi as i32 - i32::from(c != 0.0) - i32::from(d != 0.0);
let i0 = i;
let i1 = i + 1;
let sum_recip = (a + b).recip();
let cd = c - d;
let t0 = find(i0, sum_recip, cd, a, b, c, d).max(0.0);
let t1 = find(i1, sum_recip, cd, a, b, c, d).min(1.0);
let x0f = t0.mul_add(lines.dx[li as usize], lines.x0[li as usize]);
let y0f = t0.mul_add(lines.dy[li as usize], lines.y0[li as usize]);
let x1f = t1.mul_add(lines.dx[li as usize], lines.x0[li as usize]);
let y1f = t1.mul_add(lines.dy[li as usize], lines.y0[li as usize]);
let x0_sub = round(x0f);
let x1_sub = round(x1f);
let y0_sub = round(y0f);
let y1_sub = round(y1f);
let border_x = x0_sub.min(x1_sub) >> PIXEL_SHIFT;
let border_y = y0_sub.min(y1_sub) >> PIXEL_SHIFT;
let tile_x = (border_x >> TW.trailing_zeros() as i32) as i16;
let tile_y = (border_y >> TH.trailing_zeros() as i32) as i16;
let local_x = (border_x & (TW - 1) as i32) as u8;
let local_y = (border_y & (TH - 1) as i32) as u8;
let border = (border_x << PIXEL_SHIFT) + PIXEL_WIDTH as i32;
let height = y1_sub - y0_sub;
let double_area_multiplier =
((x1_sub - x0_sub).abs() + 2 * (border - x0_sub.max(x1_sub))) as u8;
let cover = height as i8;
PixelSegment::new(
lines.orders[li as usize],
tile_x,
tile_y,
local_x,
local_y,
double_area_multiplier,
cover,
)
})
.collect_into_vec(&mut self.segments);
}
const BIT_FIELD_LENGTH: [usize; 7] = bit_field_lens::<TW, TH>();
#[inline]
pub fn sort(&mut self) {
// Sort by (tile_y, tile_x, layer_id).
let offset = Self::BIT_FIELD_LENGTH[3]
+ Self::BIT_FIELD_LENGTH[4]
+ Self::BIT_FIELD_LENGTH[5]
+ Self::BIT_FIELD_LENGTH[6];
self.segments.par_sort_unstable_by_key(|segment| {
let segment: u64 = segment.into();
segment >> offset
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{GeomId, Layer, LinesBuilder, Order, Point, TILE_HEIGHT, TILE_WIDTH};
fn segments(p0: Point, p1: Point) -> Vec<PixelSegment<TILE_WIDTH, TILE_HEIGHT>> {
let mut builder = LinesBuilder::new();
builder.push(GeomId::default(), [p0, p1]);
let lines = builder
.build(|_| Some(Layer { order: Some(Order::new(0).unwrap()), ..Default::default() }));
let mut rasterizer = Rasterizer::default();
rasterizer.rasterize(&lines);
rasterizer.segments().to_vec()
}
fn areas_and_covers(segments: &[PixelSegment<TILE_WIDTH, TILE_HEIGHT>]) -> Vec<(i16, i8)> {
segments
.iter()
.map(|&segment| {
let segment: PixelSegmentUnpacked = segment.into();
(segment.double_area, segment.cover)
})
.collect()
}
#[test]
fn area_cover_octant_1() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(3.0, 2.0))),
[(11 * 16, 11), (5 * 8 + 2 * (5 * 8), 5), (5 * 8, 5), (11 * 16, 11)],
);
}
#[test]
fn area_cover_octant_2() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(2.0, 3.0))),
[(16 * 11 + 2 * (16 * 5), 16), (8 * 5, 8), (8 * 5 + 2 * (8 * 11), 8), (16 * 11, 16)],
);
}
#[test]
fn area_cover_octant_3() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(-2.0, 3.0))),
[(16 * 11, 16), (8 * 5 + 2 * (8 * 11), 8), (8 * 5, 8), (16 * 11 + 2 * (16 * 5), 16)],
);
}
#[test]
fn area_cover_octant_4() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(-3.0, 2.0))),
[(11 * 16, 11), (5 * 8, 5), (5 * 8 + 2 * (5 * 8), 5), (11 * 16, 11)],
);
}
#[test]
fn area_cover_octant_5() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(-3.0, -2.0))),
[(-(11 * 16), -11), (-(5 * 8), -5), (-(5 * 8 + 2 * (5 * 8)), -5), (-(11 * 16), -11)],
);
}
#[test]
fn area_cover_octant_6() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(-2.0, -3.0))),
[
(-(16 * 11), -16),
(-(8 * 5 + 2 * (8 * 11)), -8),
(-(8 * 5), -8),
(-(16 * 11 + 2 * (16 * 5)), -16),
],
);
}
#[test]
fn area_cover_octant_7() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(2.0, -3.0))),
[
(-(16 * 11 + 2 * (16 * 5)), -16),
(-(8 * 5), -8),
(-(8 * 5 + 2 * (8 * 11)), -8),
(-(16 * 11), -16),
],
);
}
#[test]
fn area_cover_octant_8() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(3.0, -2.0))),
[(-(11 * 16), -11), (-(5 * 8 + 2 * (5 * 8)), -5), (-(5 * 8), -5), (-(11 * 16), -11)],
);
}
#[test]
fn area_cover_axis_0() {
assert_eq!(areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(1.0, 0.0))), []);
}
#[test]
fn area_cover_axis_45() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(1.0, 1.0))),
[(16 * 16, 16)],
);
}
#[test]
fn area_cover_axis_90() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(0.0, 1.0))),
[(2 * 16 * 16, 16)],
);
}
#[test]
fn area_cover_axis_135() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(-1.0, 1.0))),
[(16 * 16, 16)],
);
}
#[test]
fn area_cover_axis_180() {
assert_eq!(areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(-1.0, 0.0))), []);
}
#[test]
fn area_cover_axis_225() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(-1.0, -1.0))),
[(-(16 * 16), -16)],
);
}
#[test]
fn area_cover_axis_270() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(0.0, -1.0))),
[(2 * -(16 * 16), -16)],
);
}
#[test]
fn area_cover_axis_315() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(-1.0, -1.0))),
[(-(16 * 16), -16)],
);
}
fn tiles(segments: &[PixelSegment<TILE_WIDTH, TILE_HEIGHT>]) -> Vec<(i16, i16, u8, u8)> {
segments
.iter()
.map(|&segment| {
let segment: PixelSegmentUnpacked = segment.into();
(segment.tile_x, segment.tile_y, segment.local_x, segment.local_y)
})
.collect()
}
#[test]
fn tile_octant_1() {
assert_eq!(
tiles(&segments(
Point::new(TILE_WIDTH as f32, TILE_HEIGHT as f32),
Point::new(TILE_WIDTH as f32 + 3.0, TILE_HEIGHT as f32 + 2.0),
)),
[(1, 1, 0, 0), (1, 1, 1, 0), (1, 1, 1, 1), (1, 1, 2, 1)],
);
}
#[test]
fn tile_octant_2() {
assert_eq!(
tiles(&segments(
Point::new(TILE_WIDTH as f32, TILE_HEIGHT as f32),
Point::new(TILE_WIDTH as f32 + 2.0, TILE_HEIGHT as f32 + 3.0),
)),
[(1, 1, 0, 0), (1, 1, 0, 1), (1, 1, 1, 1), (1, 1, 1, 2)],
);
}
#[test]
fn tile_octant_3() {
assert_eq!(
tiles(&segments(
Point::new(-(TILE_WIDTH as f32), TILE_HEIGHT as f32),
Point::new(-(TILE_WIDTH as f32) - 2.0, TILE_HEIGHT as f32 + 3.0),
)),
[
(-1, 1, TILE_WIDTH as u8 - 1, 0),
(-1, 1, TILE_WIDTH as u8 - 1, 1),
(-1, 1, TILE_WIDTH as u8 - 2, 1),
(-1, 1, TILE_WIDTH as u8 - 2, 2),
],
);
}
#[test]
fn tile_octant_4() {
assert_eq!(
tiles(&segments(
Point::new(-(TILE_WIDTH as f32), TILE_HEIGHT as f32),
Point::new(-(TILE_WIDTH as f32) - 3.0, TILE_HEIGHT as f32 + 2.0),
)),
[
(-1, 1, TILE_WIDTH as u8 - 1, 0),
(-1, 1, TILE_WIDTH as u8 - 2, 0),
(-1, 1, TILE_WIDTH as u8 - 2, 1),
(-1, 1, TILE_WIDTH as u8 - 3, 1),
],
);
}
#[test]
fn tile_octant_5() {
assert_eq!(
tiles(&segments(
Point::new(-(TILE_WIDTH as f32), -(TILE_HEIGHT as f32)),
Point::new(-(TILE_WIDTH as f32) - 3.0, -(TILE_HEIGHT as f32) - 2.0),
)),
[
(-1, -1, TILE_WIDTH as u8 - 1, TILE_HEIGHT as u8 - 1),
(-1, -1, TILE_WIDTH as u8 - 2, TILE_HEIGHT as u8 - 1),
(-1, -1, TILE_WIDTH as u8 - 2, TILE_HEIGHT as u8 - 2),
(-1, -1, TILE_WIDTH as u8 - 3, TILE_HEIGHT as u8 - 2),
],
);
}
#[test]
fn tile_octant_6() {
assert_eq!(
tiles(&segments(
Point::new(-(TILE_WIDTH as f32), -(TILE_HEIGHT as f32)),
Point::new(-(TILE_WIDTH as f32) - 2.0, -(TILE_HEIGHT as f32) - 3.0),
)),
[
(-1, -1, TILE_WIDTH as u8 - 1, TILE_HEIGHT as u8 - 1),
(-1, -1, TILE_WIDTH as u8 - 1, TILE_HEIGHT as u8 - 2),
(-1, -1, TILE_WIDTH as u8 - 2, TILE_HEIGHT as u8 - 2),
(-1, -1, TILE_WIDTH as u8 - 2, TILE_HEIGHT as u8 - 3),
],
);
}
#[test]
fn tile_octant_7() {
assert_eq!(
tiles(&segments(
Point::new(TILE_WIDTH as f32, -(TILE_HEIGHT as f32)),
Point::new(TILE_WIDTH as f32 + 2.0, -(TILE_HEIGHT as f32) - 3.0),
)),
[
(1, -1, 0, TILE_HEIGHT as u8 - 1),
(1, -1, 0, TILE_HEIGHT as u8 - 2),
(1, -1, 1, TILE_HEIGHT as u8 - 2),
(1, -1, 1, TILE_HEIGHT as u8 - 3),
],
);
}
#[test]
fn tile_octant_8() {
assert_eq!(
tiles(&segments(
Point::new(TILE_WIDTH as f32, -(TILE_HEIGHT as f32)),
Point::new(TILE_WIDTH as f32 + 3.0, -(TILE_HEIGHT as f32) - 2.0),
)),
[
(1, -1, 0, TILE_HEIGHT as u8 - 1),
(1, -1, 1, TILE_HEIGHT as u8 - 1),
(1, -1, 1, TILE_HEIGHT as u8 - 2),
(1, -1, 2, TILE_HEIGHT as u8 - 2),
],
);
}
#[test]
fn start_and_end_not_on_pixel_border() {
assert_eq!(
areas_and_covers(&segments(Point::new(0.5, 0.25), Point::new(4.0, 2.0)))[0],
(4 * 8, 4),
);
assert_eq!(
areas_and_covers(&segments(Point::new(0.0, 0.0), Point::new(3.5, 1.75)))[4],
(4 * 8 + 2 * (4 * 8), 4),
);
}
}