vello_common: Store strip data in tile units#1379
Conversation
|
Thinking about it more closely, I think it would still be possible to support footprints. We could pack x and y into a single u32, but instead still store x in user units (to support footprints), but y coordinates in tile units. If we use 18 bits for x and only 14 bits for y, it still fits into a u32 but we can represent all the possible numbers for x and y! So as far as I'm concerned, there shouldn't be any downsides to making this change. |
| // TODO: Rewrite tests so we work with tile_x instead of x. | ||
|
|
There was a problem hiding this comment.
| // TODO: Rewrite tests so we work with tile_x instead of x. | |
| // TODO: Rewrite tests so we work with tile_x instead of x. |
There was a problem hiding this comment.
Or it's just an empty line 😅
| // TODO: Ensure that all of the calculations below are compatible with i16. | ||
|
|
There was a problem hiding this comment.
| // TODO: Ensure that all of the calculations below are compatible with i16. | |
| // TODO: Ensure that all of the calculations below are compatible with i16. |
| if self.is_sentinel() { | ||
| u16::MAX | ||
| } else { | ||
| self.tile_x * Tile::WIDTH |
There was a problem hiding this comment.
This could overflow for very large tile_x. I think it’s highly unlikely in practice, but would it make sense to add an assertion when creating strip? and also use saturating_mul?
If we do this, we may be able to eliminate the branch? If I understand correctly, is_sentinel is used for clipping directly on strips.
pub fn x(&self) -> u16 {
// Callers should check is_sentinel() before calling x() on sentinel strips
debug_assert!(!self.is_sentinel(), "x() called on sentinel strip");
self.tile_x * Tile::WIDTH
}There was a problem hiding this comment.
I don't believe it should be possible to overflow, because we always do x / Tile::WIDTH, so a flooring division. But I agree using saturating makes sense here, just to be safe. I just thought having it explicitly might help us detect unexpected bugs.
I also had this debug assertion in the beginning, but it seems like some of the code relies on being able to call this for sentinel tiles, hence why I didn't add the debug assertion in the end.
Co-authored-by: Alex Gemberg <gemberg@canva.com>
We weren't able to get to it in discussion, but Laurenz has graciously taken the lead on the My existing changes on https://github.com/linebender/vello/tree/thomas/eyesixteen can be scrapped, as they are mostly exploratory. Notably, they also don't conform to the new spec Laurenz is using above, where the tile position is in "tile units." |

Context
In the near future, we want to be able to support negative coordinates. This applies to two different areas:
In order to do this, we need to eventually migrate from using
u16toi16in most places, as has been discussed in office hours.Problem
Putting aside the complications that come from dealing with negative coordinates, a more immediate problem is that in the current implementation, this would reduce the maximum drawing area that can be used. Currently, we support a dimension of 65535x65535 (i.e. u16::MAX) in each direction, but after migrating to i16, this would be reduced to half of that, so around 32k pixels. While not dramatic, I do think it's unfortunate, especially because I think we can avoid this problem by doing some preparatory work. Therefore, before we start migrating to using i16, I would propose restructuring the current code in a way that will make the migration easier. This will also allow us to split it into multiple PRs that are easier to review.
Solution
Basically, in order to alleviate this problem, we can make use of the fact that our tiles always have a width/height of 4 (in the future, this might increase, but as long as we don't decrease the tile size this will not matter). Since this is true, inside of
Strip, we can store the x/y coordinates in tile units instead of pixel coordinates, i.e. divided by 4. We actually already do this inTile, but not inStripyet. By doing so, even if we switch to usingi16later on, we can still store the maximum coordinatesu16::MAX/4inside of that, meaning that we can still represent the whole 65535x65535 pixel range (yay)!PRs
Therefore, I am planning on opening the following PRs:
WideTileso that it won't overflow once we switch to i16.While not strictly related, I noticed that we can do the same for alpha index: Instead of storing
alpha_buf.len()as the offset, we can instead storealpha_buf.len() / (Tile::WIDTH * Tile::HEIGHT), since it's guaranteed that each tile always has 16 alpha values. So that could be a fourth PR.Downside
The only downside that I can see is that if we go down this route, it will not be possible anymore to introduce the concept of footprints, since all of this is under the assumption that we are always properly aligned. But my understanding was that we weren't planning on re-introducing that, anyway.