vello/tests/src/clip.rs
Raph Levien acb3933d94 Variable size encoding of draw objects
This patch switches to a variable size encoding of draw objects.

In addition to the CPU-side scene encoding, it changes the representation of intermediate per draw object state from the `Annotated` struct to a variable "info" encoding. In addition, the bounding boxes are moved to a separate array (for a more "structure of "arrays" approach). Data that's unchanged from the scene encoding is not copied. Rather, downstream stages can access the data from the scene buffer (reducing allocation and copying).

Prefix sums, computed in `DrawMonoid` track the offset of both scene and intermediate data. The tags for the CPU-side encoding have been split into their own stream (again a change from AoS to SoA style).

This is not necessarily the final form. There's some stuff (including at least one piet-gpu-derive type) that can be deleted. In addition, the linewidth field should probably move from the info to path-specific. Also, the 1:1 correspondence between draw object and path has not yet been broken.

Closes #152
2022-03-14 16:32:08 -07:00

237 lines
7.9 KiB
Rust

// Copyright 2022 The piet-gpu authors.
//
// 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.
//! Tests for the piet-gpu clip processing stage.
use bytemuck::{Pod, Zeroable};
use rand::Rng;
use piet_gpu::stages::{self, ClipBinding, ClipCode, DrawMonoid};
use piet_gpu_hal::{BufWrite, BufferUsage};
use crate::{Config, Runner, TestResult};
struct ClipData {
clip_stream: Vec<u32>,
// In the atomic-int friendly encoding
path_bbox_stream: Vec<PathBbox>,
}
#[derive(Copy, Clone, Debug, Pod, Zeroable, Default)]
#[repr(C)]
struct PathBbox {
bbox: [u32; 4],
linewidth: f32,
trans_ix: u32,
}
pub unsafe fn clip_test(runner: &mut Runner, config: &Config) -> TestResult {
let mut result = TestResult::new("clip");
let n_clip: u64 = config.size.choose(1 << 8, 1 << 12, 1 << 16);
let data = ClipData::new(n_clip);
let stage_config = data.get_config();
let config_buf = runner
.session
.create_buffer_init(std::slice::from_ref(&stage_config), BufferUsage::STORAGE)
.unwrap();
// Need to actually get data uploaded
let mut memory = runner.buf_down(data.memory_size(), BufferUsage::STORAGE);
{
let mut buf_write = memory.map_write(..);
data.fill_memory(&mut buf_write);
}
let code = ClipCode::new(&runner.session);
let binding = ClipBinding::new(&runner.session, &code, &config_buf, &memory.dev_buf);
let mut commands = runner.commands();
commands.write_timestamp(0);
commands.upload(&memory);
binding.record(&mut commands.cmd_buf, &code, n_clip as u32);
commands.download(&memory);
commands.write_timestamp(1);
runner.submit(commands);
let dst = memory.map_read(..);
if let Some(failure) = data.verify(&dst) {
result.fail(failure);
}
result
}
fn rand_bbox() -> [u32; 4] {
let mut rng = rand::thread_rng();
const Y_MIN: u32 = 32768;
const Y_MAX: u32 = Y_MIN + 1000;
let mut x0 = rng.gen_range(Y_MIN, Y_MAX);
let mut y0 = rng.gen_range(Y_MIN, Y_MAX);
let mut x1 = rng.gen_range(Y_MIN, Y_MAX);
let mut y1 = rng.gen_range(Y_MIN, Y_MAX);
if x0 > x1 {
std::mem::swap(&mut x0, &mut x1);
}
if y0 > y1 {
std::mem::swap(&mut y0, &mut y1);
}
[x0, y0, x1, y1]
}
/// Convert from atomic-friendly to normal float bbox.
fn decode_bbox(raw: [u32; 4]) -> [f32; 4] {
fn decode(x: u32) -> f32 {
x as f32 - 32768.0
}
[
decode(raw[0]),
decode(raw[1]),
decode(raw[2]),
decode(raw[3]),
]
}
fn intersect_bbox(b0: [f32; 4], b1: [f32; 4]) -> [f32; 4] {
[
b0[0].max(b1[0]),
b0[1].max(b1[1]),
b0[2].min(b1[2]),
b0[3].min(b1[3]),
]
}
const INFTY_BBOX: [f32; 4] = [-1e9, -1e9, 1e9, 1e9];
impl ClipData {
/// Generate a random clip sequence
fn new(n: u64) -> ClipData {
// Simple LCG random generator, for deterministic results
let mut z = 20170705u64;
let mut depth = 0;
let mut path_bbox_stream = Vec::new();
let clip_stream = (0..n)
.map(|i| {
let is_push = if depth == 0 {
true
} else if depth >= 255 {
false
} else {
z = z.wrapping_mul(742938285) % ((1 << 31) - 1);
(z % 2) != 0
};
if is_push {
depth += 1;
let path_ix = path_bbox_stream.len() as u32;
let bbox = rand_bbox();
let path_bbox = PathBbox {
bbox,
..Default::default()
};
path_bbox_stream.push(path_bbox);
path_ix
} else {
depth -= 1;
!(i as u32)
}
})
.collect();
ClipData {
clip_stream,
path_bbox_stream,
}
}
fn get_config(&self) -> stages::Config {
let n_clip = self.clip_stream.len();
let n_path = self.path_bbox_stream.len();
let clip_alloc = 0;
let path_bbox_alloc = clip_alloc + 4 * n_clip;
let drawmonoid_alloc = path_bbox_alloc + 24 * n_path;
let clip_bic_alloc = drawmonoid_alloc + 8 * n_clip;
// TODO: this is over-allocated, we only need one bic per wg
let clip_stack_alloc = clip_bic_alloc + 8 * n_clip;
let clip_bbox_alloc = clip_stack_alloc + 20 * n_clip;
stages::Config {
clip_alloc: clip_alloc as u32,
path_bbox_alloc: path_bbox_alloc as u32,
drawmonoid_alloc: drawmonoid_alloc as u32,
clip_bic_alloc: clip_bic_alloc as u32,
clip_stack_alloc: clip_stack_alloc as u32,
clip_bbox_alloc: clip_bbox_alloc as u32,
n_clip: n_clip as u32,
..Default::default()
}
}
fn memory_size(&self) -> u64 {
(8 + self.clip_stream.len() * (4 + 8 + 8 + 20 + 16) + self.path_bbox_stream.len() * 24)
as u64
}
fn fill_memory(&self, buf: &mut BufWrite) {
// offset / header; no dynamic allocation
buf.fill_zero(8);
buf.extend_slice(&self.clip_stream);
buf.extend_slice(&self.path_bbox_stream);
// drawmonoid is left uninitialized
}
fn verify(&self, buf: &[u8]) -> Option<String> {
let n_clip = self.clip_stream.len();
let n_path = self.path_bbox_stream.len();
let clip_bbox_start = 8 + n_clip * (4 + 8 + 8 + 20) + n_path * 24;
let clip_range = clip_bbox_start..(clip_bbox_start + n_clip * 16);
let clip_result = bytemuck::cast_slice::<u8, [f32; 4]>(&buf[clip_range]);
let draw_start = 8 + n_clip * 4 + n_path * 24;
let draw_range = draw_start..(draw_start + n_clip * 16);
let draw_result = bytemuck::cast_slice::<u8, DrawMonoid>(&buf[draw_range]);
let mut bbox_stack = Vec::new();
let mut parent_stack = Vec::new();
for (i, path_ix) in self.clip_stream.iter().enumerate() {
let mut expected_path = None;
if *path_ix >= 0x8000_0000 {
let parent = parent_stack.pop().unwrap();
expected_path = Some(self.clip_stream[parent as usize]);
bbox_stack.pop().unwrap();
} else {
parent_stack.push(i);
let path_bbox_stream = self.path_bbox_stream[*path_ix as usize];
let bbox = decode_bbox(path_bbox_stream.bbox);
let new = match bbox_stack.last() {
None => bbox,
Some(old) => intersect_bbox(*old, bbox),
};
bbox_stack.push(new);
};
let expected = bbox_stack.last().copied().unwrap_or(INFTY_BBOX);
let clip_bbox = clip_result[i];
if clip_bbox != expected {
return Some(format!(
"{}: path_ix={}, expected bbox={:?}, clip_bbox={:?}",
i, path_ix, expected, clip_bbox
));
}
if let Some(expected_path) = expected_path {
let actual_path = draw_result[i].path_ix;
if expected_path != actual_path {
return Some(format!(
"{}: expected path {}, actual {}",
i, expected_path, actual_path
));
}
}
}
None
}
}