1use api::units::*;
13use crate::spatial_tree::{SpatialTree, SpatialNodeIndex, CoordinateSpaceMapping};
14use crate::util::{MatrixHelpers, ScaleOffset};
15
16pub const VERT_QUANTIZE_SCALE: f32 = 4.0;
18
19pub fn quantize(v: f32) -> i32 {
20 (v * VERT_QUANTIZE_SCALE).round() as i32
21}
22
23#[derive(Copy, Clone, Debug, Default, PartialEq, peek_poke::PeekPoke)]
29#[cfg_attr(feature = "capture", derive(serde::Serialize))]
30#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
31pub struct VertRange {
32 pub offset: u32,
33 pub count: u32,
34}
35
36impl VertRange {
37 pub const INVALID: VertRange = VertRange { offset: 0, count: 0 };
38
39 pub fn is_valid(self) -> bool {
40 self.count > 0
41 }
42}
43
44pub struct CornersCache {
57 unquantized: Vec<RasterPoint>,
60
61 cached_node: Option<SpatialNodeIndex>,
64
65 cached_mapping: CoordinateSpaceMapping<LayoutPixel, LayoutPixel>,
68}
69
70impl CornersCache {
71 pub fn new() -> Self {
72 CornersCache {
73 unquantized: Vec::new(),
74 cached_node: None,
75 cached_mapping: CoordinateSpaceMapping::Local,
76 }
77 }
78
79 pub fn pre_update(&mut self) {
82 self.cached_node = None;
83 }
84
85 pub fn clear_scratch(&mut self) {
88 self.unquantized.clear();
89 }
90
91 pub fn compute_to_scratch(
99 &mut self,
100 local_rect: LayoutRect,
101 prim_spatial_node: SpatialNodeIndex,
102 tile_cache_spatial_node: SpatialNodeIndex,
103 local_to_raster: ScaleOffset,
104 spatial_tree: &SpatialTree,
105 ) -> VertRange {
106 if Some(prim_spatial_node) != self.cached_node {
107 let mapping = spatial_tree.get_relative_transform(
108 prim_spatial_node,
109 tile_cache_spatial_node,
110 );
111 self.cached_mapping = match mapping {
112 CoordinateSpaceMapping::ScaleOffset(ref so) if so.is_reflection() => {
113 CoordinateSpaceMapping::Transform(so.to_transform())
114 }
115 other => other,
116 };
117 self.cached_node = Some(prim_spatial_node);
118 }
119 self.append_corners_from_mapping(local_rect, local_to_raster)
120 }
121
122 fn append_corners_from_mapping(
123 &mut self,
124 local_rect: LayoutRect,
125 local_to_raster: ScaleOffset,
126 ) -> VertRange {
127 match &self.cached_mapping {
128 CoordinateSpaceMapping::Local => {
129 let r: RasterRect = local_to_raster.map_rect(&local_rect);
130 let offset = self.unquantized.len() as u32;
131 self.unquantized.push(r.min);
132 self.unquantized.push(r.max);
133 VertRange { offset, count: 2 }
134 }
135 CoordinateSpaceMapping::ScaleOffset(so) => {
136 let r: RasterRect = so.then(&local_to_raster).map_rect(&local_rect);
137 let offset = self.unquantized.len() as u32;
138 self.unquantized.push(r.min);
139 self.unquantized.push(r.max);
140 VertRange { offset, count: 2 }
141 }
142 CoordinateSpaceMapping::Transform(m) => {
143 let raster_m = m.then(&local_to_raster.to_transform::<LayoutPixel, RasterPixel>());
144 let src = [
145 local_rect.min,
146 LayoutPoint::new(local_rect.max.x, local_rect.min.y),
147 LayoutPoint::new(local_rect.min.x, local_rect.max.y),
148 local_rect.max,
149 ];
150 let offset = self.unquantized.len() as u32;
151
152 if !raster_m.has_perspective_component() {
156 for p in &src {
157 match raster_m.transform_point2d(*p) {
158 Some(pt) => self.unquantized.push(pt),
159 None => {
160 self.unquantized.truncate(offset as usize);
161 return VertRange::INVALID;
162 }
163 }
164 }
165 return VertRange { offset, count: 4 };
166 }
167
168 let homogens = [
172 raster_m.transform_point2d_homogeneous(src[0]),
173 raster_m.transform_point2d_homogeneous(src[1]),
174 raster_m.transform_point2d_homogeneous(src[2]),
175 raster_m.transform_point2d_homogeneous(src[3]),
176 ];
177 if homogens.iter().all(|h| h.w > 0.0) {
178 for h in &homogens {
179 self.unquantized.push(RasterPoint::new(h.x / h.w, h.y / h.w));
180 }
181 VertRange { offset, count: 4 }
182 } else {
183 for h in &homogens {
192 self.unquantized.push(RasterPoint::new(h.x, h.y));
193 self.unquantized.push(RasterPoint::new(h.z, h.w));
194 }
195 VertRange { offset, count: 8 }
196 }
197 }
198 }
199 }
200
201 pub fn push_verts(&self, scratch_range: VertRange, dst: &mut Vec<i32>) -> VertRange {
204 if !scratch_range.is_valid() {
205 return VertRange::INVALID;
206 }
207 let start = scratch_range.offset as usize;
208 let end = (scratch_range.offset + scratch_range.count) as usize;
209 let corners = &self.unquantized[start..end];
210 debug_assert!(corners.len() == 2 || corners.len() == 4 || corners.len() == 8);
211 let offset = dst.len() as u32;
212 for p in corners {
213 dst.push(quantize(p.x));
214 dst.push(quantize(p.y));
215 }
216 VertRange { offset, count: (corners.len() * 2) as u32 }
217 }
218
219 pub fn push_verts_clamped(
222 &self,
223 scratch_range: VertRange,
224 tile_rect: &RasterRect,
225 dst: &mut Vec<i32>,
226 ) -> VertRange {
227 if !scratch_range.is_valid() {
228 return VertRange::INVALID;
229 }
230 let start = scratch_range.offset as usize;
231 let end = (scratch_range.offset + scratch_range.count) as usize;
232 let corners = &self.unquantized[start..end];
233 debug_assert!(corners.len() == 2 || corners.len() == 4 || corners.len() == 8);
234 let offset = dst.len() as u32;
235 if corners.len() == 8 {
236 for p in corners {
239 dst.push(quantize(p.x));
240 dst.push(quantize(p.y));
241 }
242 } else {
243 for p in corners {
244 dst.push(quantize(p.x.max(tile_rect.min.x).min(tile_rect.max.x)));
245 dst.push(quantize(p.y.max(tile_rect.min.y).min(tile_rect.max.y)));
246 }
247 }
248 VertRange { offset, count: (corners.len() * 2) as u32 }
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use api::units::{LayoutPixel, LayoutPoint, LayoutRect, LayoutTransform};
256 use euclid::Angle;
257
258 fn perspective_rotate_x_translate_y(deg: f32, d: f32, ty: f32) -> LayoutTransform {
263 let translate = LayoutTransform::translation(0.0, ty, 0.0);
264 let rotate = LayoutTransform::rotation(1.0, 0.0, 0.0, Angle::degrees(deg));
265 let mut perspective = LayoutTransform::identity();
266 perspective.m34 = -1.0 / d;
267 translate.then(&rotate).then(&perspective)
268 }
269
270 #[test]
278 fn perspective_camera_plane_fingerprint_differs_per_transform() {
279 let local_rect = LayoutRect::new(
283 LayoutPoint::new(0.0, 0.0),
284 LayoutPoint::new(200.0, 2000.0),
285 );
286 let local_to_raster = ScaleOffset::identity();
287
288 let mut cache = CornersCache::new();
289
290 cache.cached_mapping = CoordinateSpaceMapping::Transform(
291 perspective_rotate_x_translate_y(80.0, 1000.0, 0.0),
292 );
293 cache.clear_scratch();
294 let r1 = cache.append_corners_from_mapping(local_rect, local_to_raster);
295 assert!(r1.is_valid(), "fingerprint must not collapse to INVALID");
296 assert_eq!(r1.count, 8, "fingerprint encodes 4 corners as 8 RasterPoints");
297 let scratch1: Vec<RasterPoint> = cache.unquantized.clone();
298
299 cache.cached_mapping = CoordinateSpaceMapping::Transform(
300 perspective_rotate_x_translate_y(80.0, 1000.0, -20.0),
301 );
302 cache.clear_scratch();
303 let r2 = cache.append_corners_from_mapping(local_rect, local_to_raster);
304 assert_eq!(r2.count, 8);
305 let scratch2: Vec<RasterPoint> = cache.unquantized.clone();
306
307 assert_ne!(
308 scratch1, scratch2,
309 "different perspective transforms must produce different fingerprints",
310 );
311 }
312
313 #[test]
316 fn perspective_camera_plane_fingerprint_stable_for_unchanged_transform() {
317 let local_rect = LayoutRect::new(
318 LayoutPoint::new(0.0, 0.0),
319 LayoutPoint::new(200.0, 2000.0),
320 );
321 let local_to_raster = ScaleOffset::identity();
322
323 let mut cache = CornersCache::new();
324
325 let m = perspective_rotate_x_translate_y(80.0, 1000.0, -40.0);
326
327 cache.cached_mapping = CoordinateSpaceMapping::Transform(m);
328 cache.clear_scratch();
329 let _ = cache.append_corners_from_mapping(local_rect, local_to_raster);
330 let scratch1: Vec<RasterPoint> = cache.unquantized.clone();
331
332 cache.cached_mapping = CoordinateSpaceMapping::Transform(m);
333 cache.clear_scratch();
334 let _ = cache.append_corners_from_mapping(local_rect, local_to_raster);
335 let scratch2: Vec<RasterPoint> = cache.unquantized.clone();
336
337 assert_eq!(
338 scratch1, scratch2,
339 "the same perspective transform must produce identical fingerprints",
340 );
341 }
342
343 #[test]
346 fn no_perspective_uses_projected_corners() {
347 let local_rect = LayoutRect::new(
348 LayoutPoint::new(0.0, 0.0),
349 LayoutPoint::new(100.0, 100.0),
350 );
351 let local_to_raster = ScaleOffset::identity();
352
353 let mut cache = CornersCache::new();
354 cache.cached_mapping = CoordinateSpaceMapping::<LayoutPixel, LayoutPixel>::Transform(
357 LayoutTransform::rotation(1.0, 0.0, 0.0, Angle::degrees(45.0)),
358 );
359 cache.clear_scratch();
360 let r = cache.append_corners_from_mapping(local_rect, local_to_raster);
361 assert_eq!(r.count, 4, "non-perspective transform must emit 4 corners");
362 }
363}