blob: 9d8dbcd6a3e52de919c3bcc059a705a9496ff5aa [file] [log] [blame] [edit]
// Copyright 2024 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use vello_api::peniko::color::{AlphaColor, Srgb};
pub(crate) const WIDE_TILE_WIDTH: usize = 256;
pub(crate) const STRIP_HEIGHT: usize = 4;
#[derive(Debug)]
pub(crate) struct WideTile {
pub(crate) bg: AlphaColor<Srgb>,
pub(crate) cmds: Vec<Cmd>,
n_zero_clip: usize,
n_clip: usize,
}
#[derive(Debug)]
pub(crate) enum Cmd {
Fill(CmdFill),
Strip(CmdStrip),
/// Pushes a new transparent buffer to the clip stack.
PushClip,
/// Pops the clip stack.
PopClip,
ClipFill(CmdClipFill),
ClipStrip(CmdClipStrip),
}
#[derive(Debug)]
pub(crate) struct CmdFill {
pub(crate) x: u32,
pub(crate) width: u32,
// TODO: Probably want this pre-packed to u32 to avoid packing cost
pub(crate) color: AlphaColor<Srgb>,
}
#[derive(Debug)]
pub(crate) struct CmdStrip {
pub(crate) x: u32,
pub(crate) width: u32,
pub(crate) alpha_ix: usize,
pub(crate) color: AlphaColor<Srgb>,
}
/// Same as fill, but copies top of clip stack to next on stack.
#[derive(Debug)]
pub(crate) struct CmdClipFill {
pub(crate) x: u32,
pub(crate) width: u32,
// TODO: this should probably get at least an alpha for group opacity
// Also, this is where blend modes go.
}
/// Same as strip, but composites top of clip stack to next on stack.
#[derive(Debug)]
pub(crate) struct CmdClipStrip {
pub(crate) x: u32,
pub(crate) width: u32,
pub(crate) alpha_ix: usize,
// See `CmdClipFill` for blending extension points
}
impl Default for WideTile {
fn default() -> Self {
Self {
bg: AlphaColor::TRANSPARENT,
cmds: vec![],
n_zero_clip: 0,
n_clip: 0,
}
}
}
impl WideTile {
pub(crate) fn fill(&mut self, x: u32, width: u32, color: AlphaColor<Srgb>) {
if !self.is_zero_clip() {
// Note that we could be more aggressive in optimizing a whole-tile opaque fill
// even with a clip stack. It would be valid to elide all drawing commands from
// the enclosing clip push up to the fill. Further, we could extend the clip
// push command to include a background color, rather than always starting with
// a transparent buffer. Lastly, a sequence of push(bg); strip/fill; pop could
// be replaced with strip/fill with the color (the latter is true even with a
// non-opaque color).
//
// However, the extra cost of tracking such optimizations may outweigh the
// benefit, especially in hybrid mode with GPU painting.
if x == 0
&& width == WIDE_TILE_WIDTH as u32
&& color.components[3] == 1.0
&& self.n_clip == 0
{
self.cmds.clear();
self.bg = color;
} else {
self.cmds.push(Cmd::Fill(CmdFill { x, width, color }));
}
}
}
pub(crate) fn strip(&mut self, cmd_strip: CmdStrip) {
if !self.is_zero_clip() {
self.cmds.push(Cmd::Strip(cmd_strip));
}
}
pub(crate) fn push(&mut self, cmd: Cmd) {
self.cmds.push(cmd);
}
pub(crate) fn push_clip(&mut self) {
if !self.is_zero_clip() {
self.push(Cmd::PushClip);
self.n_clip += 1;
}
}
pub(crate) fn pop_clip(&mut self) {
if !self.is_zero_clip() {
if matches!(self.cmds.last(), Some(Cmd::PushClip)) {
// Nothing was drawn inside the clip, elide it.
self.cmds.pop();
} else {
self.push(Cmd::PopClip);
}
self.n_clip -= 1;
}
}
pub(crate) fn push_zero_clip(&mut self) {
self.n_zero_clip += 1;
}
pub(crate) fn pop_zero_clip(&mut self) {
self.n_zero_clip -= 1;
}
pub(crate) fn is_zero_clip(&mut self) -> bool {
self.n_zero_clip > 0
}
pub(crate) fn clip_strip(&mut self, cmd_clip_strip: CmdClipStrip) {
if !self.is_zero_clip() && !matches!(self.cmds.last(), Some(Cmd::PushClip)) {
self.cmds.push(Cmd::ClipStrip(cmd_clip_strip));
}
}
pub(crate) fn clip_fill(&mut self, x: u32, width: u32) {
if !self.is_zero_clip() && !matches!(self.cmds.last(), Some(Cmd::PushClip)) {
self.cmds.push(Cmd::ClipFill(CmdClipFill { x, width }));
}
}
}