webrender 0.69.0

A GPU accelerated 2D renderer for web content
Documentation
use api::units::{LayoutToPictureTransform, PicturePixel, PictureToLayoutTransform};
use crate::{FastHashMap, frame_allocator::FrameMemory, gpu_types::VECS_PER_TRANSFORM};
use crate::internal_types::FrameVec;
use crate::spatial_tree::{SpatialNodeIndex, SpatialTree};
use crate::util::{TransformedRectKind, MatrixHelpers};

/* 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/. */

/// Represents the information about a transform palette
/// entry that is passed to shaders. It includes an index
/// into the transform palette, and a set of flags.
#[derive(Copy, Clone, PartialEq, MallocSizeOf)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[repr(C)]
pub struct GpuTransformId(pub u32);

impl GpuTransformId {
    /// Identity transform ID.
    pub const IDENTITY: Self = GpuTransformId(0);
    const INDEX_MASK: u32 = 0x003fffff;

    // Note: we use unset bits instead of set bits to denote certain
    // properties of the transform so that the identity transform id
    // remains zero.

    /// if *not* set, the transform is axis-aligned.
    const AXIS_ALIGNED_2D_BIT: u32 = 1 << 23;
    /// If *not* set, the transform can be represented as a 2d scale + offset.
    const SCALE_OFFSET_2D_BIT: u32 = 1 << 22;

    /// Extract the transform kind from the id.
    pub fn transform_kind(&self) -> TransformedRectKind {
        if (self.0 & Self::AXIS_ALIGNED_2D_BIT) == 0 {
            TransformedRectKind::AxisAligned
        } else {
            TransformedRectKind::Complex
        }
    }

    /// Note: There are transformations that preserve axis-alignment without
    /// being scale + offsets.
    pub fn is_2d_axis_aligned(&self) -> bool {
        self.0 & Self::AXIS_ALIGNED_2D_BIT == 0
    }

    /// Returns true if the transform can be represented by a 2d scale + offset.
    pub fn is_2d_scale_offset(&self) -> bool {
        self.0 & Self::SCALE_OFFSET_2D_BIT == 0
    }

    pub fn metadata(&self) -> TransformMetadata {
        TransformMetadata {
            is_2d_axis_aligned: self.is_2d_axis_aligned(),
            is_2d_scale_offset: self.is_2d_scale_offset(),
        }
    }

    /// Override the kind of transform stored in this id. This can be useful in
    /// cases where we don't want shaders to consider certain transforms axis-
    /// aligned (i.e. perspective warp) even though we may still want to for the
    /// general case.
    pub fn override_transform_kind(&self, kind: TransformedRectKind) -> Self {
        GpuTransformId((self.0 & (1 << 23)) | ((kind as u32) << 23))
    }
}

impl std::fmt::Debug for GpuTransformId {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        if *self == Self::IDENTITY {
            write!(f, "<identity>")
        } else {
            let index = self.0 & Self::INDEX_MASK;
            write!(f, "#{index}")?;
            let flag_bits = Self::AXIS_ALIGNED_2D_BIT | Self::SCALE_OFFSET_2D_BIT;
            if self.0 & flag_bits != flag_bits {
                let axis_aligned = if self.is_2d_axis_aligned() { "axis-aligned" } else { "" };
                let scale_offset = if self.is_2d_scale_offset() { "scale-offset" } else { "" };
                write!(f, "({axis_aligned} {scale_offset})")?;
            }
            Ok(())
        }
    }
}


/// The GPU data payload for a transform palette entry.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[repr(C)]
pub struct TransformData {
    transform: LayoutToPictureTransform,
    inv_transform: PictureToLayoutTransform,
}

impl TransformData {
    fn invalid() -> Self {
        TransformData {
            transform: LayoutToPictureTransform::identity(),
            inv_transform: PictureToLayoutTransform::identity(),
        }
    }
}

// Extra data stored about each transform palette entry.
#[derive(Copy, Clone)]
pub struct TransformMetadata {
    pub is_2d_axis_aligned: bool,
    pub is_2d_scale_offset: bool,
}

impl TransformMetadata {
    pub fn invalid() -> Self {
        TransformMetadata {
            is_2d_axis_aligned: true,
            is_2d_scale_offset: true,
        }
    }

    pub fn flags(&self) -> u32 {
        let mut flags = 0;
        if !self.is_2d_axis_aligned {
            flags |= GpuTransformId::AXIS_ALIGNED_2D_BIT
        };
        if !self.is_2d_scale_offset {
            flags |= GpuTransformId::SCALE_OFFSET_2D_BIT
        };

        flags
    }
}

#[derive(Debug, Hash, Eq, PartialEq)]
struct RelativeTransformKey {
    from_index: SpatialNodeIndex,
    to_index: SpatialNodeIndex,
    scale: u32,
    pre_scale: bool,
}

pub struct TransformPalette {
    pub gpu: GpuTransforms,
}

impl TransformPalette {
    pub fn new(
        count: usize,
        memory: &FrameMemory,
    ) -> Self {
        TransformPalette {
            gpu: GpuTransforms::new(count, memory),
        }
    }

    pub fn finish(self) -> FrameVec<TransformData> {
        self.gpu.finish()
    }
}

