blob: ac8ca43fbfafc89bd0889cd4fabe704f53c9749c [file] [log] [blame]
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Generating and processing wide tiles.
use crate::{
color::{AlphaColor, Srgb},
strip::Strip,
tile::Tile,
};
use alloc::vec;
use alloc::{boxed::Box, vec::Vec};
use vello_api::color::PremulRgba8;
use vello_api::{paint::Paint, peniko::Fill};
/// A container for wide tiles.
#[derive(Debug)]
pub struct Wide {
/// The width of the container.
pub width: u16,
/// The height of the container.
pub height: u16,
/// The wide tiles in the container.
pub tiles: Vec<WideTile>,
/// Stack of scene state, used for tracking clip regions.
pub state_stack: Vec<SceneState>,
/// Stack of active clip regions.
clip_stack: Vec<Clip>,
}
/// Scene state for rendering operations.
#[derive(Debug)]
pub struct SceneState {
/// Number of active clip regions.
pub n_clip: usize,
}
/// A clip region.
#[derive(Debug)]
struct Clip {
/// The intersected bounding box after clip
pub clip_bbox: Bbox,
/// The rendered path in sparse strip representation
pub strips: Box<[Strip]>,
/// The fill rule used for this clip
pub fill_rule: Fill,
}
/// A bounding box
///
/// The first two values represent the x0 and y0 coordinates, respectively.
/// The last two values represent the x1 and y1 coordinates, respectively.
/// x0, y0 — the top-left corner of the bounding box,
/// x1, y1 — the bottom-right corner of the bounding box.
#[derive(Debug, Clone)]
struct Bbox {
pub bbox: [u16; 4],
}
impl Bbox {
pub(crate) fn new(bbox: [u16; 4]) -> Self {
Self { bbox }
}
/// Get the x0 coordinate of the bounding box.
#[inline]
pub(crate) fn x0(&self) -> u16 {
self.bbox[0]
}
/// Get the y0 coordinate of the bounding box.
#[inline]
pub(crate) fn y0(&self) -> u16 {
self.bbox[1]
}
/// Get the x1 coordinate of the bounding box.
#[inline]
pub(crate) fn x1(&self) -> u16 {
self.bbox[2]
}
/// Get the y1 coordinate of the bounding box.
#[inline]
pub(crate) fn y1(&self) -> u16 {
self.bbox[3]
}
/// Create an empty bounding box (zero area).
#[inline]
pub(crate) fn empty() -> Self {
Self::new([0, 0, 0, 0])
}
/// Calculate the intersection of this bounding box with another.
#[inline]
pub(crate) fn intersect(&self, other: &Self) -> Self {
Self::new([
self.x0().max(other.x0()),
self.y0().max(other.y0()),
self.x1().min(other.x1()),
self.y1().min(other.y1()),
])
}
}
impl Wide {
/// Create a new container for wide tiles.
pub fn new(width: u16, height: u16) -> Self {
let width_tiles = width.div_ceil(WideTile::WIDTH);
let height_tiles = height.div_ceil(Tile::HEIGHT);
let mut tiles = Vec::with_capacity(usize::from(width_tiles) * usize::from(height_tiles));
for w in 0..width_tiles {
for h in 0..height_tiles {
tiles.push(WideTile::new(w * WideTile::WIDTH, h * Tile::HEIGHT));
}
}
Self {
tiles,
width,
height,
state_stack: vec![SceneState { n_clip: 0 }],
clip_stack: vec![],
}
}
/// Reset all tiles in the container.
pub fn reset(&mut self) {
for tile in &mut self.tiles {
tile.bg = AlphaColor::<Srgb>::TRANSPARENT.premultiply().to_rgba8();
tile.cmds.clear();
}
}
/// Return the number of horizontal tiles.
pub fn width_tiles(&self) -> u16 {
self.width.div_ceil(WideTile::WIDTH)
}
/// Return the number of vertical tiles.
pub fn height_tiles(&self) -> u16 {
self.height.div_ceil(Tile::HEIGHT)
}
/// Get the wide tile at a certain index.
///
/// Panics if the index is out-of-range.
pub fn get(&self, x: u16, y: u16) -> &WideTile {
assert!(
x < self.width_tiles() && y < self.height_tiles(),
"attempted to access out-of-bounds wide tile"
);
&self.tiles[usize::from(y) * usize::from(self.width_tiles()) + usize::from(x)]
}
/// Get mutable access to the wide tile at a certain index.
///
/// Panics if the index is out-of-range.
pub fn get_mut(&mut self, x: u16, y: u16) -> &mut WideTile {
assert!(
x < self.width_tiles() && y < self.height_tiles(),
"attempted to access out-of-bounds wide tile"
);
let idx = usize::from(y) * usize::from(self.width_tiles()) + usize::from(x);
&mut self.tiles[idx]
}
/// Return a reference to all wide tiles.
pub fn tiles(&self) -> &[WideTile] {
self.tiles.as_slice()
}
/// Generate wide tile commands from the strip buffer.
///
/// This method processes a buffer of strips that represent a path, applies the fill rule,
/// and generates appropriate drawing commands for each affected wide tile.
///
/// # Algorithm overview:
/// 1. For each strip in the buffer:
/// - Calculate its position and width in pixels
/// - Determine which wide tiles the strip intersects
/// - Generate alpha fill commands for the intersected wide tiles
/// 2. For active fill regions (determined by fill rule):
/// - Generate solid fill commands for the regions between strips
pub fn generate(&mut self, strip_buf: &[Strip], fill_rule: Fill, paint: Paint) {
if strip_buf.is_empty() {
return;
}
// Get current clip bounding box or full viewport if no clip is active
let bbox = self.get_bbox();
for i in 0..strip_buf.len() - 1 {
let strip = &strip_buf[i];
debug_assert!(
strip.y < self.height,
"Strips below the viewport should have been culled prior to this stage."
);
// Don't render strips that are outside the viewport width
if strip.x >= self.width {
continue;
}
let next_strip = &strip_buf[i + 1];
let x0 = strip.x;
let strip_y = strip.strip_y();
// Skip strips outside the current clip bounding box
if strip_y < bbox.y0() {
continue;
}
if strip_y >= bbox.y1() {
// The rest of our strips must be outside the clip, so we can break early.
break;
}
// Calculate the width of the strip in columns
let mut col = strip.alpha_idx / u32::from(Tile::HEIGHT);
let next_col = next_strip.alpha_idx / u32::from(Tile::HEIGHT);
// Can potentially be 0 if strip only changes winding without covering pixels
let strip_width = next_col.saturating_sub(col) as u16;
let x1 = x0 + strip_width;
// Calculate which wide tiles this strip intersects
let wtile_x0 = (x0 / WideTile::WIDTH).max(bbox.x0());
// It's possible that a strip extends into a new wide tile, but we don't actually
// have as many wide tiles (e.g. because the pixmap width is only 512, but
// strip ends at 513), so take the minimum between the rounded values and `width_tiles`.
let wtile_x1 = x1.div_ceil(WideTile::WIDTH).min(bbox.x1());
// Adjust column starting position if needed to respect clip boundaries
let mut x = x0;
let clip_x = bbox.x0() * WideTile::WIDTH;
if clip_x > x {
col += (clip_x - x) as u32;
x = clip_x;
}
// Generate alpha fill commands for each wide tile intersected by this strip
for wtile_x in wtile_x0..wtile_x1 {
let x_wtile_rel = x % WideTile::WIDTH;
// Restrict the width of the fill to the width of the wide tile
let width = x1.min((wtile_x + 1) * WideTile::WIDTH) - x;
let cmd = CmdAlphaFill {
x: x_wtile_rel,
width,
alpha_idx: (col * u32::from(Tile::HEIGHT)) as usize,
paint: paint.clone(),
};
x += width;
col += u32::from(width);
self.get_mut(wtile_x, strip_y).strip(cmd);
}
// Determine if the region between this strip and the next should be filled
// based on the fill rule (NonZero or EvenOdd)
let active_fill = match fill_rule {
Fill::NonZero => next_strip.winding != 0,
Fill::EvenOdd => next_strip.winding % 2 != 0,
};
// If region should be filled and both strips are on the same row,
// generate fill commands for the region between them
if active_fill && strip_y == next_strip.strip_y() {
// Clamp the fill to the clip bounding box
x = x1.max(bbox.x0() * WideTile::WIDTH);
let x2 = next_strip
.x
.min(self.width.next_multiple_of(WideTile::WIDTH));
let wfxt0 = (x1 / WideTile::WIDTH).max(bbox.x0());
let wfxt1 = x2.div_ceil(WideTile::WIDTH).min(bbox.x1());
// Generate fill commands for each wide tile in the fill region
for wtile_x in wfxt0..wfxt1 {
let x_wtile_rel = x % WideTile::WIDTH;
let width = x2.min((wtile_x + 1) * WideTile::WIDTH) - x;
x += width;
self.get_mut(wtile_x, strip_y)
.fill(x_wtile_rel, width, paint.clone());
}
}
}
}
/// Adds a clipping region defined by the provided strips.
///
/// This method takes a vector of strips representing a clip path, calculates the
/// intersection with the current clip region, and updates the clip stack.
///
/// # Algorithm overview:
/// 1. Calculate bounding box of the clip path
/// 2. Intersect with current clip bounding box
/// 3. For each tile in the intersected bounding box:
/// - If covered by zero winding: `push_zero_clip`
/// - If fully covered by non-zero winding: do nothing (clip is a no-op)
/// - If partially covered: `push_clip`
pub fn push_clip(&mut self, strips: impl Into<Box<[Strip]>>, fill_rule: Fill) {
let strips = strips.into();
let n_strips = strips.len();
// Calculate the bounding box of the clip path in strip coordinates
let path_bbox = if n_strips <= 1 {
Bbox::empty()
} else {
// Calculate the y range from first to last strip in wide tile coordinates
let wtile_y0 = strips[0].strip_y();
let wtile_y1 = strips[n_strips.saturating_sub(1)].strip_y() + 1;
// Calculate the x range by examining all strips in wide tile coordinates
let mut wtile_x0 = strips[0].x / WideTile::WIDTH;
let mut wtile_x1 = wtile_x0;
for i in 0..n_strips.saturating_sub(1) {
let strip = &strips[i];
let next_strip = &strips[i + 1];
let width =
((next_strip.alpha_idx - strip.alpha_idx) / u32::from(Tile::HEIGHT)) as u16;
let x = strip.x;
wtile_x0 = wtile_x0.min(x / WideTile::WIDTH);
wtile_x1 = wtile_x1.max((x + width).div_ceil(WideTile::WIDTH));
}
Bbox::new([wtile_x0, wtile_y0, wtile_x1, wtile_y1])
};
let parent_bbox = self.get_bbox();
// Calculate the intersection of the parent clip bounding box and the path bounding box.
let clip_bbox = parent_bbox.intersect(&path_bbox);
let mut cur_wtile_x = clip_bbox.x0();
let mut cur_wtile_y = clip_bbox.y0();
// Process strips to determine the clipping state for each wide tile
for i in 0..n_strips.saturating_sub(1) {
let strip = &strips[i];
let strip_y = strip.strip_y();
// Skip strips before current wide tile row
if strip_y < cur_wtile_y {
continue;
}
// Process wide tiles in rows before this strip's row
// These wide tiles are all zero-winding (outside the path)
while cur_wtile_y < strip_y.min(clip_bbox.y1()) {
for wtile_x in cur_wtile_x..clip_bbox.x1() {
self.get_mut(wtile_x, cur_wtile_y).push_zero_clip();
}
// Reset x to the left edge of the clip bounding box
cur_wtile_x = clip_bbox.x0();
// Move to the next row
cur_wtile_y += 1;
}
// If we've reached the bottom of the clip bounding box, stop processing
if cur_wtile_y == clip_bbox.y1() {
break;
}
// Process wide tiles to the left of this strip in the same row
let x = strip.x;
let wtile_x_clamped = (x / WideTile::WIDTH).min(clip_bbox.x1());
if cur_wtile_x < wtile_x_clamped {
// If winding is zero or doesn't match fill rule, these wide tiles are outside the path
let is_inside = match fill_rule {
Fill::NonZero => strip.winding != 0,
Fill::EvenOdd => strip.winding % 2 != 0,
};
if !is_inside {
for wtile_x in cur_wtile_x..wtile_x_clamped {
self.get_mut(wtile_x, cur_wtile_y).push_zero_clip();
}
}
// If winding is nonzero, then wide tiles covered entirely
// by sparse fill are no-op (no clipping is applied).
cur_wtile_x = wtile_x_clamped;
}
// Process wide tiles covered by the strip - these need actual clipping
let next_strip = &strips[i + 1];
let width = ((next_strip.alpha_idx - strip.alpha_idx) / u32::from(Tile::HEIGHT)) as u16;
let wtile_x1 = (x + width).div_ceil(WideTile::WIDTH).min(clip_bbox.x1());
if cur_wtile_x < wtile_x1 {
for wtile_x in cur_wtile_x..wtile_x1 {
self.get_mut(wtile_x, cur_wtile_y).push_clip();
}
cur_wtile_x = wtile_x1;
}
}
// Process any remaining wide tiles in the bounding box (all zero-winding)
while cur_wtile_y < clip_bbox.y1() {
for wtile_x in cur_wtile_x..clip_bbox.x1() {
self.get_mut(wtile_x, cur_wtile_y).push_zero_clip();
}
cur_wtile_x = clip_bbox.x0();
cur_wtile_y += 1;
}
self.clip_stack.push(Clip {
clip_bbox,
strips,
fill_rule,
});
self.state_stack.last_mut().unwrap().n_clip += 1;
}
/// Get the bounding box of the current clip region or the entire viewport if no clip regions are active.
fn get_bbox(&self) -> Bbox {
if let Some(top) = self.clip_stack.last() {
top.clip_bbox.clone()
} else {
// Convert pixel dimensions to wide tile coordinates
Bbox::new([0, 0, self.width_tiles(), self.height_tiles()])
}
}
/// Removes all active clip regions.
pub fn pop_clips(&mut self) {
while self.state_stack.last().unwrap().n_clip > 0 {
self.pop_clip();
}
}
/// Removes the most recently added clip region.
///
/// This is the inverse operation of `push_clip`, carefully undoing all the clipping
/// operations while also handling any rendering needed for the clip region itself.
///
/// # Algorithm overview:
/// 1. Retrieve the top clip from the stack
/// 2. For each wide tile in the clip's bounding box:
/// - If covered by zero winding: `pop_zero_clip`
/// - If fully covered by non-zero winding: do nothing (was no-op)
/// - If partially covered: render the clip and `pop_clip`
///
/// This operation must be symmetric with `push_clip` to maintain a balanced clip stack.
pub fn pop_clip(&mut self) {
self.state_stack.last_mut().unwrap().n_clip -= 1;
let Clip {
clip_bbox,
strips,
fill_rule,
} = self.clip_stack.pop().unwrap();
let n_strips = strips.len();
let mut cur_wtile_x = clip_bbox.x0();
let mut cur_wtile_y = clip_bbox.y0();
let mut pop_pending = false;
// Process each strip to determine the clipping state for each tile
for i in 0..n_strips.saturating_sub(1) {
let strip = &strips[i];
let strip_y = strip.strip_y();
// Skip strips before current tile row
if strip_y < cur_wtile_y {
continue;
}
// Process tiles in rows before this strip's row
// These tiles all had zero-winding clips
while cur_wtile_y < strip_y.min(clip_bbox.y1()) {
// Handle any pending clip pop from previous iteration
if core::mem::take(&mut pop_pending) {
self.get_mut(cur_wtile_x, cur_wtile_y).pop_clip();
cur_wtile_x += 1;
}
// Pop zero clips for all remaining tiles in this row
for wtile_x in cur_wtile_x..clip_bbox.x1() {
self.get_mut(wtile_x, cur_wtile_y).pop_zero_clip();
}
cur_wtile_x = clip_bbox.x0();
cur_wtile_y += 1;
}
// If we've reached the bottom of the clip bounding box, stop processing
if cur_wtile_y == clip_bbox.y1() {
break;
}
// Process tiles to the left of this strip in the same row
let x0 = strip.x;
let wtile_x_clamped = (x0 / WideTile::WIDTH).min(clip_bbox.x1());
if cur_wtile_x < wtile_x_clamped {
// Handle any pending clip pop from previous iteration
if core::mem::take(&mut pop_pending) {
self.get_mut(cur_wtile_x, cur_wtile_y).pop_clip();
cur_wtile_x += 1;
}
// Pop zero clips for tiles that had zero winding or didn't match fill rule
// TODO: The winding check is probably not needed; if there was a fill,
// the logic below should have advanced wtile_x.
let is_inside = match fill_rule {
Fill::NonZero => strip.winding != 0,
Fill::EvenOdd => strip.winding % 2 != 0,
};
if !is_inside {
for wtile_x in cur_wtile_x..wtile_x_clamped {
self.get_mut(wtile_x, cur_wtile_y).pop_zero_clip();
}
}
cur_wtile_x = wtile_x_clamped;
}
// Process tiles covered by the strip - render clip content and pop
let next_strip = &strips[i + 1];
let strip_width =
((next_strip.alpha_idx - strip.alpha_idx) / u32::from(Tile::HEIGHT)) as u16;
let x1 = x0 + strip_width;
let wtile_x0 = (x0 / WideTile::WIDTH).max(clip_bbox.x0());
let wtile_x1 = x1.div_ceil(WideTile::WIDTH).min(clip_bbox.x1());
// Calculate starting position and column for alpha mask
let mut x = x0;
let mut col = (strip.alpha_idx / u32::from(Tile::HEIGHT)) as u16;
let clip_x = clip_bbox.x0() * WideTile::WIDTH;
if clip_x > x {
col += clip_x - x;
x = clip_x;
}
// Render clip strips for each affected tile and mark for popping
for wtile_x in wtile_x0..wtile_x1 {
// If we've moved past tile_x and have a pending pop, do it now
if cur_wtile_x < wtile_x && core::mem::take(&mut pop_pending) {
self.get_mut(cur_wtile_x, cur_wtile_y).pop_clip();
}
// Calculate the portion of the strip that affects this tile
let x_rel = (x % WideTile::WIDTH) as u32;
let width = x1.min((wtile_x + 1) * WideTile::WIDTH) - x;
// Create clip strip command for rendering the partial coverage
let cmd = CmdClipAlphaFill {
x: x_rel,
width: width as u32,
alpha_idx: (col * Tile::HEIGHT) as usize,
};
x += width;
col += width;
// Apply the clip strip command and update state
self.get_mut(wtile_x, cur_wtile_y).clip_strip(cmd);
cur_wtile_x = wtile_x;
pop_pending = true;
}
// Handle fill regions between strips based on fill rule
let is_inside = match fill_rule {
Fill::NonZero => next_strip.winding != 0,
Fill::EvenOdd => next_strip.winding % 2 != 0,
};
if is_inside && strip_y == next_strip.strip_y() {
let x2 = next_strip.x;
let clipped_x2 = x2.min((cur_wtile_x + 1) * WideTile::WIDTH);
let width = clipped_x2.saturating_sub(x1);
// If there's a gap, fill it
if width > 0 {
let x_rel = (x1 % WideTile::WIDTH) as u32;
self.get_mut(cur_wtile_x, cur_wtile_y)
.clip_fill(x_rel, width as u32);
}
// If the next strip is a sentinel, skip the fill
// It's a sentinel in the row if there is non-zero winding for the sparse fill
// Look more into this in the strip.rs render function
if x2 == u16::MAX {
continue;
}
// If fill extends to next tile, pop current and handle next
if x2 > (cur_wtile_x + 1) * WideTile::WIDTH {
self.get_mut(cur_wtile_x, cur_wtile_y).pop_clip();
let width2 = x2 % WideTile::WIDTH;
cur_wtile_x = x2 / WideTile::WIDTH;
if width2 > 0 {
self.get_mut(cur_wtile_x, cur_wtile_y)
.clip_fill(0, width2 as u32);
}
}
}
}
// Handle any pending clip pop from the last iteration
if core::mem::take(&mut pop_pending) {
self.get_mut(cur_wtile_x, cur_wtile_y).pop_clip();
cur_wtile_x += 1;
}
// Process any remaining tiles in the bounding box (all zero-winding)
while cur_wtile_y < clip_bbox.y1() {
for wtile_x in cur_wtile_x..clip_bbox.x1() {
self.get_mut(wtile_x, cur_wtile_y).pop_zero_clip();
}
cur_wtile_x = clip_bbox.x0();
cur_wtile_y += 1;
}
}
}
/// A wide tile.
#[derive(Debug)]
pub struct WideTile {
/// The x coordinate of the wide tile.
pub x: u16,
/// The y coordinate of the wide tile.
pub y: u16,
/// The background of the tile.
pub bg: PremulRgba8,
/// The draw commands of the tile.
pub cmds: Vec<Cmd>,
/// The number of zero-winding clips.
pub n_zero_clip: usize,
/// The number of non-zero-winding clips.
pub n_clip: usize,
}
impl WideTile {
/// The width of a wide tile in pixels.
pub const WIDTH: u16 = 256;
/// Create a new wide tile.
pub fn new(x: u16, y: u16) -> Self {
Self {
x,
y,
bg: AlphaColor::<Srgb>::TRANSPARENT.premultiply().to_rgba8(),
cmds: vec![],
n_zero_clip: 0,
n_clip: 0,
}
}
pub(crate) fn fill(&mut self, x: u16, width: u16, paint: Paint) {
if !self.is_zero_clip() {
let bg = if let Paint::Solid(s) = &paint {
// 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.
let can_override = x == 0 && width == Self::WIDTH && s.a == 255 && self.n_clip == 0;
can_override.then_some(*s)
} else {
// TODO: Implement for indexed paints.
None
};
if let Some(bg) = bg {
self.cmds.clear();
self.bg = bg;
} else {
self.cmds.push(Cmd::Fill(CmdFill { x, width, paint }));
}
}
}
pub(crate) fn strip(&mut self, cmd_strip: CmdAlphaFill) {
if !self.is_zero_clip() {
self.cmds.push(Cmd::AlphaFill(cmd_strip));
}
}
pub(crate) fn push(&mut self, cmd: Cmd) {
self.cmds.push(cmd);
}
/// Adds a new clip region to the current wide tile.
pub fn push_clip(&mut self) {
if !self.is_zero_clip() {
self.push(Cmd::PushClip);
self.n_clip += 1;
}
}
/// Removes the most recently added clip region from the current wide tile.
pub 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;
}
}
/// Adds a zero-winding clip region to the stack.
pub fn push_zero_clip(&mut self) {
self.n_zero_clip += 1;
}
/// Removes the most recently added zero-winding clip region.
pub fn pop_zero_clip(&mut self) {
self.n_zero_clip -= 1;
}
/// Checks if the current clip region is a zero-winding clip.
pub fn is_zero_clip(&mut self) -> bool {
self.n_zero_clip > 0
}
/// Applies a clip strip operation with the given parameters.
pub fn clip_strip(&mut self, cmd_clip_strip: CmdClipAlphaFill) {
if !self.is_zero_clip() && !matches!(self.cmds.last(), Some(Cmd::PushClip)) {
self.cmds.push(Cmd::ClipStrip(cmd_clip_strip));
}
}
/// Applies a clip fill operation at the specified position and width.
pub 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 }));
}
}
}
/// A drawing command.
#[derive(Debug)]
pub enum Cmd {
/// A fill command.
Fill(CmdFill),
/// A fill command with alpha mask.
AlphaFill(CmdAlphaFill),
/// Pushes a new transparent buffer to the clip stack.
///
/// This command is used to start a new clip region.
PushClip,
/// Pops the clip stack.
///
/// This command is used to end the current clip region.
PopClip,
/// A fill command within a clipping region.
///
/// This command is used to fill a region of a wide tile within a clipping region.
ClipFill(CmdClipFill),
/// A fill command with alpha mask within a clipping region.
///
/// This command is used to fill a region of a wide tile with an alpha mask within a clipping region.
ClipStrip(CmdClipAlphaFill),
}
/// Fill a consecutive region of a wide tile.
#[derive(Debug)]
pub struct CmdFill {
/// The horizontal start position of the command in pixels.
pub x: u16,
/// The width of the command in pixels.
pub width: u16,
/// The paint that should be used to fill the area.
pub paint: Paint,
}
/// Fill a consecutive region of a wide tile with an alpha mask.
#[derive(Debug)]
pub struct CmdAlphaFill {
/// The horizontal start position of the command in pixels.
pub x: u16,
/// The width of the command in pixels.
pub width: u16,
/// The start index into the alpha buffer of the command.
pub alpha_idx: usize,
/// The paint that should be used to fill the area.
pub paint: Paint,
}
/// Same as fill, but copies top of clip stack to next on stack.
#[derive(Debug)]
pub struct CmdClipFill {
/// The horizontal start position of the command in pixels.
pub x: u32,
/// The width of the command in pixels.
pub width: u32,
}
/// Same as strip, but composites top of clip stack to next on stack.
#[derive(Debug)]
pub struct CmdClipAlphaFill {
/// The horizontal start position of the command in pixels.
pub x: u32,
/// The width of the command in pixels.
pub width: u32,
/// The start index into the alpha buffer of the command.
pub alpha_idx: usize,
}