Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  vert_buffer.rs

  Sprache: Rust
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


//! Quantized raster-space vertex buffer for output-space tile invalidation.
//!
//! Each primitive and clip gets its transformed, raster-space corners stored
//! here as quantized i32 values. The tile descriptor stores a VertRange
//! referencing into this buffer instead of a picture-space prim_clip_box or
//! spatial node dependency.

use api::units::*;
use crate::spatial_tree::{SpatialTree, SpatialNodeIndex, CoordinateSpaceMapping};
use crate::util::{MatrixHelpers, ScaleOffset};

/// Sub-pixel quantization scale: quarter-pixel precision.
pub const VERT_QUANTIZE_SCALE: f32 = 4.0;

pub fn quantize(v: f32) -> i32 {
    (v * VERT_QUANTIZE_SCALE).round() as i32
}

/// A reference into a per-tile vert_data buffer: offset (in i32 elements) and count.
/// count is 4 for an axis-aligned rect (2 corners × 2 coords), 8 for a
/// non-axis-aligned quad (4 corners × 2 coords), or 16 for a transform fingerprint
/// emitted when a perspective-projected rect crosses the camera plane (4 corners ×
/// homogeneous (x, y, z, w) coords).
#[derive(Copy, Clone, Debug, Default, PartialEq, peek_poke::PeekPoke)]
#[cfg_attr(feature = "capture", derive(serde::Serialize))]
#[cfg_attr(feature = "replay", derive(serde::Deserialize))]
pub struct VertRange {
    pub offset: u32,
    pub count: u32,
}

impl VertRange {
    pub const INVALID: VertRange = VertRange { offset: 0, count: 0 };

    pub fn is_valid(self) -> bool {
        self.count > 0
    }
}

/// Persistent per-tile-cache scratch and transform cache for computing
/// raster-space corners.
///
/// Lives on TileCacheInstance and provides two optimisations:
///
/// 1. **Amortised unquantized scratch**: `unquantized` is never dropped between
///    frames, so the heap allocation is paid once after warmup.
///
/// 2. **Spatial-node transform cache**: the relative transform from
///    `prim_spatial_node` → `tile_cache_spatial_node` is cached so that
///    consecutive primitives in the same scroll frame avoid repeated
///    `get_relative_transform` calls.
pub struct CornersCache {
    /// Amortised scratch for unquantized corners.
    /// Cleared once before computing prim + coverage + clips for each primitive.
    unquantized: Vec<RasterPoint>,

    /// The primitive spatial node for which `cached_mapping` was computed.
    /// `None` means the cache is cold (reset at frame start).
    cached_node: Option<SpatialNodeIndex>,

    /// Cached mapping for `cached_node`. Valid only when
    /// `cached_node == Some(current prim_spatial_node)`.
    cached_mapping: CoordinateSpaceMapping<LayoutPixel, LayoutPixel>,
}

impl CornersCache {
    pub fn new() -> Self {
        CornersCache {
            unquantized: Vec::new(),
            cached_node: None,
            cached_mapping: CoordinateSpaceMapping::Local,
        }
    }

    /// Reset the transform cache. Call once at the start of each frame's
    /// dependency update, before any primitives are processed.
    pub fn pre_update(&mut self) {
        self.cached_node = None;
    }

    /// Clear the unquantized scratch. Call once before computing corners for a
    /// single primitive (before prim rect, coverage rect and all clips).
    pub fn clear_scratch(&mut self) {
        self.unquantized.clear();
    }

    /// Compute unquantized raster-space corners for `local_rect` and append
    /// them to the scratch buffer. Returns a VertRange into the scratch, or
    /// VertRange::INVALID if the transform is non-invertible.
    ///
    /// The relative transform for `prim_spatial_node` is cached across calls:
    /// if the same node is passed as the previous call, `get_relative_transform`
    /// is not recomputed.
    pub fn compute_to_scratch(
        &mut self,
        local_rect: LayoutRect,
        prim_spatial_node: SpatialNodeIndex,
        tile_cache_spatial_node: SpatialNodeIndex,
        local_to_raster: ScaleOffset,
        spatial_tree: &SpatialTree,
    ) -> VertRange {
        if Some(prim_spatial_node) != self.cached_node {
            let mapping = spatial_tree.get_relative_transform(
                prim_spatial_node,
                tile_cache_spatial_node,
            );
            self.cached_mapping = match mapping {
                CoordinateSpaceMapping::ScaleOffset(ref so) if so.is_reflection() => {
                    CoordinateSpaceMapping::Transform(so.to_transform())
                }
                other => other,
            };
            self.cached_node = Some(prim_spatial_node);
        }
        self.append_corners_from_mapping(local_rect, local_to_raster)
    }