// Stores a contiguous list of TransformData structs, that
// are ready for upload to the GPU.
// TODO(gw): For now, this only stores the complete local
//           to world transform for each spatial node. In
//           the future, the transform palette will support
//           specifying a coordinate system that the transform
//           should be relative to.
pub struct GpuTransforms {
    transforms: FrameVec<TransformData>,
    metadata: Vec<TransformMetadata>,
    map: FastHashMap<RelativeTransformKey, usize>,
}

impl GpuTransforms {
    fn new(
        count: usize,
        memory: &FrameMemory,
    ) -> Self {
        let _ = VECS_PER_TRANSFORM;

        let mut transforms = memory.new_vec_with_capacity(count);
        let mut metadata = Vec::with_capacity(count);

        transforms.push(TransformData::invalid());
        metadata.push(TransformMetadata::invalid());

        GpuTransforms {
            transforms,
            metadata,
            map: FastHashMap::default(),
        }
    }

    fn finish(self) -> FrameVec<TransformData> {
        self.transforms
    }

    fn get_index(
        &mut self,
        child_index: SpatialNodeIndex,
        parent_index: SpatialNodeIndex,
        mut scale: Option<f32>,
        pre_scale: bool,
        spatial_tree: &SpatialTree,
    ) -> usize {
        if scale == Some(1.0) {
            scale = None;
        }

        // Deduplicate the common case of identity transforms.
        if child_index == parent_index && scale.is_none() {
            return 0;
        }

        let scale_key = scale.map(|s| s.to_bits()).unwrap_or(0);

        let key = RelativeTransformKey {
            from_index: child_index,
            to_index: parent_index,
            scale: scale_key,
            pre_scale,
        };

        let metadata = &mut self.metadata;
        let transforms = &mut self.transforms;

        *self.map.entry(key).or_insert_with(|| {
            let transform = spatial_tree.get_relative_transform(
                child_index,
                parent_index,
            );

            let is_2d_axis_aligned = transform.is_2d_axis_aligned();
            let is_2d_scale_offset  = transform.is_2d_scale_translation();

            let transform = transform
                .into_transform()
                .with_destination::<PicturePixel>();

            register_gpu_transform(
                metadata,
                transforms,
                transform,
                scale,
                pre_scale,
                TransformMetadata {
                    is_2d_axis_aligned,
                    is_2d_scale_offset,
                }
            )
        })
    }

    // Get a transform palette id for the given spatial node.
    // TODO(gw): In the future, it will be possible to specify
    //           a coordinate system id here, to allow retrieving
    //           transforms in the local space of a given spatial node.
    pub fn get_id(
        &mut self,
        from_index: SpatialNodeIndex,
        to_index: SpatialNodeIndex,
        spatial_tree: &SpatialTree,
    ) -> GpuTransformId {
        let index = self.get_index(
            from_index,
            to_index,
            None,
            false,
            spatial_tree,
        );

        let flags = self.metadata[index].flags();

        GpuTransformId((index as u32) | flags)
    }

    pub fn get_id_with_post_scale(
        &mut self,
        from_index: SpatialNodeIndex,
        to_index: SpatialNodeIndex,
        scale: f32,
        spatial_tree: &SpatialTree,
    ) -> GpuTransformId {
        let index = self.get_index(
            from_index,
            to_index,
            Some(scale),
            false,
            spatial_tree,
        );

        let flags = self.metadata[index].flags();

        GpuTransformId((index as u32) | flags)
    }

    pub fn get_id_with_pre_scale(
        &mut self,
        scale: f32,
        from_index: SpatialNodeIndex,
        to_index: SpatialNodeIndex,
        spatial_tree: &SpatialTree,
    ) -> GpuTransformId {
        let index = self.get_index(
            from_index,
            to_index,
            Some(scale),
            true,
            spatial_tree,
        );

        let flags = self.metadata[index].flags();

        GpuTransformId((index as u32) | flags)
    }

    pub fn get_custom(
        &mut self,
        transform: LayoutToPictureTransform,
    ) -> GpuTransformId {
        let is_2d_scale_offset = transform.is_2d_scale_translation();
        let is_axis_aligned = transform.preserves_2d_axis_alignment();
        let metadata = TransformMetadata {
            is_2d_scale_offset,
            is_2d_axis_aligned: is_axis_aligned,
        };
        let index = register_gpu_transform(
            &mut self.metadata,
            &mut self.transforms,
            transform,
            None,
            false,
            metadata,
        );

        GpuTransformId((index as u32) | metadata.flags())
    }
}

// Set the local -> world transform for a given spatial
// node in the transform palette.
fn register_gpu_transform(
    metadatas: &mut Vec<TransformMetadata>,
    transforms: &mut FrameVec<TransformData>,
    mut transform: LayoutToPictureTransform,
    scale: Option<f32>,
    pre_scale: bool,
    metadata: TransformMetadata,
) -> usize {
    if let Some(scale) = scale {
        if pre_scale {
            transform = transform.pre_scale(scale, scale, 1.0);
        } else {
            transform = transform.then_scale(scale, scale, 1.0);
        }
    }
    // TODO: refactor the calling code to not even try
    // registering a non-invertible transform.
    let inv_transform = transform
        .inverse()
        .unwrap_or_else(PictureToLayoutTransform::identity);

    let data = TransformData {
        transform,
        inv_transform,
    };

    let index = transforms.len();
    metadatas.push(metadata);
    transforms.push(data);

    index
}