-
Notifications
You must be signed in to change notification settings - Fork 164
Add fast path for stripping rectangles #900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 13 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
4e3faec
Nearly working version
LaurenzV 5ea063c
Fixes
LaurenzV a5513b6
Add reference images
LaurenzV 8db5b16
Fix comment
LaurenzV 89dff11
Reformat
LaurenzV 541d3d8
Some clippy fixes
LaurenzV f27f57c
More clippy fixes
LaurenzV 3870ffd
Update sparse_strips/vello_common/src/strip.rs
LaurenzV 38443c6
Micro-optimize transform_non_skewed rect
LaurenzV 68bbba4
Add clippy msg
LaurenzV e1d43a4
Reformat
LaurenzV 9f1374a
Merge branch 'main' into rectangles
LaurenzV f7b5c64
Add more test cases for rectangles as paths
LaurenzV 14bb997
Move methods to vello_common
LaurenzV 050f1d3
Add additional test cases
LaurenzV a7d8cfb
Add comment
LaurenzV bc16c1e
Add rectangle fast path to vello_hybrid
LaurenzV 013488e
Add one more additional test
LaurenzV 9165cfc
Reformat
LaurenzV 73d055f
Apply code review
LaurenzV File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
use vello_api::peniko::Fill; | ||
|
||
use crate::flatten::Line; | ||
use crate::kurbo::Rect; | ||
use crate::tile::{Tile, Tiles}; | ||
|
||
/// A strip. | ||
|
@@ -302,3 +303,186 @@ pub fn render( | |
} | ||
} | ||
} | ||
|
||
/// Draw the strips of a rectangle. This is faster than using the normal path, because we | ||
/// do not need to go through the "flatten", "tiling" and "sort" stages, but can instead | ||
/// directly emit strips. | ||
pub fn render_rect( | ||
rect: &Rect, | ||
strip_buf: &mut Vec<Strip>, | ||
alpha_buf: &mut Vec<u8>, | ||
width: u16, | ||
height: u16, | ||
) { | ||
// The idea for this fast path is as follows: | ||
// - We generate strips of width 1 for the left as well as the right side of the rectangle. The | ||
// left side has a winding number of 0, the right side has a winding number of 1. | ||
// - We generate a strip of the full rectangle width for the top and bottom part of the rectangle. | ||
// - Of course, it's also possible that a rectangle has a width of less than 2 or a height of | ||
// less than 4. The current logic does account for those edge cases. | ||
// - There could be some further optimizations (for example, if a rectangle is strip-aligned on | ||
// the y-axis, we don't need the strips for the top part of the rectangle), but I don't think | ||
// those edge cases are worth adding to the complexity of this method. | ||
|
||
strip_buf.clear(); | ||
|
||
// Don't try to draw empty rectangles. | ||
if rect.is_zero_area() { | ||
return; | ||
} | ||
|
||
// Note that we currently deal with negative-area rects as positive-area rects. | ||
// Shouldn't be a problem for solid fill, but might need some tweaking for gradient | ||
// and pattern fills. | ||
let (x0, x1, y0, y1) = ( | ||
rect.min_x().max(0.0) as f32, | ||
rect.max_x().min(width as f64) as f32, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for |
||
rect.min_y().max(0.0) as f32, | ||
rect.max_y().min(height as f64) as f32, | ||
); | ||
|
||
let top_strip_idx = (y0 as u16) / Tile::HEIGHT; | ||
let top_strip_y = top_strip_idx * Tile::HEIGHT; | ||
// In the wide tile generation stage, there is an assertion that all strips outside the | ||
// viewport must have been culled, so we cull here. | ||
// | ||
// This index is inclusive, i.e. pixels at row `bottom_strip_idx` | ||
// are still part of the rectangle. | ||
let bottom_strip_idx = (y1 as u16).min(height - 1) / Tile::HEIGHT; | ||
let bottom_strip_y = bottom_strip_idx * Tile::HEIGHT; | ||
|
||
let x0_floored = x0.floor(); | ||
let x1_floored = x1.floor(); | ||
|
||
let x_start = x0_floored as u16; | ||
// Inclusive, i.e. the pixel at column `x_end` is the very right border (possibly only anti-aliased) | ||
// of the rectangle, which should still be stripped. | ||
let x_end = (x1_floored as u16).min(width - 1); | ||
|
||
// Calculate the vertical/horizontal coverage of a pixel, using a start | ||
// and end point. The area between the start and end point is considered to be | ||
// covered by the shape. | ||
let pixel_coverage = |pixel_pos: u16, start: f32, end: f32| { | ||
let pixel_pos = pixel_pos as f32; | ||
let end = (end - pixel_pos).clamp(0.0, 1.0); | ||
let start = (start - pixel_pos).clamp(0.0, 1.0); | ||
|
||
end - start | ||
}; | ||
|
||
// Calculate the alpha coverages of the strips containing the top/bottom | ||
// borders of the rectangle. | ||
let vertical_alpha_coverage = |strip_y: u16| { | ||
let mut buf = [0.0_f32; Tile::HEIGHT as usize]; | ||
|
||
// For each row in the strip, calculate how much it is covered by given the | ||
// vertical endpoints y0 and y1. | ||
for i in 0..Tile::HEIGHT { | ||
buf[i as usize] = pixel_coverage(strip_y + i, y0, y1); | ||
} | ||
|
||
buf | ||
}; | ||
|
||
// Note that the alpha coverage of all pixels on either the left or ride side of a | ||
// rectangle is always the same (except for corners), so we just need to calculate | ||
// a single value. The coverage of corners will be calculated by adding an additional | ||
// opacity mask as calculated in `horizontal_alphas`. | ||
let left_alpha = pixel_coverage(x_start, x0, x1); | ||
let right_alpha = pixel_coverage(x_end, x0, x1); | ||
|
||
// Calculate the alpha coverages of a strip using an alpha mask. For example, if we | ||
// want to calculate the coverage of the very first column of the top line in the | ||
// rect (which might start at the horizontal offset .5), then we need to multiply | ||
// all its alpha values by 0.5 to account for anti-aliasing of the left edge. | ||
let push_alpha = |col_alphas: &[f32; 4], alpha_mask: f32, alpha_buffer: &mut Vec<u8>| { | ||
for alpha in col_alphas { | ||
let u8_alpha = ((*alpha * alpha_mask) * 255.0 + 0.5) as u8; | ||
alpha_buffer.push(u8_alpha); | ||
} | ||
}; | ||
|
||
// Create a strip for the top/bottom edge of the rectangle. | ||
let horizontal_strip = |alpha_buffer: &mut Vec<u8>, | ||
strip_buffer: &mut Vec<Strip>, | ||
col_alphas: &[f32; 4], | ||
strip_y: u16| { | ||
// Strip the first column, which might have an additional alpha mask due to non-integer | ||
// alignment of x0. If the rectangle is less than 1 pixel wide, this will represent | ||
// the total coverage of the rectangle inside the pixel. | ||
let alpha_idx = alpha_buffer.len() as u32; | ||
push_alpha(col_alphas, left_alpha, alpha_buffer); | ||
|
||
// If the rect covers more than one pixel horizontally, fill all the remaining ones | ||
// except for the last one with the same opacity as in `alphas`. | ||
// If the rect is contained within one pixel horizontally, | ||
// then right_alpha == left_alpha, and thus the alpha we pushed above is enough. | ||
if x_end - x_start >= 1 { | ||
for _ in (x_start + 1)..x_end { | ||
push_alpha(col_alphas, 1.0, alpha_buffer); | ||
} | ||
|
||
// Fill the last, right column, which might also need an additional alpha mask | ||
// due to non-integer alignment of x1. | ||
push_alpha(col_alphas, right_alpha, alpha_buffer); | ||
} | ||
|
||
// Push the actual strip. | ||
strip_buffer.push(Strip { | ||
x: x0_floored as u16, | ||
y: strip_y, | ||
alpha_idx, | ||
winding: 0, | ||
}); | ||
}; | ||
|
||
let top_alphas = vertical_alpha_coverage(top_strip_y); | ||
// Create the strip for the top part of the rectangle. | ||
horizontal_strip(alpha_buf, strip_buf, &top_alphas, top_strip_y); | ||
|
||
// If rect covers more than one strip vertically, we need to strip the vertical line | ||
// segments of the rectangle, and finally the bottom horizontal line segment. | ||
if top_strip_idx != bottom_strip_idx { | ||
let alphas = [1.0, 1.0, 1.0, 1.0]; | ||
LaurenzV marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Strip all parts that are inside the rectangle (i.e. neither the top nor the | ||
// bottom part. In this case, all pixels will have full opacity). | ||
for i in (top_strip_idx + 1)..bottom_strip_idx { | ||
// Left side (and right side if rect is only one pixel wide). | ||
let mut alpha_idx = alpha_buf.len() as u32; | ||
push_alpha(&alphas, left_alpha, alpha_buf); | ||
|
||
strip_buf.push(Strip { | ||
x: x0_floored as u16, | ||
y: i * Tile::HEIGHT, | ||
alpha_idx, | ||
winding: 0, | ||
}); | ||
|
||
if x_end > x_start { | ||
// Right side. | ||
alpha_idx = alpha_buf.len() as u32; | ||
push_alpha(&alphas, right_alpha, alpha_buf); | ||
|
||
strip_buf.push(Strip { | ||
x: x1_floored as u16, | ||
y: i * Tile::HEIGHT, | ||
alpha_idx, | ||
winding: 1, | ||
}); | ||
} | ||
} | ||
|
||
// Strip the bottom part of the rectangle. | ||
let bottom_alphas = vertical_alpha_coverage(bottom_strip_y); | ||
horizontal_strip(alpha_buf, strip_buf, &bottom_alphas, bottom_strip_y); | ||
} | ||
|
||
// Push sentinel strip. | ||
strip_buf.push(Strip { | ||
x: u16::MAX, | ||
y: bottom_strip_y, | ||
alpha_idx: alpha_buf.len() as u32, | ||
winding: 0, | ||
}); | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions
3
sparse_strips/vello_cpu/snapshots/filled_unaligned_rect_as_path.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.