blob: 48c6b12cea995e179f0431e65025f2629b293400 [file] [log] [blame]
// Copyright 2023 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense
use std::cmp::max;
use vello_encoding::{
BinHeader, BumpAllocators, ConfigUniform, DRAW_INFO_FLAGS_FILL_RULE_BIT, DrawMonoid, DrawTag,
Path, Tile,
};
use super::{
CMD_BEGIN_CLIP, CMD_BLUR_RECT, CMD_COLOR, CMD_END, CMD_END_CLIP, CMD_FILL, CMD_IMAGE, CMD_JUMP,
CMD_LIN_GRAD, CMD_RAD_GRAD, CMD_SOLID, CMD_SWEEP_GRAD, CpuBinding, PTCL_INITIAL_ALLOC,
};
// Tiles per bin
const N_TILE_X: usize = 16;
const N_TILE_Y: usize = 16;
const N_TILE: usize = N_TILE_X * N_TILE_Y;
// If changing also change in config.wgsl
const BLEND_STACK_SPLIT: u32 = 4;
// Pixels per tile
const TILE_WIDTH: u32 = 16;
const TILE_HEIGHT: u32 = 16;
const PTCL_INCREMENT: u32 = 256;
const PTCL_HEADROOM: u32 = 2;
// Modeled in the WGSL as private-scoped variables
struct TileState {
cmd_offset: u32,
cmd_limit: u32,
}
impl TileState {
fn new(tile_ix: u32) -> Self {
let cmd_offset = tile_ix * PTCL_INITIAL_ALLOC;
let cmd_limit = cmd_offset + (PTCL_INITIAL_ALLOC - PTCL_HEADROOM);
Self {
cmd_offset,
cmd_limit,
}
}
fn alloc_cmd(
&mut self,
size: u32,
config: &ConfigUniform,
bump: &mut BumpAllocators,
ptcl: &mut [u32],
) {
if self.cmd_offset + size >= self.cmd_limit {
let ptcl_dyn_start =
config.width_in_tiles * config.height_in_tiles * PTCL_INITIAL_ALLOC;
let chunk_size = PTCL_INCREMENT.max(size + PTCL_HEADROOM);
let new_cmd = ptcl_dyn_start + bump.ptcl;
bump.ptcl += chunk_size;
ptcl[self.cmd_offset as usize] = CMD_JUMP;
ptcl[self.cmd_offset as usize + 1] = new_cmd;
self.cmd_offset = new_cmd;
self.cmd_limit = new_cmd + (PTCL_INCREMENT - PTCL_HEADROOM);
}
}
fn write(&mut self, ptcl: &mut [u32], offset: u32, value: u32) {
ptcl[(self.cmd_offset + offset) as usize] = value;
}
fn write_path(
&mut self,
config: &ConfigUniform,
bump: &mut BumpAllocators,
ptcl: &mut [u32],
tile: &mut Tile,
draw_flags: u32,
) {
let n_segs = tile.segment_count_or_ix;
if n_segs != 0 {
let seg_ix = bump.segments;
tile.segment_count_or_ix = !seg_ix;
bump.segments += n_segs;
self.alloc_cmd(4, config, bump, ptcl);
self.write(ptcl, 0, CMD_FILL);
let even_odd = (draw_flags & DRAW_INFO_FLAGS_FILL_RULE_BIT) != 0;
let size_and_rule = (n_segs << 1) | (even_odd as u32);
self.write(ptcl, 1, size_and_rule);
self.write(ptcl, 2, seg_ix);
self.write(ptcl, 3, tile.backdrop as u32);
self.cmd_offset += 4;
} else {
self.alloc_cmd(1, config, bump, ptcl);
self.write(ptcl, 0, CMD_SOLID);
self.cmd_offset += 1;
}
}
fn write_color(
&mut self,
config: &ConfigUniform,
bump: &mut BumpAllocators,
ptcl: &mut [u32],
rgba_color: u32,
) {
self.alloc_cmd(2, config, bump, ptcl);
self.write(ptcl, 0, CMD_COLOR);
self.write(ptcl, 1, rgba_color);
self.cmd_offset += 2;
}
fn write_image(
&mut self,
config: &ConfigUniform,
bump: &mut BumpAllocators,
ptcl: &mut [u32],
info_offset: u32,
) {
self.alloc_cmd(2, config, bump, ptcl);
self.write(ptcl, 0, CMD_IMAGE);
self.write(ptcl, 1, info_offset);
self.cmd_offset += 2;
}
fn write_grad(
&mut self,
config: &ConfigUniform,
bump: &mut BumpAllocators,
ptcl: &mut [u32],
ty: u32,
index: u32,
info_offset: u32,
) {
self.alloc_cmd(3, config, bump, ptcl);
self.write(ptcl, 0, ty);
self.write(ptcl, 1, index);
self.write(ptcl, 2, info_offset);
self.cmd_offset += 3;
}
fn write_blur_rect(
&mut self,
config: &ConfigUniform,
bump: &mut BumpAllocators,
ptcl: &mut [u32],
rgba_color: u32,
info_offset: u32,
) {
self.alloc_cmd(3, config, bump, ptcl);
self.write(ptcl, 0, CMD_BLUR_RECT);
self.write(ptcl, 1, info_offset);
self.write(ptcl, 2, rgba_color);
self.cmd_offset += 3;
}
fn write_begin_clip(
&mut self,
config: &ConfigUniform,
bump: &mut BumpAllocators,
ptcl: &mut [u32],
) {
self.alloc_cmd(1, config, bump, ptcl);
self.write(ptcl, 0, CMD_BEGIN_CLIP);
self.cmd_offset += 1;
}
fn write_end_clip(
&mut self,
config: &ConfigUniform,
bump: &mut BumpAllocators,
ptcl: &mut [u32],
blend: u32,
alpha: f32,
) {
self.alloc_cmd(3, config, bump, ptcl);
self.write(ptcl, 0, CMD_END_CLIP);
self.write(ptcl, 1, blend);
self.write(ptcl, 2, f32::to_bits(alpha));
self.cmd_offset += 3;
}
}
fn coarse_main(
config: &ConfigUniform,
scene: &[u32],
draw_monoids: &[DrawMonoid],
bin_headers: &[BinHeader],
info_bin_data: &[u32],
paths: &[Path],
tiles: &mut [Tile],
bump: &mut BumpAllocators,
ptcl: &mut [u32],
) {
let width_in_tiles = config.width_in_tiles;
let height_in_tiles = config.height_in_tiles;
let width_in_bins = width_in_tiles.div_ceil(N_TILE_X as u32);
let height_in_bins = height_in_tiles.div_ceil(N_TILE_Y as u32);
let n_bins = width_in_bins * height_in_bins;
let bin_data_start = config.layout.bin_data_start;
let drawtag_base = config.layout.draw_tag_base;
let mut compacted = vec![vec![]; N_TILE];
let n_partitions = config.layout.n_draw_objects.div_ceil(N_TILE as u32);
for bin in 0..n_bins {
for v in &mut compacted {
v.clear();
}
let bin_x = bin % width_in_bins;
let bin_y = bin / width_in_bins;
let bin_tile_x = N_TILE_X as u32 * bin_x;
let bin_tile_y = N_TILE_Y as u32 * bin_y;
for part in 0..n_partitions {
let in_ix = part * N_TILE as u32 + bin;
let bin_header = bin_headers[in_ix as usize];
let start = bin_data_start + bin_header.chunk_offset;
for i in 0..bin_header.element_count {
let drawobj_ix = info_bin_data[(start + i) as usize];
let tag = scene[(drawtag_base + drawobj_ix) as usize];
if DrawTag(tag) != DrawTag::NOP {
let draw_monoid = draw_monoids[drawobj_ix as usize];
let path_ix = draw_monoid.path_ix;
let path = paths[path_ix as usize];
let dx = path.bbox[0] as i32 - bin_tile_x as i32;
let dy = path.bbox[1] as i32 - bin_tile_y as i32;
let x0 = dx.clamp(0, N_TILE_X as i32);
let y0 = dy.clamp(0, N_TILE_Y as i32);
let x1 = (path.bbox[2] as i32 - bin_tile_x as i32).clamp(0, N_TILE_X as i32);
let y1 = (path.bbox[3] as i32 - bin_tile_y as i32).clamp(0, N_TILE_Y as i32);
for y in y0..y1 {
for x in x0..x1 {
compacted[(y * N_TILE_X as i32 + x) as usize].push(drawobj_ix);
}
}
}
}
}
// compacted now has the list of draw objects for each tile.
// While the WGSL source does at most 256 draw objects at a time,
// this version does all the draw objects in a tile.
for tile_ix in 0..N_TILE {
let tile_x = (tile_ix % N_TILE_X) as u32;
let tile_y = (tile_ix / N_TILE_X) as u32;
let this_tile_ix = (bin_tile_y + tile_y) * width_in_tiles + bin_tile_x + tile_x;
let mut tile_state = TileState::new(this_tile_ix);
let blend_offset = tile_state.cmd_offset;
tile_state.cmd_offset += 1;
let mut clip_depth = 0;
let mut render_blend_depth = 0;
let mut max_blend_depth = 0_u32;
let mut clip_zero_depth = 0;
for drawobj_ix in &compacted[tile_ix] {
let drawtag = scene[(drawtag_base + drawobj_ix) as usize];
if clip_zero_depth == 0 {
let draw_monoid = draw_monoids[*drawobj_ix as usize];
let path_ix = draw_monoid.path_ix;
let path = paths[path_ix as usize];
let bbox = path.bbox;
let stride = bbox[2] - bbox[0];
let x = bin_tile_x + tile_x - bbox[0];
let y = bin_tile_y + tile_y - bbox[1];
let tile = &mut tiles[(path.tiles + y * stride + x) as usize];
let is_clip = (drawtag & 1) != 0;
let mut is_blend = false;
let dd = config.layout.draw_data_base + draw_monoid.scene_offset;
let di = draw_monoid.info_offset;
if is_clip {
const BLEND_CLIP: u32 = (128 << 8) | 3;
let blend = scene[dd as usize];
is_blend = blend != BLEND_CLIP;
}
let draw_flags = info_bin_data[di as usize];
let even_odd = (draw_flags & DRAW_INFO_FLAGS_FILL_RULE_BIT) != 0;
let n_segs = tile.segment_count_or_ix;
// If this draw object represents an even-odd fill and we know that no line segment
// crosses this tile and then this draw object should not contribute to the tile if its
// backdrop (i.e. the winding number of its top-left corner) is even.
let backdrop_clear = if even_odd {
tile.backdrop.abs() & 1
} else {
tile.backdrop
} == 0;
let include_tile = n_segs != 0 || (backdrop_clear == is_clip) || is_blend;
if include_tile {
match DrawTag(drawtag) {
DrawTag::COLOR => {
tile_state.write_path(config, bump, ptcl, tile, draw_flags);
let rgba_color = scene[dd as usize];
tile_state.write_color(config, bump, ptcl, rgba_color);
}
DrawTag::IMAGE => {
tile_state.write_path(config, bump, ptcl, tile, draw_flags);
tile_state.write_image(config, bump, ptcl, di + 1);
}
DrawTag::LINEAR_GRADIENT => {
tile_state.write_path(config, bump, ptcl, tile, draw_flags);
let index = scene[dd as usize];
tile_state.write_grad(
config,
bump,
ptcl,
CMD_LIN_GRAD,
index,
di + 1,
);
}
DrawTag::RADIAL_GRADIENT => {
tile_state.write_path(config, bump, ptcl, tile, draw_flags);
let index = scene[dd as usize];
tile_state.write_grad(
config,
bump,
ptcl,
CMD_RAD_GRAD,
index,
di + 1,
);
}
DrawTag::SWEEP_GRADIENT => {
tile_state.write_path(config, bump, ptcl, tile, draw_flags);
let index = scene[dd as usize];
tile_state.write_grad(
config,
bump,
ptcl,
CMD_SWEEP_GRAD,
index,
di + 1,
);
}
DrawTag::BLUR_RECT => {
tile_state.write_path(config, bump, ptcl, tile, draw_flags);
let rgba_color = scene[dd as usize];
tile_state.write_blur_rect(config, bump, ptcl, rgba_color, di + 1);
}
DrawTag::BEGIN_CLIP => {
if tile.segment_count_or_ix == 0 && tile.backdrop == 0 {
clip_zero_depth = clip_depth + 1;
} else {
tile_state.write_begin_clip(config, bump, ptcl);
// TODO: Do we need to track this separately, seems like it
// is always the same as clip_depth in this code path
render_blend_depth += 1;
max_blend_depth = max(render_blend_depth, max_blend_depth);
}
clip_depth += 1;
}
DrawTag::END_CLIP => {
clip_depth -= 1;
// A clip shape is always a non-zero fill (draw_flags=0).
tile_state.write_path(config, bump, ptcl, tile, 0);
let blend = scene[dd as usize];
let alpha = f32::from_bits(scene[dd as usize + 1]);
tile_state.write_end_clip(config, bump, ptcl, blend, alpha);
render_blend_depth -= 1;
}
_ => todo!(),
}
}
} else {
// In "clip zero" state, suppress all drawing
match DrawTag(drawtag) {
DrawTag::BEGIN_CLIP => clip_depth += 1,
DrawTag::END_CLIP => {
if clip_depth == clip_zero_depth {
clip_zero_depth = 0;
}
clip_depth -= 1;
}
_ => (),
}
}
}
if bin_tile_x + tile_x < width_in_tiles && bin_tile_y + tile_y < height_in_tiles {
ptcl[tile_state.cmd_offset as usize] = CMD_END;
let scratch_size =
(max_blend_depth.saturating_sub(BLEND_STACK_SPLIT)) * TILE_WIDTH * TILE_HEIGHT;
ptcl[blend_offset as usize] = bump.blend;
bump.blend += scratch_size;
}
}
}
}
pub fn coarse(_n_wg: u32, resources: &[CpuBinding<'_>]) {
let config = resources[0].as_typed();
let scene = resources[1].as_slice();
let draw_monoids = resources[2].as_slice();
let bin_headers = resources[3].as_slice();
let info_bin_data = resources[4].as_slice();
let paths = resources[5].as_slice();
let mut tiles = resources[6].as_slice_mut();
let mut bump = resources[7].as_typed_mut();
let mut ptcl = resources[8].as_slice_mut();
coarse_main(
&config,
&scene,
&draw_monoids,
&bin_headers,
&info_bin_data,
&paths,
&mut tiles,
&mut bump,
&mut ptcl,
);
}