use api::{FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint, WorldPoint};
use api::{DeviceIntRect, DeviceIntSize, DevicePoint, DeviceRect};
use api::{LayoutRect, PictureToRasterTransform, LayoutPixel, PropertyBinding, PropertyBindingId};
use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, WorldSize, ClipMode, LayoutSize};
use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, WorldVector2D, LayoutPoint};
use api::{DebugFlags, DeviceHomogeneousVector, DeviceVector2D};
use box_shadow::{BLUR_SAMPLE_SCALE};
use clip::{ClipChainId, ClipChainNode, ClipItem, ClipStore, ClipDataStore, ClipChainStack};
use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, CoordinateSystemId, VisibleFace};
use debug_colors;
use euclid::{size2, vec3, TypedPoint2D, TypedScale, TypedSize2D};
use euclid::approxeq::ApproxEq;
use frame_builder::{FrameVisibilityContext, FrameVisibilityState};
use intern::ItemUid;
use internal_types::{FastHashMap, FastHashSet, PlaneSplitter};
use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
use gpu_types::{TransformPalette, UvRectKind};
use plane_split::{Clipper, Polygon, Splitter};
use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, PrimitiveInstanceKind};
use prim_store::{get_raster_rects, PrimitiveScratchBuffer, VectorKey, PointKey};
use prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex, RectangleKey};
use print_tree::PrintTreePrinter;
use render_backend::DataStores;
use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle, TileBlit};
use render_task::{RenderTaskId, RenderTaskLocation};
use resource_cache::ResourceCache;
use scene::{FilterOpHelpers, SceneProperties};
use scene_builder::Interners;
use smallvec::SmallVec;
use std::{mem, u16};
use std::sync::atomic::{AtomicUsize, Ordering};
use texture_cache::TextureCacheHandle;
use tiling::RenderTargetKind;
use util::{ComparableVec, TransformedRectKind, MatrixHelpers, MaxRect};
struct PictureInfo {
spatial_node_index: SpatialNodeIndex,
}
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct RetainedTiles {
#[cfg_attr(feature = "capture", serde(skip))]
pub tiles: Vec<Tile>,
pub ref_prims: FastHashMap<ItemUid, WorldPoint>,
}
impl RetainedTiles {
pub fn new() -> Self {
RetainedTiles {
tiles: Vec::new(),
ref_prims: FastHashMap::default(),
}
}
pub fn merge(&mut self, other: RetainedTiles) {
assert!(self.tiles.is_empty() || other.tiles.is_empty());
self.tiles.extend(other.tiles);
self.ref_prims.extend(other.ref_prims);
}
}
#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct TileCoordinate;
pub type TileOffset = TypedPoint2D<i32, TileCoordinate>;
pub type TileSize = TypedSize2D<i32, TileCoordinate>;
pub struct TileIndex(pub usize);
const TILE_SIZE_WIDTH: i32 = 1024;
const TILE_SIZE_HEIGHT: i32 = 256;
const TILE_SIZE_TESTING: i32 = 64;
pub const FRAMES_BEFORE_PICTURE_CACHING: usize = 2;
const MAX_DIRTY_RECTS: usize = 3;
const MAX_SURFACE_SIZE: f32 = 4096.0;
const MAX_PRIMS_TO_SEARCH: usize = 128;
static NEXT_TILE_ID: AtomicUsize = AtomicUsize::new(0);
fn clamp(value: i32, low: i32, high: i32) -> i32 {
value.max(low).min(high)
}
#[derive(Debug)]
pub struct OpacityBindingInfo {
value: f32,
changed: bool,
}
#[derive(Debug, PartialEq, Clone)]
pub enum OpacityBinding {
Value(f32),
Binding(PropertyBindingId),
}
impl From<PropertyBinding<f32>> for OpacityBinding {
fn from(binding: PropertyBinding<f32>) -> OpacityBinding {
match binding {
PropertyBinding::Binding(key, _) => OpacityBinding::Binding(key.id),
PropertyBinding::Value(value) => OpacityBinding::Value(value),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
struct TileId(usize);
#[derive(Debug)]
pub struct Tile {
world_rect: WorldRect,
pub local_rect: LayoutRect,
visible_rect: Option<WorldRect>,
valid_rect: WorldRect,
descriptor: TileDescriptor,
pub handle: TextureCacheHandle,
is_valid: bool,
is_same_content: bool,
same_frames: usize,
id: TileId,
transforms: FastHashSet<SpatialNodeIndex>,
potential_clips: FastHashMap<RectangleKey, SpatialNodeIndex>,
consider_for_dirty_rect: bool,
}
impl Tile {
fn new(
id: TileId,
) -> Self {
Tile {
local_rect: LayoutRect::zero(),
world_rect: WorldRect::zero(),
visible_rect: None,
valid_rect: WorldRect::zero(),
handle: TextureCacheHandle::invalid(),
descriptor: TileDescriptor::new(),
is_same_content: false,
is_valid: false,
same_frames: 0,
transforms: FastHashSet::default(),
potential_clips: FastHashMap::default(),
id,
consider_for_dirty_rect: false,
}
}
fn clear(&mut self) {
self.transforms.clear();
self.descriptor.clear();
self.potential_clips.clear();
}
fn update_content_validity(&mut self) {
self.is_same_content &= self.descriptor.is_same_content();
self.is_valid &= self.is_same_content;
}
fn update_rect_validity(&mut self, tile_bounding_rect: &WorldRect) {
self.is_valid &= self.valid_rect.contains_rect(tile_bounding_rect);
if !self.is_same_content {
self.same_frames = 0;
}
self.same_frames += 1;
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PrimitiveDescriptor {
prim_uid: ItemUid,
origin: WorldPoint,
first_clip: u16,
clip_count: u16,
world_culling_rect: WorldRect,
}
#[derive(Debug)]
pub struct TileDescriptor {
prims: ComparableVec<PrimitiveDescriptor>,
clip_uids: ComparableVec<ItemUid>,
clip_vertices: ComparableVec<PointKey>,
image_keys: ComparableVec<ImageKey>,
opacity_bindings: ComparableVec<OpacityBinding>,
transforms: ComparableVec<PointKey>,
}
impl TileDescriptor {
fn new() -> Self {
TileDescriptor {
prims: ComparableVec::new(),
clip_uids: ComparableVec::new(),
clip_vertices: ComparableVec::new(),
opacity_bindings: ComparableVec::new(),
image_keys: ComparableVec::new(),
transforms: ComparableVec::new(),
}
}
fn clear(&mut self) {
self.prims.reset();
self.clip_uids.reset();
self.clip_vertices.reset();
self.opacity_bindings.reset();
self.image_keys.reset();
self.transforms.reset();
}
fn is_same_content(&self) -> bool {
if !self.image_keys.is_valid() {
return false;
}
if !self.opacity_bindings.is_valid() {
return false;
}
if !self.clip_uids.is_valid() {
return false;
}
if !self.clip_vertices.is_valid() {
return false;
}
if !self.prims.is_valid() {
return false;
}
if !self.transforms.is_valid() {
return false;
}
true
}
}
#[derive(Debug, Clone)]
pub struct DirtyRegionRect {
pub world_rect: WorldRect,
}
#[derive(Debug, Clone)]
pub struct DirtyRegion {
pub dirty_rects: Vec<DirtyRegionRect>,
pub combined: DirtyRegionRect,
}
impl DirtyRegion {
pub fn new() -> Self {
DirtyRegion {
dirty_rects: Vec::with_capacity(MAX_DIRTY_RECTS),
combined: DirtyRegionRect {
world_rect: WorldRect::zero(),
},
}
}
pub fn clear(&mut self) {
self.dirty_rects.clear();
self.combined = DirtyRegionRect {
world_rect: WorldRect::zero(),
}
}
pub fn push(
&mut self,
rect: WorldRect,
) {
self.combined.world_rect = self.combined.world_rect.union(&rect);
self.dirty_rects.push(DirtyRegionRect {
world_rect: rect,
});
}
pub fn is_empty(&self) -> bool {
self.dirty_rects.is_empty()
}
pub fn collapse(&mut self) {
self.dirty_rects.clear();
self.dirty_rects.push(self.combined.clone());
}
pub fn inflate(
&self,
inflate_amount: f32,
) -> DirtyRegion {
let mut dirty_rects = Vec::with_capacity(self.dirty_rects.len());
let mut combined = DirtyRegionRect {
world_rect: WorldRect::zero(),
};
for rect in &self.dirty_rects {
let world_rect = rect.world_rect.inflate(inflate_amount, inflate_amount);
combined.world_rect = combined.world_rect.union(&world_rect);
dirty_rects.push(DirtyRegionRect {
world_rect,
});
}
DirtyRegion {
dirty_rects,
combined,
}
}
pub fn record(&self) -> RecordedDirtyRegion {
let mut rects: Vec<WorldRect> =
self.dirty_rects.iter().map(|r| r.world_rect.clone()).collect();
rects.sort_unstable_by_key(|r| (r.origin.y as usize, r.origin.x as usize));
RecordedDirtyRegion { rects }
}
}
pub struct RecordedDirtyRegion {
pub rects: Vec<WorldRect>,
}
impl ::std::fmt::Display for RecordedDirtyRegion {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
for r in self.rects.iter() {
let (x, y, w, h) = (r.origin.x, r.origin.y, r.size.width, r.size.height);
write!(f, "[({},{}):{}x{}]", x, y, w, h)?;
}
Ok(())
}
}
impl ::std::fmt::Debug for RecordedDirtyRegion {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::std::fmt::Display::fmt(self, f)
}
}
struct DirtyRegionBuilder<'a> {
tiles: &'a mut [Tile],
tile_count: TileSize,
}
impl<'a> DirtyRegionBuilder<'a> {
fn new(
tiles: &'a mut [Tile],
tile_count: TileSize,
) -> Self {
DirtyRegionBuilder {
tiles,
tile_count,
}
}
fn tile_index(&self, x: i32, y: i32) -> usize {
(y * self.tile_count.width + x) as usize
}
fn is_dirty(&self, x: i32, y: i32) -> bool {
if x == self.tile_count.width || y == self.tile_count.height {
return false;
}
self.get_tile(x, y).consider_for_dirty_rect
}
fn get_tile(&self, x: i32, y: i32) -> &Tile {
&self.tiles[self.tile_index(x, y)]
}
fn get_tile_mut(&mut self, x: i32, y: i32) -> &mut Tile {
&mut self.tiles[self.tile_index(x, y)]
}
fn column_is_dirty(&self, x: i32, y0: i32, y1: i32) -> bool {
for y in y0 .. y1 {
if !self.is_dirty(x, y) {
return false;
}
}
true
}
fn push_dirty_rect(
&mut self,
x0: i32,
y0: i32,
x1: i32,
y1: i32,
dirty_region: &mut DirtyRegion,
) {
let mut dirty_world_rect = WorldRect::zero();
for y in y0 .. y1 {
for x in x0 .. x1 {
let tile = self.get_tile_mut(x, y);
tile.consider_for_dirty_rect = false;
if let Some(visible_rect) = tile.visible_rect {
dirty_world_rect = dirty_world_rect.union(&visible_rect);
}
}
}
dirty_region.push(dirty_world_rect);
}
fn build(&mut self, dirty_region: &mut DirtyRegion) {
for x0 in 0 .. self.tile_count.width {
for y0 in 0 .. self.tile_count.height {
let mut y1 = y0;
while self.is_dirty(x0, y1) {
y1 += 1;
}
if y1 > y0 {
let mut x1 = x0;
while self.column_is_dirty(x1, y0, y1) {
x1 += 1;
}
self.push_dirty_rect(x0, y0, x1, y1, dirty_region);
}
}
}
}
}
pub struct TileCache {
spatial_node_index: SpatialNodeIndex,
pub tiles: Vec<Tile>,
map_local_to_world: SpaceMapper<LayoutPixel, WorldPixel>,
pub tiles_to_draw: Vec<TileIndex>,
opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
pub dirty_region: DirtyRegion,
world_origin: WorldPoint,
world_tile_size: WorldSize,
tile_count: TileSize,
scroll_offset: Option<WorldVector2D>,
pub pending_blits: Vec<TileBlit>,
world_bounding_rect: WorldRect,
root_clip_rect: WorldRect,
reference_prims: ReferencePrimitiveList,
root_clip_chain_id: ClipChainId,
pub is_enabled: bool,
}
#[derive(Clone)]
struct ReferencePrimitive {
uid: ItemUid,
local_pos: LayoutPoint,
spatial_node_index: SpatialNodeIndex,
ref_count: usize,
}
struct ReferencePrimitiveList {
ref_prims: Vec<ReferencePrimitive>,
}
impl ReferencePrimitiveList {
fn new(
prim_instances: &[PrimitiveInstance],
pictures: &[PicturePrimitive],
) -> Self {
let mut map = FastHashMap::default();
let mut search_count = 0;
collect_ref_prims(
prim_instances,
pictures,
&mut map,
&mut search_count,
);
let ref_prims = map.values().filter(|prim| {
prim.ref_count == 1
}).cloned().collect();
ReferencePrimitiveList {
ref_prims,
}
}
}
fn collect_ref_prims(
prim_instances: &[PrimitiveInstance],
pictures: &[PicturePrimitive],
map: &mut FastHashMap<ItemUid, ReferencePrimitive>,
search_count: &mut usize,
) {
for prim_instance in prim_instances {
if *search_count > MAX_PRIMS_TO_SEARCH {
return;
}
match prim_instance.kind {
PrimitiveInstanceKind::Picture { pic_index, .. } => {
collect_ref_prims(
&pictures[pic_index.0].prim_list.prim_instances,
pictures,
map,
search_count,
);
}
_ => {
let uid = prim_instance.uid();
let entry = map.entry(uid).or_insert_with(|| {
ReferencePrimitive {
uid,
local_pos: prim_instance.prim_origin,
spatial_node_index: prim_instance.spatial_node_index,
ref_count: 0,
}
});
entry.ref_count += 1;
*search_count = *search_count + 1;
}
}
}
}
impl TileCache {
pub fn new(
spatial_node_index: SpatialNodeIndex,
prim_instances: &[PrimitiveInstance],
root_clip_chain_id: ClipChainId,
pictures: &[PicturePrimitive],
) -> Self {
let reference_prims = ReferencePrimitiveList::new(
prim_instances,
pictures,
);
TileCache {
spatial_node_index,
tiles: Vec::new(),
map_local_to_world: SpaceMapper::new(
ROOT_SPATIAL_NODE_INDEX,
WorldRect::zero(),
),
tiles_to_draw: Vec::new(),
opacity_bindings: FastHashMap::default(),
dirty_region: DirtyRegion::new(),
world_origin: WorldPoint::zero(),
world_tile_size: WorldSize::zero(),
tile_count: TileSize::zero(),
scroll_offset: None,
pending_blits: Vec::new(),
world_bounding_rect: WorldRect::zero(),
root_clip_rect: WorldRect::max_rect(),
reference_prims,
root_clip_chain_id,
is_enabled: true,
}
}
fn get_tile_coords_for_rect(
&self,
rect: &WorldRect,
) -> (TileOffset, TileOffset) {
let origin = rect.origin - self.world_origin;
let mut p0 = TileOffset::new(
(origin.x / self.world_tile_size.width).floor() as i32,
(origin.y / self.world_tile_size.height).floor() as i32,
);
let mut p1 = TileOffset::new(
((origin.x + rect.size.width) / self.world_tile_size.width).ceil() as i32,
((origin.y + rect.size.height) / self.world_tile_size.height).ceil() as i32,
);
p0.x = clamp(p0.x, 0, self.tile_count.width);
p0.y = clamp(p0.y, 0, self.tile_count.height);
p1.x = clamp(p1.x, 0, self.tile_count.width);
p1.y = clamp(p1.y, 0, self.tile_count.height);
(p0, p1)
}
pub fn pre_update(
&mut self,
pic_rect: LayoutRect,
frame_context: &FrameVisibilityContext,
frame_state: &mut FrameVisibilityState,
surface_index: SurfaceIndex,
) {
self.is_enabled = surface_index == SurfaceIndex(1);
if !self.is_enabled {
return;
}
let DeviceIntSize { width: tile_width, height: tile_height, _unit: _ } =
Self::tile_dimensions(frame_context.config.testing);
let scroll_offset_point = frame_context.clip_scroll_tree
.get_relative_transform(
self.spatial_node_index,
ROOT_SPATIAL_NODE_INDEX,
)
.expect("bug: unable to get scroll transform")
.flattened
.inverse_project_2d_origin()
.unwrap_or_else(LayoutPoint::zero);
let scroll_offset = WorldVector2D::new(scroll_offset_point.x, scroll_offset_point.y);
let scroll_delta = match self.scroll_offset {
Some(prev) => prev - scroll_offset,
None => WorldVector2D::zero(),
};
self.scroll_offset = Some(scroll_offset);
let world_offset = if frame_state.retained_tiles.tiles.is_empty() {
None
} else {
assert!(self.tiles.is_empty());
self.tiles = mem::replace(&mut frame_state.retained_tiles.tiles, Vec::new());
let mut new_prim_map = FastHashMap::default();
build_ref_prims(
&self.reference_prims.ref_prims,
&mut new_prim_map,
frame_context.clip_scroll_tree,
);
correlate_prim_maps(
&frame_state.retained_tiles.ref_prims,
&new_prim_map,
)
}.unwrap_or(WorldVector2D::zero());
self.tiles_to_draw.clear();
self.map_local_to_world = SpaceMapper::new(
ROOT_SPATIAL_NODE_INDEX,
frame_context.screen_world_rect,
);
let world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
self.spatial_node_index,
frame_context.screen_world_rect,
frame_context.clip_scroll_tree,
);
let current_properties = frame_context.scene_properties.float_properties();
let old_properties = mem::replace(&mut self.opacity_bindings, FastHashMap::default());
for (id, value) in current_properties {
let changed = match old_properties.get(id) {
Some(old_property) => !old_property.value.approx_eq(value),
None => true,
};
self.opacity_bindings.insert(*id, OpacityBindingInfo {
value: *value,
changed,
});
}
let pic_world_rect = world_mapper
.map(&pic_rect)
.expect("bug: unable to map picture rect to world");
let needed_world_rect = frame_context
.screen_world_rect
.intersection(&pic_world_rect)
.unwrap_or(frame_context.screen_world_rect);
let mut world_ref_point = if self.tiles.is_empty() {
needed_world_rect.origin.floor()
} else {
self.tiles[0].world_rect.origin + world_offset
};
world_ref_point += scroll_delta;
let device_ref_point = world_ref_point * frame_context.device_pixel_scale;
let device_world_rect = frame_context.screen_world_rect * frame_context.device_pixel_scale;
let pic_device_rect = pic_world_rect * frame_context.device_pixel_scale;
let needed_device_rect = pic_device_rect
.intersection(&device_world_rect)
.unwrap_or(device_world_rect);
let needed_device_rect = needed_device_rect.inflate(
0.0,
3.0 * tile_height as f32,
);
let p0 = needed_device_rect.origin;
let p1 = needed_device_rect.bottom_right();
let p0 = DevicePoint::new(
device_ref_point.x + ((p0.x - device_ref_point.x) / tile_width as f32).floor() * tile_width as f32,
device_ref_point.y + ((p0.y - device_ref_point.y) / tile_height as f32).floor() * tile_height as f32,
);
let p1 = DevicePoint::new(
device_ref_point.x + ((p1.x - device_ref_point.x) / tile_width as f32).ceil() * tile_width as f32,
device_ref_point.y + ((p1.y - device_ref_point.y) / tile_height as f32).ceil() * tile_height as f32,
);
let x_tiles = ((p1.x - p0.x) / tile_width as f32).round() as i32;
let y_tiles = ((p1.y - p0.y) / tile_height as f32).round() as i32;
let mut old_tiles = FastHashMap::default();
for tile in self.tiles.drain(..) {
let tile_device_pos = (tile.world_rect.origin + scroll_delta) * frame_context.device_pixel_scale;
let key = (
(tile_device_pos.x + world_offset.x).round() as i32,
(tile_device_pos.y + world_offset.y).round() as i32,
);
old_tiles.insert(key, tile);
}
self.world_origin = WorldPoint::new(
p0.x / frame_context.device_pixel_scale.0,
p0.y / frame_context.device_pixel_scale.0,
);
self.world_tile_size = WorldSize::new(
tile_width as f32 / frame_context.device_pixel_scale.0,
tile_height as f32 / frame_context.device_pixel_scale.0,
);
self.tile_count = TileSize::new(x_tiles, y_tiles);
for y in 0 .. y_tiles {
for x in 0 .. x_tiles {
let px = p0.x + x as f32 * tile_width as f32;
let py = p0.y + y as f32 * tile_height as f32;
let key = (px.round() as i32, py.round() as i32);
let mut tile = match old_tiles.remove(&key) {
Some(tile) => tile,
None => {
let next_id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed));
Tile::new(next_id)
}
};
tile.world_rect = WorldRect::new(
WorldPoint::new(
px / frame_context.device_pixel_scale.0,
py / frame_context.device_pixel_scale.0,
),
self.world_tile_size,
);
tile.local_rect = world_mapper
.unmap(&tile.world_rect)
.expect("bug: can't unmap world rect");
tile.visible_rect = tile.world_rect.intersection(&frame_context.screen_world_rect);
self.tiles.push(tile);
}
}
if !old_tiles.is_empty() {
}
self.world_bounding_rect = WorldRect::zero();
self.root_clip_rect = WorldRect::max_rect();
if self.root_clip_chain_id != ClipChainId::NONE {
let root_clip_chain_node = &frame_state
.clip_store
.clip_chain_nodes[self.root_clip_chain_id.0 as usize];
let root_clip_node = &frame_state
.data_stores
.clip[root_clip_chain_node.handle];
if let Some(clip_rect) = root_clip_node.item.get_local_clip_rect(root_clip_chain_node.local_pos) {
self.map_local_to_world.set_target_spatial_node(
root_clip_chain_node.spatial_node_index,
frame_context.clip_scroll_tree,
);
if let Some(world_clip_rect) = self.map_local_to_world.map(&clip_rect) {
self.root_clip_rect = world_clip_rect;
}
}
}
for tile in &mut self.tiles {
tile.is_same_content = true;
for image_key in tile.descriptor.image_keys.items() {
if frame_state.resource_cache.is_image_dirty(*image_key) {
tile.is_same_content = false;
break;
}
}
for binding in tile.descriptor.opacity_bindings.items() {
if let OpacityBinding::Binding(id) = binding {
let changed = match self.opacity_bindings.get(id) {
Some(info) => info.changed,
None => true,
};
if changed {
tile.is_same_content = false;
break;
}
}
}
tile.clear();
}
}
pub fn update_prim_dependencies(
&mut self,
prim_instance: &PrimitiveInstance,
clip_chain_stack: &ClipChainStack,
prim_rect: LayoutRect,
clip_scroll_tree: &ClipScrollTree,
data_stores: &DataStores,
clip_chain_nodes: &[ClipChainNode],
pictures: &[PicturePrimitive],
resource_cache: &ResourceCache,
opacity_binding_store: &OpacityBindingStorage,
image_instances: &ImageInstanceStorage,
) -> bool {
if !self.is_enabled {
return true;
}
self.map_local_to_world.set_target_spatial_node(
prim_instance.spatial_node_index,
clip_scroll_tree,
);
let world_rect = match self.map_local_to_world.map(&prim_rect) {
Some(rect) => rect,
None => return false,
};
if world_rect.size.width <= 0.0 || world_rect.size.height <= 0.0 {
return false;
}
let (p0, p1) = self.get_tile_coords_for_rect(&world_rect);
if p0.x == p1.x || p0.y == p1.y {
return false;
}
let mut opacity_bindings: SmallVec<[OpacityBinding; 4]> = SmallVec::new();
let mut clip_chain_uids: SmallVec<[ItemUid; 8]> = SmallVec::new();
let mut clip_vertices: SmallVec<[WorldPoint; 8]> = SmallVec::new();
let mut image_keys: SmallVec<[ImageKey; 8]> = SmallVec::new();
let mut clip_spatial_nodes = FastHashSet::default();
let mut world_clips: SmallVec<[(RectangleKey, SpatialNodeIndex); 4]> = SmallVec::default();
let is_cacheable = prim_instance.is_cacheable(
&data_stores,
resource_cache,
);
let include_clip_rect = match prim_instance.kind {
PrimitiveInstanceKind::Picture { pic_index,.. } => {
let pic = &pictures[pic_index.0];
if let Some(PictureCompositeMode::Filter(FilterOp::Opacity(binding, _))) = pic.requested_composite_mode {
opacity_bindings.push(binding.into());
}
false
}
PrimitiveInstanceKind::Rectangle { opacity_binding_index, .. } => {
if opacity_binding_index != OpacityBindingIndex::INVALID {
let opacity_binding = &opacity_binding_store[opacity_binding_index];
for binding in &opacity_binding.bindings {
opacity_bindings.push(OpacityBinding::from(*binding));
}
}
true
}
PrimitiveInstanceKind::Image { data_handle, image_instance_index, .. } => {
let image_data = &data_stores.image[data_handle].kind;
let image_instance = &image_instances[image_instance_index];
let opacity_binding_index = image_instance.opacity_binding_index;
if opacity_binding_index != OpacityBindingIndex::INVALID {
let opacity_binding = &opacity_binding_store[opacity_binding_index];
for binding in &opacity_binding.bindings {
opacity_bindings.push(OpacityBinding::from(*binding));
}
}
image_keys.push(image_data.key);
true
}
PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
let yuv_image_data = &data_stores.yuv_image[data_handle].kind;
image_keys.extend_from_slice(&yuv_image_data.yuv_key);
true
}
PrimitiveInstanceKind::TextRun { .. } |
PrimitiveInstanceKind::LineDecoration { .. } |
PrimitiveInstanceKind::Clear { .. } |
PrimitiveInstanceKind::NormalBorder { .. } |
PrimitiveInstanceKind::LinearGradient { .. } |
PrimitiveInstanceKind::RadialGradient { .. } |
PrimitiveInstanceKind::ImageBorder { .. } => {
true
}
};
let mut world_clip_rect = world_rect;
let mut culling_rect = prim_rect
.intersection(&prim_instance.local_clip_rect)
.unwrap_or(LayoutRect::zero());
for clip_stack in &clip_chain_stack.stack {
for clip_chain_id in clip_stack {
let mut current_clip_chain_id = *clip_chain_id;
while current_clip_chain_id != ClipChainId::NONE {
let clip_chain_node = &clip_chain_nodes[current_clip_chain_id.0 as usize];
let clip_node = &data_stores.clip[clip_chain_node.handle];
if current_clip_chain_id == self.root_clip_chain_id {
current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
continue;
}
self.map_local_to_world.set_target_spatial_node(
clip_chain_node.spatial_node_index,
clip_scroll_tree,
);
let add_to_clip_deps = match clip_node.item {
ClipItem::Rectangle(size, ClipMode::Clip) => {
let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_chain_node.spatial_node_index.0 as usize];
let local_clip_rect = LayoutRect::new(
clip_chain_node.local_pos,
size,
);
if clip_spatial_node.coordinate_system_id == CoordinateSystemId(0) {
match self.map_local_to_world.map(&local_clip_rect) {
Some(clip_world_rect) => {
world_clip_rect = world_clip_rect
.intersection(&clip_world_rect)
.unwrap_or(WorldRect::zero());
if clip_chain_node.spatial_node_index == prim_instance.spatial_node_index {
culling_rect = culling_rect.intersection(&local_clip_rect).unwrap_or(LayoutRect::zero());
false
} else if !clip_scroll_tree.is_same_or_child_of(
clip_chain_node.spatial_node_index,
self.spatial_node_index,
) {
world_clips.push((
clip_world_rect.into(),
clip_chain_node.spatial_node_index,
));
false
} else {
true
}
}
None => {
true
}
}
} else {
true
}
}
ClipItem::Rectangle(_, ClipMode::ClipOut) |
ClipItem::RoundedRectangle(..) |
ClipItem::Image { .. } |
ClipItem::BoxShadow(..) => {
true
}
};
if add_to_clip_deps {
clip_chain_uids.push(clip_chain_node.handle.uid());
if clip_chain_node.spatial_node_index != self.spatial_node_index {
clip_spatial_nodes.insert(clip_chain_node.spatial_node_index);
}
let local_clip_rect = LayoutRect::new(
clip_chain_node.local_pos,
LayoutSize::zero(),
);
if let Some(world_clip_rect) = self.map_local_to_world.map(&local_clip_rect) {
clip_vertices.push(world_clip_rect.origin);
}
}
current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
}
}
}
if include_clip_rect {
if let Some(clipped_world_rect) = world_clip_rect.intersection(&self.root_clip_rect) {
self.world_bounding_rect = self.world_bounding_rect.union(&clipped_world_rect);
}
}
self.map_local_to_world.set_target_spatial_node(
prim_instance.spatial_node_index,
clip_scroll_tree,
);
let world_culling_rect = match self.map_local_to_world.map(&culling_rect) {
Some(rect) => rect,
None => return false,
};
for y in p0.y .. p1.y {
for x in p0.x .. p1.x {
let index = (y * self.tile_count.width + x) as usize;
let tile = &mut self.tiles[index];
let world_culling_rect = world_culling_rect
.intersection(&tile.world_rect)
.map(|rect| {
rect.translate(&-tile.world_rect.origin.to_vector())
})
.unwrap_or(WorldRect::zero())
.round();
tile.is_same_content &= is_cacheable;
tile.descriptor.image_keys.extend_from_slice(&image_keys);
tile.descriptor.opacity_bindings.extend_from_slice(&opacity_bindings);
tile.descriptor.prims.push(PrimitiveDescriptor {
prim_uid: prim_instance.uid(),
origin: (world_rect.origin - tile.world_rect.origin.to_vector()).round(),
first_clip: tile.descriptor.clip_uids.len() as u16,
clip_count: clip_chain_uids.len() as u16,
world_culling_rect,
});
tile.descriptor.clip_uids.extend_from_slice(&clip_chain_uids);
for clip_vertex in &clip_vertices {
let clip_vertex = (*clip_vertex - tile.world_rect.origin.to_vector()).round();
tile.descriptor.clip_vertices.push(clip_vertex.into());
}
if prim_instance.spatial_node_index != self.spatial_node_index {
tile.transforms.insert(prim_instance.spatial_node_index);
}
for spatial_node_index in &clip_spatial_nodes {
tile.transforms.insert(*spatial_node_index);
}
for (world_rect, spatial_node_index) in &world_clips {
tile.potential_clips.insert(world_rect.clone(), *spatial_node_index);
}
}
}
true
}
pub fn post_update(
&mut self,
resource_cache: &mut ResourceCache,
gpu_cache: &mut GpuCache,
frame_context: &FrameVisibilityContext,
scratch: &mut PrimitiveScratchBuffer,
) -> LayoutRect {
self.dirty_region.clear();
self.pending_blits.clear();
if !self.is_enabled {
return LayoutRect::max_rect();
}
if !self.world_bounding_rect.intersects(&frame_context.screen_world_rect) {
return LayoutRect::zero();
}
let map_surface_to_world: SpaceMapper<LayoutPixel, WorldPixel> = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
self.spatial_node_index,
frame_context.screen_world_rect,
frame_context.clip_scroll_tree,
);
let local_clip_rect = map_surface_to_world
.unmap(&self.world_bounding_rect)
.expect("bug: unable to map local clip rect");
for (i, tile) in self.tiles.iter_mut().enumerate() {
for (clip_world_rect, spatial_node_index) in &tile.potential_clips {
let clip_world_rect = WorldRect::from(clip_world_rect.clone());
if !clip_world_rect.contains_rect(&self.world_bounding_rect) {
tile.transforms.insert(*spatial_node_index);
}
}
let mut transform_spatial_nodes: Vec<SpatialNodeIndex> = tile.transforms.drain().collect();
transform_spatial_nodes.sort();
for spatial_node_index in transform_spatial_nodes {
let inverse_origin = if self.spatial_node_index >= spatial_node_index {
frame_context.clip_scroll_tree
.get_relative_transform(
self.spatial_node_index,
spatial_node_index,
)
.expect("BUG: unable to get relative transform")
.flattened
.transform_point2d(&LayoutPoint::zero())
} else {
frame_context.clip_scroll_tree
.get_relative_transform(
spatial_node_index,
self.spatial_node_index,
)
.expect("BUG: unable to get relative transform")
.flattened
.inverse_project_2d_origin()
};
let key = inverse_origin
.unwrap_or_else(LayoutPoint::zero)
.round();
tile.descriptor.transforms.push(key.into());
}
if resource_cache.texture_cache.is_allocated(&tile.handle) {
resource_cache.texture_cache.request(&tile.handle, gpu_cache);
} else {
tile.is_valid = false;
}
tile.update_content_validity();
let visible_rect = match tile.visible_rect {
Some(rect) => rect,
None => continue,
};
let tile_bounding_rect = match visible_rect.intersection(&self.world_bounding_rect) {
Some(rect) => rect.translate(&-tile.world_rect.origin.to_vector()),
None => continue,
};
tile.update_rect_validity(&tile_bounding_rect);
if tile.descriptor.prims.is_empty() {
continue;
}
if tile.is_valid {
tile.consider_for_dirty_rect = false;
self.tiles_to_draw.push(TileIndex(i));
if frame_context.debug_flags.contains(DebugFlags::PICTURE_CACHING_DBG) {
if let Some(world_rect) = tile.world_rect.intersection(&self.world_bounding_rect) {
let tile_device_rect = world_rect * frame_context.device_pixel_scale;
let mut label_offset = DeviceVector2D::new(20.0, 30.0);
scratch.push_debug_rect(
tile_device_rect,
debug_colors::GREEN,
);
if tile_device_rect.size.height >= label_offset.y {
scratch.push_debug_string(
tile_device_rect.origin + label_offset,
debug_colors::RED,
format!("{:?} {:?} {:?}", tile.id, tile.handle, tile.world_rect),
);
}
label_offset.y += 20.0;
if tile_device_rect.size.height >= label_offset.y {
scratch.push_debug_string(
tile_device_rect.origin + label_offset,
debug_colors::RED,
format!("same: {} frames", tile.same_frames),
);
}
}
}
} else {
if frame_context.debug_flags.contains(DebugFlags::PICTURE_CACHING_DBG) {
if let Some(world_rect) = visible_rect.intersection(&self.world_bounding_rect) {
scratch.push_debug_rect(
world_rect * frame_context.device_pixel_scale,
debug_colors::RED,
);
}
}
if tile.same_frames >= FRAMES_BEFORE_PICTURE_CACHING {
if !resource_cache.texture_cache.is_allocated(&tile.handle) {
resource_cache.texture_cache.update_picture_cache(
&mut tile.handle,
gpu_cache,
);
}
let cache_item = resource_cache
.get_texture_cache_item(&tile.handle);
let src_origin = (visible_rect.origin * frame_context.device_pixel_scale).round().to_i32();
let valid_rect = visible_rect.translate(&-tile.world_rect.origin.to_vector());
tile.valid_rect = visible_rect
.intersection(&self.world_bounding_rect)
.map(|rect| rect.translate(&-tile.world_rect.origin.to_vector()))
.unwrap_or(WorldRect::zero());
let dest_rect = (valid_rect * frame_context.device_pixel_scale).round().to_i32();
self.pending_blits.push(TileBlit {
target: cache_item,
src_offset: src_origin,
dest_offset: dest_rect.origin,
size: dest_rect.size,
});
tile.is_valid = true;
}
tile.consider_for_dirty_rect = true;
}
}
let mut builder = DirtyRegionBuilder::new(
&mut self.tiles,
self.tile_count,
);
builder.build(&mut self.dirty_region);
if frame_context.config.testing {
scratch.recorded_dirty_regions.push(self.dirty_region.record());
}
if self.dirty_region.dirty_rects.len() > MAX_DIRTY_RECTS {
self.dirty_region.collapse();
}
local_clip_rect
}
pub fn tile_dimensions(testing: bool) -> DeviceIntSize {
if testing {
size2(TILE_SIZE_TESTING, TILE_SIZE_TESTING)
} else {
size2(TILE_SIZE_WIDTH, TILE_SIZE_HEIGHT)
}
}
}
pub struct PictureUpdateState<'a> {
surfaces: &'a mut Vec<SurfaceInfo>,
surface_stack: Vec<SurfaceIndex>,
picture_stack: Vec<PictureInfo>,
are_raster_roots_assigned: bool,
}
impl<'a> PictureUpdateState<'a> {
pub fn update_all(
surfaces: &'a mut Vec<SurfaceInfo>,
pic_index: PictureIndex,
picture_primitives: &mut [PicturePrimitive],
frame_context: &FrameBuildingContext,
gpu_cache: &mut GpuCache,
clip_store: &ClipStore,
clip_data_store: &ClipDataStore,
) {
profile_marker!("UpdatePictures");
let mut state = PictureUpdateState {
surfaces,
surface_stack: vec![SurfaceIndex(0)],
picture_stack: Vec::new(),
are_raster_roots_assigned: true,
};
state.update(
pic_index,
ClipChainId::NONE,
picture_primitives,
frame_context,
gpu_cache,
clip_store,
clip_data_store,
);
if !state.are_raster_roots_assigned {
state.assign_raster_roots(
pic_index,
picture_primitives,
ROOT_SPATIAL_NODE_INDEX,
);
}
}
fn current_surface(&self) -> &SurfaceInfo {
&self.surfaces[self.surface_stack.last().unwrap().0]
}
fn current_surface_mut(&mut self) -> &mut SurfaceInfo {
&mut self.surfaces[self.surface_stack.last().unwrap().0]
}
fn push_surface(
&mut self,
surface: SurfaceInfo,
) -> SurfaceIndex {
let surface_index = SurfaceIndex(self.surfaces.len());
self.surfaces.push(surface);
self.surface_stack.push(surface_index);
surface_index
}
fn pop_surface(&mut self) -> SurfaceIndex{
self.surface_stack.pop().unwrap()
}
fn current_picture(&self) -> Option<&PictureInfo> {
self.picture_stack.last()
}
fn push_picture(
&mut self,
info: PictureInfo,
) {
self.picture_stack.push(info);
}
fn pop_picture(
&mut self,
) -> PictureInfo {
self.picture_stack.pop().unwrap()
}
fn update(
&mut self,
pic_index: PictureIndex,
clip_chain_id: ClipChainId,
picture_primitives: &mut [PicturePrimitive],
frame_context: &FrameBuildingContext,
gpu_cache: &mut GpuCache,
clip_store: &ClipStore,
clip_data_store: &ClipDataStore,
) {
if let Some(prim_list) = picture_primitives[pic_index.0].pre_update(
clip_chain_id,
self,
frame_context,
clip_store,
clip_data_store,
) {
for (child_pic_index, clip_chain_id) in &prim_list.pictures {
self.update(
*child_pic_index,
*clip_chain_id,
picture_primitives,
frame_context,
gpu_cache,
clip_store,
clip_data_store,
);
}
picture_primitives[pic_index.0].post_update(
prim_list,
self,
frame_context,
gpu_cache,
);
}
}
fn assign_raster_roots(
&mut self,
pic_index: PictureIndex,
picture_primitives: &[PicturePrimitive],
fallback_raster_spatial_node: SpatialNodeIndex,
) {
let picture = &picture_primitives[pic_index.0];
if !picture.is_visible() {
return
}
let new_fallback = match picture.raster_config {
Some(ref config) => {
let surface = &mut self.surfaces[config.surface_index.0];
if !config.establishes_raster_root {
surface.raster_spatial_node_index = fallback_raster_spatial_node;
}
surface.raster_spatial_node_index
}
None => fallback_raster_spatial_node,
};
for (child_pic_index, _) in &picture.prim_list.pictures {
self.assign_raster_roots(*child_pic_index, picture_primitives, new_fallback);
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct SurfaceIndex(pub usize);
pub const ROOT_SURFACE_INDEX: SurfaceIndex = SurfaceIndex(0);
#[derive(Debug)]
pub struct SurfaceInfo {
pub rect: PictureRect,
pub map_local_to_surface: SpaceMapper<LayoutPixel, PicturePixel>,
pub raster_spatial_node_index: SpatialNodeIndex,
pub surface_spatial_node_index: SpatialNodeIndex,
pub surface: Option<PictureSurface>,
pub tasks: Vec<RenderTaskId>,
pub inflation_factor: f32,
}
impl SurfaceInfo {
pub fn new(
surface_spatial_node_index: SpatialNodeIndex,
raster_spatial_node_index: SpatialNodeIndex,
inflation_factor: f32,
world_rect: WorldRect,
clip_scroll_tree: &ClipScrollTree,
) -> Self {
let map_surface_to_world = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
surface_spatial_node_index,
world_rect,
clip_scroll_tree,
);
let pic_bounds = map_surface_to_world
.unmap(&map_surface_to_world.bounds)
.unwrap_or(PictureRect::max_rect());
let map_local_to_surface = SpaceMapper::new(
surface_spatial_node_index,
pic_bounds,
);
SurfaceInfo {
rect: PictureRect::zero(),
map_local_to_surface,
surface: None,
raster_spatial_node_index,
surface_spatial_node_index,
tasks: Vec::new(),
inflation_factor,
}
}
pub fn take_render_tasks(&mut self) -> Vec<RenderTaskId> {
mem::replace(&mut self.tasks, Vec::new())
}
}
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct RasterConfig {
pub composite_mode: PictureCompositeMode,
pub surface_index: SurfaceIndex,
pub establishes_raster_root: bool,
}
bitflags! {
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct BlitReason: u32 {
const ISOLATE = 1;
const CLIP = 2;
const PRESERVE3D = 4;
}
}
#[allow(dead_code)]
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub enum PictureCompositeMode {
MixBlend(MixBlendMode),
Filter(FilterOp),
Blit(BlitReason),
TileCache {
clear_color: ColorF,
},
}
#[derive(Debug)]
pub enum PictureSurface {
RenderTask(RenderTaskId),
#[allow(dead_code)]
TextureCache(RenderTaskCacheEntryHandle),
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub enum Picture3DContext<C> {
Out,
In {
root_data: Option<Vec<C>>,
ancestor_index: SpatialNodeIndex,
},
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct OrderedPictureChild {
pub anchor: usize,
pub spatial_node_index: SpatialNodeIndex,
pub gpu_address: GpuCacheAddress,
}
#[derive(Hash, Eq, PartialEq, Copy, Clone)]
struct PrimitiveClusterKey {
spatial_node_index: SpatialNodeIndex,
is_backface_visible: bool,
}
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct PrimitiveCluster {
spatial_node_index: SpatialNodeIndex,
is_backface_visible: bool,
bounding_rect: LayoutRect,
pub is_visible: bool,
}
impl PrimitiveCluster {
fn new(
spatial_node_index: SpatialNodeIndex,
is_backface_visible: bool,
) -> Self {
PrimitiveCluster {
bounding_rect: LayoutRect::zero(),
spatial_node_index,
is_backface_visible,
is_visible: false,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct PrimitiveClusterIndex(pub u32);
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct ClusterIndex(pub u16);
impl ClusterIndex {
pub const INVALID: ClusterIndex = ClusterIndex(u16::MAX);
}
pub type PictureList = SmallVec<[(PictureIndex, ClipChainId); 4]>;
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct PrimitiveList {
pub prim_instances: Vec<PrimitiveInstance>,
pub pictures: PictureList,
pub clusters: SmallVec<[PrimitiveCluster; 4]>,
}
impl PrimitiveList {
pub fn empty() -> Self {
PrimitiveList {
prim_instances: Vec::new(),
pictures: SmallVec::new(),
clusters: SmallVec::new(),
}
}
pub fn new(
mut prim_instances: Vec<PrimitiveInstance>,
interners: &Interners
) -> Self {
let mut pictures = SmallVec::new();
let mut clusters_map = FastHashMap::default();
let mut clusters: SmallVec<[PrimitiveCluster; 4]> = SmallVec::new();
for prim_instance in &mut prim_instances {
let is_pic = match prim_instance.kind {
PrimitiveInstanceKind::Picture { pic_index, .. } => {
pictures.push((pic_index, prim_instance.clip_chain_id));
true
}
_ => {
false
}
};
let prim_data = match prim_instance.kind {
PrimitiveInstanceKind::Rectangle { data_handle, .. } |
PrimitiveInstanceKind::Clear { data_handle, .. } => {
&interners.prim[data_handle]
}
PrimitiveInstanceKind::Image { data_handle, .. } => {
&interners.image[data_handle]
}
PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
&interners.image_border[data_handle]
}
PrimitiveInstanceKind::LineDecoration { data_handle, .. } => {
&interners.line_decoration[data_handle]
}
PrimitiveInstanceKind::LinearGradient { data_handle, .. } => {
&interners.linear_grad[data_handle]
}
PrimitiveInstanceKind::NormalBorder { data_handle, .. } => {
&interners.normal_border[data_handle]
}
PrimitiveInstanceKind::Picture { data_handle, .. } => {
&interners.picture[data_handle]
}
PrimitiveInstanceKind::RadialGradient { data_handle, ..} => {
&interners.radial_grad[data_handle]
}
PrimitiveInstanceKind::TextRun { data_handle, .. } => {
&interners.text_run[data_handle]
}
PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
&interners.yuv_image[data_handle]
}
};
let key = PrimitiveClusterKey {
spatial_node_index: prim_instance.spatial_node_index,
is_backface_visible: prim_data.is_backface_visible,
};
let cluster_index = *clusters_map
.entry(key)
.or_insert_with(|| {
let index = clusters.len();
clusters.push(PrimitiveCluster::new(
prim_instance.spatial_node_index,
prim_data.is_backface_visible,
));
index
}
);
let cluster = &mut clusters[cluster_index];
if !is_pic {
let prim_rect = LayoutRect::new(
prim_instance.prim_origin,
prim_data.prim_size,
);
let culling_rect = prim_instance.local_clip_rect
.intersection(&prim_rect)
.unwrap_or(LayoutRect::zero());
cluster.bounding_rect = cluster.bounding_rect.union(&culling_rect);
}
prim_instance.cluster_index = ClusterIndex(cluster_index as u16);
}
PrimitiveList {
prim_instances,
pictures,
clusters,
}
}
}
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct PictureOptions {
pub inflate_if_required: bool,
}
impl Default for PictureOptions {
fn default() -> Self {
PictureOptions {
inflate_if_required: true,
}
}
}
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct PicturePrimitive {
pub prim_list: PrimitiveList,
#[cfg_attr(feature = "capture", serde(skip))]
pub state: Option<(PictureState, PictureContext)>,
pub pipeline_id: PipelineId,
pub apply_local_clip_rect: bool,
pub secondary_render_task_id: Option<RenderTaskId>,
pub requested_composite_mode: Option<PictureCompositeMode>,
pub requested_raster_space: RasterSpace,
pub raster_config: Option<RasterConfig>,
pub context_3d: Picture3DContext<OrderedPictureChild>,
pub frame_output_pipeline_id: Option<PipelineId>,
pub extra_gpu_data_handle: GpuCacheHandle,
pub spatial_node_index: SpatialNodeIndex,
pub local_rect: LayoutRect,
pub local_clip_rect: LayoutRect,
#[cfg_attr(feature = "capture", serde(skip))]
pub tile_cache: Option<TileCache>,
options: PictureOptions,
}
impl PicturePrimitive {
pub fn print<T: PrintTreePrinter>(
&self,
pictures: &[Self],
self_index: PictureIndex,
pt: &mut T,
) {
pt.new_level(format!("{:?}", self_index));
pt.add_item(format!("prim_count: {:?}", self.prim_list.prim_instances.len()));
pt.add_item(format!("local_rect: {:?}", self.local_rect));
if self.apply_local_clip_rect {
pt.add_item(format!("local_clip_rect: {:?}", self.local_clip_rect));
}
pt.add_item(format!("spatial_node_index: {:?}", self.spatial_node_index));
pt.add_item(format!("raster_config: {:?}", self.raster_config));
pt.add_item(format!("requested_composite_mode: {:?}", self.requested_composite_mode));
for (index, _) in &self.prim_list.pictures {
pictures[index.0].print(pictures, *index, pt);
}
pt.end_level();
}
fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
match self.requested_composite_mode {
Some(PictureCompositeMode::Filter(ref mut filter)) => {
match *filter {
FilterOp::Opacity(ref binding, ref mut value) => {
*value = properties.resolve_float(binding);
}
_ => {}
}
filter.is_visible()
}
_ => true,
}
}
pub fn is_visible(&self) -> bool {
match self.requested_composite_mode {
Some(PictureCompositeMode::Filter(ref filter)) => {
filter.is_visible()
}
_ => true,
}
}
pub fn destroy(
mut self,
retained_tiles: &mut RetainedTiles,
clip_scroll_tree: &ClipScrollTree,
) {
if let Some(tile_cache) = self.tile_cache.take() {
build_ref_prims(
&tile_cache.reference_prims.ref_prims,
&mut retained_tiles.ref_prims,
clip_scroll_tree,
);
for tile in tile_cache.tiles {
retained_tiles.tiles.push(tile);
}
}
}
pub fn new_image(
requested_composite_mode: Option<PictureCompositeMode>,
context_3d: Picture3DContext<OrderedPictureChild>,
pipeline_id: PipelineId,
frame_output_pipeline_id: Option<PipelineId>,
apply_local_clip_rect: bool,
requested_raster_space: RasterSpace,
prim_list: PrimitiveList,
spatial_node_index: SpatialNodeIndex,
local_clip_rect: LayoutRect,
tile_cache: Option<TileCache>,
options: PictureOptions,
) -> Self {
PicturePrimitive {
prim_list,
state: None,
secondary_render_task_id: None,
requested_composite_mode,
raster_config: None,
context_3d,
frame_output_pipeline_id,
extra_gpu_data_handle: GpuCacheHandle::new(),
apply_local_clip_rect,
pipeline_id,
requested_raster_space,
spatial_node_index,
local_rect: LayoutRect::zero(),
local_clip_rect,
tile_cache,
options,
}
}
pub fn take_context(
&mut self,
pic_index: PictureIndex,
surface_spatial_node_index: SpatialNodeIndex,
raster_spatial_node_index: SpatialNodeIndex,
surface_index: SurfaceIndex,
parent_allows_subpixel_aa: bool,
frame_state: &mut FrameBuildingState,
frame_context: &FrameBuildingContext,
) -> Option<(PictureContext, PictureState, PrimitiveList)> {
if !self.is_visible() {
return None;
}
let (raster_spatial_node_index, surface_spatial_node_index, surface_index, inflation_factor) = match self.raster_config {
Some(ref raster_config) => {
let surface = &frame_state.surfaces[raster_config.surface_index.0];
(
surface.raster_spatial_node_index,
self.spatial_node_index,
raster_config.surface_index,
surface.inflation_factor,
)
}
None => {
(
raster_spatial_node_index,
surface_spatial_node_index,
surface_index,
0.0,
)
}
};
let map_pic_to_world = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
surface_spatial_node_index,
frame_context.screen_world_rect,
frame_context.clip_scroll_tree,
);
let pic_bounds = map_pic_to_world.unmap(&map_pic_to_world.bounds)
.unwrap_or(PictureRect::max_rect());
let map_local_to_pic = SpaceMapper::new(
surface_spatial_node_index,
pic_bounds,
);
let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
surface_spatial_node_index,
raster_spatial_node_index,
frame_context.screen_world_rect,
frame_context.clip_scroll_tree,
);
let plane_splitter = match self.context_3d {
Picture3DContext::Out => {
None
}
Picture3DContext::In { root_data: Some(_), .. } => {
Some(PlaneSplitter::new())
}
Picture3DContext::In { root_data: None, .. } => {
None
}
};
let state = PictureState {
map_local_to_pic,
map_pic_to_world,
map_pic_to_raster,
map_raster_to_world,
plane_splitter,
};
let allow_subpixel_aa = match self.raster_config {
Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { clear_color, .. }, .. }) => {
clear_color.a >= 1.0
},
Some(_) => {
false
}
None => {
true
}
};
let allow_subpixel_aa = parent_allows_subpixel_aa && allow_subpixel_aa;
let mut dirty_region_count = 0;
if let Some(ref tile_cache) = self.tile_cache {
if tile_cache.is_enabled {
frame_state.push_dirty_region(tile_cache.dirty_region.clone());
dirty_region_count += 1;
}
}
if inflation_factor > 0.0 {
let inflated_region = frame_state.current_dirty_region().inflate(inflation_factor);
frame_state.push_dirty_region(inflated_region);
dirty_region_count += 1;
}
let context = PictureContext {
pic_index,
apply_local_clip_rect: self.apply_local_clip_rect,
allow_subpixel_aa,
is_passthrough: self.raster_config.is_none(),
raster_space: self.requested_raster_space,
raster_spatial_node_index,
surface_spatial_node_index,
surface_index,
dirty_region_count,
};
let prim_list = mem::replace(&mut self.prim_list, PrimitiveList::empty());
Some((context, state, prim_list))
}
pub fn restore_context(
&mut self,
prim_list: PrimitiveList,
context: PictureContext,
state: PictureState,
frame_state: &mut FrameBuildingState,
) {
for _ in 0 .. context.dirty_region_count {
frame_state.pop_dirty_region();
}
self.prim_list = prim_list;
self.state = Some((state, context));
}
pub fn take_state_and_context(&mut self) -> (PictureState, PictureContext) {
self.state.take().expect("bug: no state present!")
}
pub fn add_split_plane(
splitter: &mut PlaneSplitter,
transforms: &TransformPalette,
prim_instance: &PrimitiveInstance,
original_local_rect: LayoutRect,
combined_local_clip_rect: &LayoutRect,
world_rect: WorldRect,
plane_split_anchor: usize,
) -> bool {
let transform = transforms
.get_world_transform(prim_instance.spatial_node_index);
let matrix = transform.cast();
let local_rect = match original_local_rect
.intersection(combined_local_clip_rect)
{
Some(rect) => rect.cast(),
None => return false,
};
let world_rect = world_rect.cast();
match transform.transform_kind() {
TransformedRectKind::AxisAligned => {
let inv_transform = transforms
.get_world_inv_transform(prim_instance.spatial_node_index);
let polygon = Polygon::from_transformed_rect_with_inverse(
local_rect,
&matrix,
&inv_transform.cast(),
plane_split_anchor,
).unwrap();
splitter.add(polygon);
}
TransformedRectKind::Complex => {
let mut clipper = Clipper::new();
let results = clipper.clip_transformed(
Polygon::from_rect(
local_rect,
plane_split_anchor,
),
&matrix,
Some(world_rect),
);
if let Ok(results) = results {
for poly in results {
splitter.add(poly);
}
}
}
}
true
}
pub fn resolve_split_planes(
&mut self,
splitter: &mut PlaneSplitter,
frame_state: &mut FrameBuildingState,
) {
let ordered = match self.context_3d {
Picture3DContext::In { root_data: Some(ref mut list), .. } => list,
_ => panic!("Expected to find 3D context root"),
};
ordered.clear();
for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
let spatial_node_index = self.prim_list.prim_instances[poly.anchor].spatial_node_index;
let transform = frame_state.transforms.get_world_inv_transform(spatial_node_index);
let local_points = [
transform.transform_point3d(&poly.points[0].cast()).unwrap(),
transform.transform_point3d(&poly.points[1].cast()).unwrap(),
transform.transform_point3d(&poly.points[2].cast()).unwrap(),
transform.transform_point3d(&poly.points[3].cast()).unwrap(),
];
let gpu_blocks = [
[local_points[0].x, local_points[0].y, local_points[1].x, local_points[1].y].into(),
[local_points[2].x, local_points[2].y, local_points[3].x, local_points[3].y].into(),
];
let gpu_handle = frame_state.gpu_cache.push_per_frame_blocks(&gpu_blocks);
let gpu_address = frame_state.gpu_cache.get_address(&gpu_handle);
ordered.push(OrderedPictureChild {
anchor: poly.anchor,
spatial_node_index,
gpu_address,
});
}
}
fn pre_update(
&mut self,
clip_chain_id: ClipChainId,
state: &mut PictureUpdateState,
frame_context: &FrameBuildingContext,
clip_store: &ClipStore,
clip_data_store: &ClipDataStore,
) -> Option<PrimitiveList> {
self.raster_config = None;
if !self.resolve_scene_properties(frame_context.scene_properties) {
return None;
}
state.push_picture(PictureInfo {
spatial_node_index: self.spatial_node_index,
});
let actual_composite_mode = match self.requested_composite_mode {
Some(PictureCompositeMode::Filter(filter)) if filter.is_noop() => None,
Some(PictureCompositeMode::Blit(reason)) if reason == BlitReason::CLIP => {
let mut apply_clip_to_picture = false;
let mut current_clip_chain_id = clip_chain_id;
while current_clip_chain_id != ClipChainId::NONE {
let clip_chain_node = &clip_store.clip_chain_nodes[current_clip_chain_id.0 as usize];
let clip_node = &clip_data_store[clip_chain_node.handle];
match clip_node.item {
ClipItem::Rectangle(_, ClipMode::Clip) => {
}
ClipItem::Rectangle(_, ClipMode::ClipOut) |
ClipItem::RoundedRectangle(..) |
ClipItem::Image { .. } |
ClipItem::BoxShadow(..) => {
apply_clip_to_picture = true;
break;
}
}
current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
}
if apply_clip_to_picture {
Some(PictureCompositeMode::Blit(reason))
} else {
None
}
}
mode => mode,
};
if let Some(composite_mode) = actual_composite_mode {
let parent_raster_node_index = state.current_surface().raster_spatial_node_index;
let surface_spatial_node_index = self.spatial_node_index;
let inflation_factor = match composite_mode {
PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
if self.options.inflate_if_required {
BLUR_SAMPLE_SCALE * blur_radius
} else {
0.0
}
}
_ => {
0.0
}
};
let establishes_raster_root = frame_context.clip_scroll_tree
.get_relative_transform(surface_spatial_node_index, parent_raster_node_index)
.expect("BUG: unable to get relative transform")
.is_perspective;
let surface = SurfaceInfo::new(
surface_spatial_node_index,
if establishes_raster_root {
surface_spatial_node_index
} else {
parent_raster_node_index
},
inflation_factor,
frame_context.screen_world_rect,
&frame_context.clip_scroll_tree,
);
self.raster_config = Some(RasterConfig {
composite_mode,
establishes_raster_root,
surface_index: state.push_surface(surface),
});
}
Some(mem::replace(&mut self.prim_list, PrimitiveList::empty()))
}
fn post_update(
&mut self,
prim_list: PrimitiveList,
state: &mut PictureUpdateState,
frame_context: &FrameBuildingContext,
gpu_cache: &mut GpuCache,
) {
self.prim_list = prim_list;
state.pop_picture();
for cluster in &mut self.prim_list.clusters {
if !cluster.is_backface_visible {
let containing_block_index = match self.context_3d {
Picture3DContext::Out => {
state.current_picture().map_or(ROOT_SPATIAL_NODE_INDEX, |info| {
info.spatial_node_index
})
}
Picture3DContext::In { root_data: Some(_), ancestor_index } => {
ancestor_index
}
Picture3DContext::In { root_data: None, ancestor_index } => {
ancestor_index
}
};
let map_local_to_containing_block: SpaceMapper<LayoutPixel, LayoutPixel> = SpaceMapper::new_with_target(
containing_block_index,
cluster.spatial_node_index,
LayoutRect::zero(),
&frame_context.clip_scroll_tree,
);
match map_local_to_containing_block.visible_face() {
VisibleFace::Back => continue,
VisibleFace::Front => {}
}
}
let spatial_node = &frame_context
.clip_scroll_tree
.spatial_nodes[cluster.spatial_node_index.0 as usize];
if !spatial_node.invertible {
continue;
}
let surface = state.current_surface_mut();
surface.map_local_to_surface.set_target_spatial_node(
cluster.spatial_node_index,
frame_context.clip_scroll_tree,
);
cluster.is_visible = true;
if let Some(cluster_rect) = surface.map_local_to_surface.map(&cluster.bounding_rect) {
surface.rect = surface.rect.union(&cluster_rect);
}
}
if let Some(ref mut raster_config) = self.raster_config {
let mut surface_rect = {
let surface = state.current_surface_mut();
let inflation_size = match raster_config.composite_mode {
PictureCompositeMode::Filter(FilterOp::Blur(_)) => surface.inflation_factor,
PictureCompositeMode::Filter(FilterOp::DropShadow(_, blur_radius, _)) =>
(blur_radius * BLUR_SAMPLE_SCALE).ceil(),
_ => 0.0,
};
surface.rect = surface.rect.inflate(inflation_size, inflation_size);
surface.rect * TypedScale::new(1.0)
};
let surface_index = state.pop_surface();
debug_assert_eq!(surface_index, raster_config.surface_index);
if self.local_rect != surface_rect {
if let PictureCompositeMode::Filter(FilterOp::DropShadow(..)) = raster_config.composite_mode {
gpu_cache.invalidate(&self.extra_gpu_data_handle);
}
self.local_rect = surface_rect;
}
if raster_config.establishes_raster_root {
if surface_rect.size.width > MAX_SURFACE_SIZE ||
surface_rect.size.height > MAX_SURFACE_SIZE
{
raster_config.establishes_raster_root = false;
state.are_raster_roots_assigned = false;
}
}
if let PictureCompositeMode::Filter(FilterOp::DropShadow(offset, ..)) = raster_config.composite_mode {
let content_rect = surface_rect;
let shadow_rect = surface_rect.translate(&offset);
surface_rect = content_rect.union(&shadow_rect);
}
let parent_surface = state.current_surface_mut();
parent_surface.map_local_to_surface.set_target_spatial_node(
self.spatial_node_index,
frame_context.clip_scroll_tree,
);
if let Some(parent_surface_rect) = parent_surface
.map_local_to_surface
.map(&surface_rect)
{
parent_surface.rect = parent_surface.rect.union(&parent_surface_rect);
}
}
}
pub fn prepare_for_render(
&mut self,
pic_index: PictureIndex,
prim_instance: &PrimitiveInstance,
clipped_prim_bounding_rect: WorldRect,
surface_index: SurfaceIndex,
frame_context: &FrameBuildingContext,
frame_state: &mut FrameBuildingState,
) -> bool {
let (mut pic_state_for_children, pic_context) = self.take_state_and_context();
if let Some(ref mut splitter) = pic_state_for_children.plane_splitter {
self.resolve_split_planes(splitter, frame_state);
}
let raster_config = match self.raster_config {
Some(ref mut raster_config) => raster_config,
None => {
return true
}
};
let (raster_spatial_node_index, child_tasks) = {
let surface_info = &mut frame_state.surfaces[raster_config.surface_index.0];
(surface_info.raster_spatial_node_index, surface_info.take_render_tasks())
};
let surfaces = &mut frame_state.surfaces;
let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
prim_instance.spatial_node_index,
raster_spatial_node_index,
frame_context.screen_world_rect,
frame_context.clip_scroll_tree,
);
let pic_rect = PictureRect::from_untyped(&self.local_rect.to_untyped());
let (clipped, unclipped) = match get_raster_rects(
pic_rect,
&map_pic_to_raster,
&map_raster_to_world,
clipped_prim_bounding_rect,
frame_context.device_pixel_scale,
) {
Some(info) => info,
None => return false,
};
let transform = map_pic_to_raster.get_transform();
let surface = match raster_config.composite_mode {
PictureCompositeMode::TileCache { .. } => {
let surface = &mut surfaces[surface_index.0];
surface.tasks.extend(child_tasks);
return true;
}
PictureCompositeMode::Filter(FilterOp::Blur(blur_radius)) => {
let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
let inflation_factor = surfaces[raster_config.surface_index.0].inflation_factor;
let inflation_factor = (inflation_factor * frame_context.device_pixel_scale.0).ceil() as i32;
let mut device_rect = clipped
.inflate(inflation_factor, inflation_factor)
.intersection(&unclipped.to_i32())
.unwrap();
device_rect.size = RenderTask::adjusted_blur_source_size(
device_rect.size,
blur_std_deviation,
);
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&device_rect,
frame_context.device_pixel_scale,
true,
);
let picture_task = RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, device_rect.size),
unclipped.size,
pic_index,
device_rect.origin,
child_tasks,
uv_rect_kind,
pic_context.raster_spatial_node_index,
);
let picture_task_id = frame_state.render_tasks.add(picture_task);
let blur_render_task = RenderTask::new_blur(
blur_std_deviation,
picture_task_id,
frame_state.render_tasks,
RenderTargetKind::Color,
ClearMode::Transparent,
);
let render_task_id = frame_state.render_tasks.add(blur_render_task);
surfaces[surface_index.0].tasks.push(render_task_id);
PictureSurface::RenderTask(render_task_id)
}
PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color)) => {
let blur_std_deviation = blur_radius * frame_context.device_pixel_scale.0;
let blur_range = (blur_std_deviation * BLUR_SAMPLE_SCALE).ceil() as i32;
let rounded_std_dev = blur_std_deviation.round();
let mut device_rect = clipped.inflate(blur_range, blur_range)
.intersection(&unclipped.to_i32())
.unwrap();
device_rect.size = RenderTask::adjusted_blur_source_size(
device_rect.size,
rounded_std_dev,
);
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&device_rect,
frame_context.device_pixel_scale,
true,
);
let mut picture_task = RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, device_rect.size),
unclipped.size,
pic_index,
device_rect.origin,
child_tasks,
uv_rect_kind,
pic_context.raster_spatial_node_index,
);
picture_task.mark_for_saving();
let picture_task_id = frame_state.render_tasks.add(picture_task);
let blur_render_task = RenderTask::new_blur(
rounded_std_dev,
picture_task_id,
frame_state.render_tasks,
RenderTargetKind::Color,
ClearMode::Transparent,
);
self.secondary_render_task_id = Some(picture_task_id);
let render_task_id = frame_state.render_tasks.add(blur_render_task);
surfaces[surface_index.0].tasks.push(render_task_id);
if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
let shadow_rect = self.local_rect.translate(&offset);
request.push(color.premultiplied());
request.push(PremultipliedColorF::WHITE);
request.push([
self.local_rect.size.width,
self.local_rect.size.height,
0.0,
0.0,
]);
request.push(shadow_rect);
request.push([0.0, 0.0, 0.0, 0.0]);
}
PictureSurface::RenderTask(render_task_id)
}
PictureCompositeMode::MixBlend(..) => {
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&clipped,
frame_context.device_pixel_scale,
true,
);
let picture_task = RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, clipped.size),
unclipped.size,
pic_index,
clipped.origin,
child_tasks,
uv_rect_kind,
pic_context.raster_spatial_node_index,
);
let readback_task_id = frame_state.render_tasks.add(
RenderTask::new_readback(clipped)
);
self.secondary_render_task_id = Some(readback_task_id);
surfaces[surface_index.0].tasks.push(readback_task_id);
let render_task_id = frame_state.render_tasks.add(picture_task);
surfaces[surface_index.0].tasks.push(render_task_id);
PictureSurface::RenderTask(render_task_id)
}
PictureCompositeMode::Filter(filter) => {
if let FilterOp::ColorMatrix(m) = filter {
if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
for i in 0..5 {
request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
}
}
}
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&clipped,
frame_context.device_pixel_scale,
true,
);
let picture_task = RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, clipped.size),
unclipped.size,
pic_index,
clipped.origin,
child_tasks,
uv_rect_kind,
pic_context.raster_spatial_node_index,
);
let render_task_id = frame_state.render_tasks.add(picture_task);
surfaces[surface_index.0].tasks.push(render_task_id);
PictureSurface::RenderTask(render_task_id)
}
PictureCompositeMode::Blit(_) => {
let supports_snapping = match self.context_3d {
Picture3DContext::In{ .. } => false,
_ => true,
};
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&clipped,
frame_context.device_pixel_scale,
supports_snapping,
);
let picture_task = RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, clipped.size),
unclipped.size,
pic_index,
clipped.origin,
child_tasks,
uv_rect_kind,
pic_context.raster_spatial_node_index,
);
let render_task_id = frame_state.render_tasks.add(picture_task);
surfaces[surface_index.0].tasks.push(render_task_id);
PictureSurface::RenderTask(render_task_id)
}
};
surfaces[raster_config.surface_index.0].surface = Some(surface);
true
}
}
fn calculate_screen_uv(
local_pos: &PicturePoint,
transform: &PictureToRasterTransform,
rendered_rect: &DeviceRect,
device_pixel_scale: DevicePixelScale,
supports_snapping: bool,
) -> DeviceHomogeneousVector {
let raster_pos = transform.transform_point2d_homogeneous(local_pos);
let mut device_vec = DeviceHomogeneousVector::new(
raster_pos.x * device_pixel_scale.0,
raster_pos.y * device_pixel_scale.0,
0.0,
raster_pos.w,
);
if transform.transform_kind() == TransformedRectKind::AxisAligned && supports_snapping {
device_vec = DeviceHomogeneousVector::new(
(device_vec.x / device_vec.w + 0.5).floor(),
(device_vec.y / device_vec.w + 0.5).floor(),
0.0,
1.0,
);
}
DeviceHomogeneousVector::new(
(device_vec.x - rendered_rect.origin.x * device_vec.w) / rendered_rect.size.width,
(device_vec.y - rendered_rect.origin.y * device_vec.w) / rendered_rect.size.height,
0.0,
device_vec.w,
)
}
fn calculate_uv_rect_kind(
pic_rect: &PictureRect,
transform: &PictureToRasterTransform,
rendered_rect: &DeviceIntRect,
device_pixel_scale: DevicePixelScale,
supports_snapping: bool,
) -> UvRectKind {
let rendered_rect = rendered_rect.to_f32();
let top_left = calculate_screen_uv(
&pic_rect.origin,
transform,
&rendered_rect,
device_pixel_scale,
supports_snapping,
);
let top_right = calculate_screen_uv(
&pic_rect.top_right(),
transform,
&rendered_rect,
device_pixel_scale,
supports_snapping,
);
let bottom_left = calculate_screen_uv(
&pic_rect.bottom_left(),
transform,
&rendered_rect,
device_pixel_scale,
supports_snapping,
);
let bottom_right = calculate_screen_uv(
&pic_rect.bottom_right(),
transform,
&rendered_rect,
device_pixel_scale,
supports_snapping,
);
UvRectKind::Quad {
top_left,
top_right,
bottom_left,
bottom_right,
}
}
fn create_raster_mappers(
surface_spatial_node_index: SpatialNodeIndex,
raster_spatial_node_index: SpatialNodeIndex,
world_rect: WorldRect,
clip_scroll_tree: &ClipScrollTree,
) -> (SpaceMapper<RasterPixel, WorldPixel>, SpaceMapper<PicturePixel, RasterPixel>) {
let map_raster_to_world = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
raster_spatial_node_index,
world_rect,
clip_scroll_tree,
);
let raster_bounds = map_raster_to_world.unmap(&world_rect)
.unwrap_or(RasterRect::max_rect());
let map_pic_to_raster = SpaceMapper::new_with_target(
raster_spatial_node_index,
surface_spatial_node_index,
raster_bounds,
clip_scroll_tree,
);
(map_raster_to_world, map_pic_to_raster)
}
fn build_ref_prims(
ref_prims: &[ReferencePrimitive],
prim_map: &mut FastHashMap<ItemUid, WorldPoint>,
clip_scroll_tree: &ClipScrollTree,
) {
prim_map.clear();
let mut map_local_to_world = SpaceMapper::new(
ROOT_SPATIAL_NODE_INDEX,
WorldRect::zero(),
);
for ref_prim in ref_prims {
map_local_to_world.set_target_spatial_node(
ref_prim.spatial_node_index,
clip_scroll_tree,
);
let rect = LayoutRect::new(
ref_prim.local_pos,
LayoutSize::zero(),
);
if let Some(rect) = map_local_to_world.map(&rect) {
prim_map.insert(ref_prim.uid, rect.origin);
}
}
}
fn correlate_prim_maps(
old_prims: &FastHashMap<ItemUid, WorldPoint>,
new_prims: &FastHashMap<ItemUid, WorldPoint>,
) -> Option<WorldVector2D> {
let mut map: FastHashMap<VectorKey, usize> = FastHashMap::default();
for (uid, old_point) in old_prims {
if let Some(new_point) = new_prims.get(uid) {
let key = (*new_point - *old_point).round().into();
let key_count = map.entry(key).or_insert(0);
*key_count += 1;
}
}
map.into_iter()
.max_by_key(|&(_, count)| count)
.and_then(|(offset, count)| {
let prims_available = new_prims.len().min(old_prims.len());
if count >= prims_available / 4 {
Some(offset.into())
} else {
None
}
})
}