    fn append_corners_from_mapping(
        &mut self,
        local_rect: LayoutRect,
        local_to_raster: ScaleOffset,
    ) -> VertRange {
        match &self.cached_mapping {
            CoordinateSpaceMapping::Local => {
                let r: RasterRect = local_to_raster.map_rect(&local_rect);
                let offset = self.unquantized.len() as u32;
                self.unquantized.push(r.min);
                self.unquantized.push(r.max);
                VertRange { offset, count: 2 }
            }
            CoordinateSpaceMapping::ScaleOffset(so) => {
                let r: RasterRect = so.then(&local_to_raster).map_rect(&local_rect);
                let offset = self.unquantized.len() as u32;
                self.unquantized.push(r.min);
                self.unquantized.push(r.max);
                VertRange { offset, count: 2 }
            }
            CoordinateSpaceMapping::Transform(m) => {
                let raster_m = m.then(&local_to_raster.to_transform::<LayoutPixel, RasterPixel>());
                let src = [
                    local_rect.min,
                    LayoutPoint::new(local_rect.max.x, local_rect.min.y),
                    LayoutPoint::new(local_rect.min.x, local_rect.max.y),
                    local_rect.max,
                ];
                let offset = self.unquantized.len() as u32;

                // Fast path: no perspective component. transform_point2d can never
                // fail for one corner while succeeding for another, so we don't need
                // homogeneous coords or a fingerprint fallback.
                if !raster_m.has_perspective_component() {
                    for p in &src {
                        match raster_m.transform_point2d(*p) {
                            Some(pt) => self.unquantized.push(pt),
                            None => {
                                self.unquantized.truncate(offset as usize);
                                return VertRange::INVALID;
                            }
                        }
                    }
                    return VertRange { offset, count: 4 };
                }

                // Perspective transform: compute homogeneous coords so we can
                // distinguish "all corners in front of camera" (project them) from
                // "rect crosses the camera plane" (push a stable fingerprint).
                let homogens = [
                    raster_m.transform_point2d_homogeneous(src[0]),
                    raster_m.transform_point2d_homogeneous(src[1]),
                    raster_m.transform_point2d_homogeneous(src[2]),
                    raster_m.transform_point2d_homogeneous(src[3]),
                ];
                if homogens.iter().all(|h| h.w > 0.0) {
                    for h in &homogens {
                        self.unquantized.push(RasterPoint::new(h.x / h.w, h.y / h.w));
                    }
                    VertRange { offset, count: 4 }
                } else {
                    // At least one corner is at or behind the camera plane and can't be
                    // projected to a finite 2D raster point. Falling back to INVALID would
                    // make compare_prim see equal empty slices on every frame, silently
                    // hiding transform animations (bug 2036730). Instead, push the
                    // homogeneous (x, y, z, w) of each corner as a stable per-transform
                    // fingerprint: equal across frames when the transform is unchanged
                    // (no over-invalidation), but different when the transform animates
                    // (correct invalidation). Two RasterPoints encode each corner.
                    for h in &homogens {
                        self.unquantized.push(RasterPoint::new(h.x, h.y));
                        self.unquantized.push(RasterPoint::new(h.z, h.w));
                    }
                    VertRange { offset, count: 8 }
                }
            }
        }
    }

    /// Quantize corners at `scratch_range` from the scratch buffer into `dst`.
    /// Returns a VertRange into `dst`, or INVALID if `scratch_range` is invalid.
    pub fn push_verts(&self, scratch_range: VertRange, dst: &le='color:red'>mut Vec<i32>) -> VertRange {
        if !scratch_range.is_valid() {
            return VertRange::INVALID;
        }
        let start = scratch_range.offset as usize;
        let end = (scratch_range.offset + scratch_range.count) as usize;
        let corners = &self.unquantized[start..end];
        debug_assert!(corners.len() == 2 || corners.len() == 4 || corners.len() == 8);
        let offset = dst.len() as u32;
        for p in corners {
            dst.push(quantize(p.x));
            dst.push(quantize(p.y));
        }
        VertRange { offset, count: (corners.len() * 2as u32 }
    }

    /// Quantize corners at `scratch_range` into `dst`, clamping to `tile_rect`.
    /// Returns a VertRange into `dst`, or INVALID if `scratch_range` is invalid.
    pub fn push_verts_clamped(
        &self,
        scratch_range: VertRange,
        tile_rect: &RasterRect,
        dst: &mut Vec<i32>,
    ) -> VertRange {
        if !scratch_range.is_valid() {
            return VertRange::INVALID;
        }
        let start = scratch_range.offset as usize;
        let end = (scratch_range.offset + scratch_range.count) as usize;
        let corners = &self.unquantized[start..end];
        debug_assert!(corners.len() == 2 || corners.len() == 4 || corners.len() == 8);
        let offset = dst.len() as u32;
        if corners.len() == 8 {
            // Transform fingerprint (homogeneous coords for a perspective-crossing rect).
            // Clamping these to tile bounds would corrupt the fingerprint, so skip the clamp.
            for p in corners {
                dst.push(quantize(p.x));
                dst.push(quantize(p.y));
            }
        } else {
            for p in corners {
                dst.push(quantize(p.x.max(tile_rect.min.x).min(tile_rect.max.x)));
                dst.push(quantize(p.y.max(tile_rect.min.y).min(tile_rect.max.y)));
            }
        }
        VertRange { offset, count: (corners.len() * 2as u32 }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use api::units::{LayoutPixel, LayoutPoint, LayoutRect, LayoutTransform};
    use euclid::Angle;

    /// Build a perspective(d) * rotateX(deg) * translate(0, ty, 0) row-vector matrix.
    /// Mirrors the CSS-style transform in bug 2036730's repro: a tall rect rotated
    /// around its top edge so the bottom corners fall past the perspective camera
    /// plane.
    fn perspective_rotate_x_translate_y(deg: f32, d: f32, ty: f32) -> LayoutTransform {
        let translate = LayoutTransform::translation(0.0, ty, 0.0);
        let rotate = LayoutTransform::rotation(1.00.00.0, Angle::degrees(deg));
        let mut perspective = LayoutTransform::identity();
        perspective.m34 = -1.0 / d;
        translate.then(&rotate).then(&perspective)
    }

    /// Bug 2036730 regression test. When a perspective-projected rect has corners
    /// with w <= 0 (the rect crosses the camera plane), compute_to_scratch must
    /// emit a stable per-transform fingerprint instead of returning INVALID. Two
    /// frames of an animated transform must therefore produce different scratch
    /// contents so downstream tile invalidation detects the change. Pre-fix,
    /// both frames returned VertRange::INVALID and compare_prim saw equal empty
    /// slices — silently bypassing invalidation.
    #[test]
    fn perspective_camera_plane_fingerprint_differs_per_transform() {
        // 200 x 2000 rect rotated 80deg around the top edge. With perspective
        // distance 1000, the bottom corners reach z ≈ 2000*sin(80°) ≈ 1969,
        // which is past the camera and gives w ≈ -0.97.
        let local_rect = LayoutRect::new(
            LayoutPoint::new(0.00.0),
            LayoutPoint::new(200.02000.0),
        );
        let local_to_raster = ScaleOffset::identity();

        let mut cache = CornersCache::new();

        cache.cached_mapping = CoordinateSpaceMapping::Transform(
            perspective_rotate_x_translate_y(80.01000.00.0),
        );
        cache.clear_scratch();
        let r1 = cache.append_corners_from_mapping(local_rect, local_to_raster);
        assert!(r1.is_valid(), "fingerprint must not collapse to INVALID");
        assert_eq!(r1.count, 8"fingerprint encodes 4 corners as 8 RasterPoints");
        let scratch1: Vec<RasterPoint> = cache.unquantized.clone();

        cache.cached_mapping = CoordinateSpaceMapping::Transform(
            perspective_rotate_x_translate_y(80.01000.0, -20.0),
        );
        cache.clear_scratch();
        let r2 = cache.append_corners_from_mapping(local_rect, local_to_raster);
        assert_eq!(r2.count, 8);
        let scratch2: Vec<RasterPoint> = cache.unquantized.clone();

        assert_ne!(
            scratch1, scratch2,
            "different perspective transforms must produce different fingerprints",
        );
    }

    /// The same transform applied twice must produce the same fingerprint, so a
    /// static perspective-crossing primitive does not trip spurious invalidations.
    #[test]
    fn perspective_camera_plane_fingerprint_stable_for_unchanged_transform() {
        let local_rect = LayoutRect::new(
            LayoutPoint::new(0.00.0),
            LayoutPoint::new(200.02000.0),
        );
        let local_to_raster = ScaleOffset::identity();

        let mut cache = CornersCache::new();

        let m = perspective_rotate_x_translate_y(80.01000.0, -40.0);

        cache.cached_mapping = CoordinateSpaceMapping::Transform(m);
        cache.clear_scratch();
        let _ = cache.append_corners_from_mapping(local_rect, local_to_raster);
        let scratch1: Vec<RasterPoint> = cache.unquantized.clone();

        cache.cached_mapping = CoordinateSpaceMapping::Transform(m);
        cache.clear_scratch();
        let _ = cache.append_corners_from_mapping(local_rect, local_to_raster);
        let scratch2: Vec<RasterPoint> = cache.unquantized.clone();

        assert_eq!(
            scratch1, scratch2,
            "the same perspective transform must produce identical fingerprints",
        );
    }

    /// A transform without a perspective component (rotate only) must take the
    /// fast path and emit 4 projected corners, not the 8-element fingerprint.
    #[test]
    fn no_perspective_uses_projected_corners() {
        let local_rect = LayoutRect::new(
            LayoutPoint::new(0.00.0),
            LayoutPoint::new(100.0100.0),
        );
        let local_to_raster = ScaleOffset::identity();

        let mut cache = CornersCache::new();
        // Pure rotation around X axis — no perspective component (m14, m24, m34 = 0
        // and m44 = 1), so the fast-path projection should be used.
        cache.cached_mapping = CoordinateSpaceMapping::<LayoutPixel, LayoutPixel>::Transform(
            LayoutTransform::rotation(1.00.00.0, Angle::degrees(45.0)),
        );
        cache.clear_scratch();
        let r = cache.append_corners_from_mapping(local_rect, local_to_raster);
        assert_eq!(r.count, 4"non-perspective transform must emit 4 corners");
    }
}

Messung V0.5 in Prozent
C=78 H=100 G=89

¤ Dauer der Verarbeitung: 0.11 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=752002