use api::{MixBlendMode, PipelineId, PremultipliedColorF, FilterPrimitiveKind};
use api::{PropertyBinding, PropertyBindingId, FilterPrimitive, FontRenderMode};
use api::{DebugFlags, RasterSpace, ImageKey, ColorF, ColorU, PrimitiveFlags};
use api::units::*;
use crate::box_shadow::BLUR_SAMPLE_SCALE;
use crate::clip::{ClipStore, ClipChainInstance, ClipDataHandle, ClipChainId};
use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX,
SpatialTree, CoordinateSpaceMapping, SpatialNodeIndex, VisibleFace
};
use crate::composite::{CompositorKind, CompositeState, NativeSurfaceId, NativeTileId};
use crate::composite::{ExternalSurfaceDescriptor};
use crate::debug_colors;
use euclid::{vec2, vec3, Point2D, Scale, Size2D, Vector2D, Rect, Transform3D, SideOffsets2D};
use euclid::approxeq::ApproxEq;
use crate::filterdata::SFilterData;
use crate::frame_builder::{FrameBuilderConfig, FrameVisibilityContext, FrameVisibilityState};
use crate::intern::ItemUid;
use crate::internal_types::{FastHashMap, FastHashSet, PlaneSplitter, Filter, PlaneSplitAnchor, TextureSource};
use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
use crate::gpu_types::{UvRectKind, ZBufferId};
use plane_split::{Clipper, Polygon, Splitter};
use crate::prim_store::{SpaceMapper, PrimitiveVisibilityMask, PointKey, PrimitiveTemplateKind};
use crate::prim_store::{SpaceSnapper, PictureIndex, PrimitiveInstance, PrimitiveInstanceKind};
use crate::prim_store::{get_raster_rects, PrimitiveScratchBuffer, RectangleKey};
use crate::prim_store::{OpacityBindingStorage, ImageInstanceStorage, OpacityBindingIndex};
use crate::prim_store::{ColorBindingStorage, ColorBindingIndex, PrimitiveVisibilityFlags};
use crate::print_tree::{PrintTree, PrintTreePrinter};
use crate::render_backend::{DataStores, FrameId};
use crate::render_task_graph::RenderTaskId;
use crate::render_target::RenderTargetKind;
use crate::render_task::{RenderTask, RenderTaskLocation, BlurTaskCache, ClearMode};
use crate::resource_cache::{ResourceCache, ImageGeneration};
use crate::scene::SceneProperties;
use crate::spatial_tree::CoordinateSystemId;
use smallvec::SmallVec;
use std::{mem, u8, marker, u32};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::collections::hash_map::Entry;
use crate::texture_cache::TextureCacheHandle;
use crate::util::{MaxRect, VecHelper, RectHelpers, MatrixHelpers};
use crate::filterdata::{FilterDataHandle};
#[cfg(any(feature = "capture", feature = "replay"))]
use ron;
#[cfg(feature = "capture")]
use crate::scene_builder_thread::InternerUpdates;
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::intern::{Internable, UpdateList};
#[cfg(any(feature = "capture", feature = "replay"))]
use api::{ClipIntern, FilterDataIntern, PrimitiveKeyKind};
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::backdrop::Backdrop;
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::gradient::{LinearGradient, RadialGradient, ConicGradient};
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::image::{Image, YuvImage};
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::line_dec::LineDecoration;
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::picture::Picture;
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::prim_store::text_run::TextRun;
#[cfg(feature = "capture")]
use std::fs::File;
#[cfg(feature = "capture")]
use std::io::prelude::*;
#[cfg(feature = "capture")]
use std::path::PathBuf;
use crate::scene_building::{SliceFlags};
#[cfg(feature = "replay")]
use std::collections::HashMap;
pub const MAX_BLUR_RADIUS: f32 = 100.;
#[derive(Debug, Clone, PartialEq)]
pub enum SubpixelMode {
Allow,
Deny,
Conditional {
excluded_rects: Vec<PictureRect>,
},
}
#[derive(Debug, Clone)]
struct MatrixKey {
m: [f32; 16],
}
impl PartialEq for MatrixKey {
fn eq(&self, other: &Self) -> bool {
const EPSILON: f32 = 0.001;
for (i, j) in self.m.iter().zip(other.m.iter()) {
if !i.approx_eq_eps(j, &EPSILON) {
return false;
}
}
true
}
}
#[derive(Debug, PartialEq, Clone)]
enum TransformKey {
Local,
ScaleOffset {
scale_x: f32,
scale_y: f32,
offset_x: f32,
offset_y: f32,
},
Transform {
m: MatrixKey,
}
}
impl<Src, Dst> From<CoordinateSpaceMapping<Src, Dst>> for TransformKey {
fn from(transform: CoordinateSpaceMapping<Src, Dst>) -> TransformKey {
match transform {
CoordinateSpaceMapping::Local => {
TransformKey::Local
}
CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
TransformKey::ScaleOffset {
scale_x: scale_offset.scale.x,
scale_y: scale_offset.scale.y,
offset_x: scale_offset.offset.x,
offset_y: scale_offset.offset.y,
}
}
CoordinateSpaceMapping::Transform(ref m) => {
TransformKey::Transform {
m: MatrixKey {
m: m.to_row_major_array(),
},
}
}
}
}
}
struct PictureInfo {
_spatial_node_index: SpatialNodeIndex,
}
pub struct PictureCacheState {
pub tiles: FastHashMap<TileOffset, Box<Tile>>,
spatial_node_comparer: SpatialNodeComparer,
opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
root_transform: TransformKey,
current_tile_size: DeviceIntSize,
allocations: PictureCacheRecycledAllocations,
pub native_surface: Option<NativeSurface>,
pub external_native_surface_cache: FastHashMap<ExternalNativeSurfaceKey, ExternalNativeSurface>,
virtual_offset: DeviceIntPoint,
frame_id: FrameId,
}
pub struct PictureCacheRecycledAllocations {
old_opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
old_color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
}
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct RetainedTiles {
#[cfg_attr(feature = "capture", serde(skip))]
pub caches: FastHashMap<usize, PictureCacheState>,
}
impl RetainedTiles {
pub fn new() -> Self {
RetainedTiles {
caches: FastHashMap::default(),
}
}
pub fn merge(&mut self, other: RetainedTiles) {
assert!(self.caches.is_empty() || other.caches.is_empty());
if self.caches.is_empty() {
self.caches = other.caches;
}
}
}
#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct TileCoordinate;
pub type TileOffset = Point2D<i32, TileCoordinate>;
pub type TileSize = Size2D<i32, TileCoordinate>;
pub type TileRect = Rect<i32, TileCoordinate>;
const MAX_COMPOSITOR_SURFACES: usize = 4;
pub const TILE_SIZE_DEFAULT: DeviceIntSize = DeviceIntSize {
width: 1024,
height: 512,
_unit: marker::PhantomData,
};
pub const TILE_SIZE_SCROLLBAR_HORIZONTAL: DeviceIntSize = DeviceIntSize {
width: 512,
height: 16,
_unit: marker::PhantomData,
};
pub const TILE_SIZE_SCROLLBAR_VERTICAL: DeviceIntSize = DeviceIntSize {
width: 16,
height: 512,
_unit: marker::PhantomData,
};
const TILE_SIZE_FOR_TESTS: [DeviceIntSize; 6] = [
DeviceIntSize {
width: 128,
height: 128,
_unit: marker::PhantomData,
},
DeviceIntSize {
width: 256,
height: 256,
_unit: marker::PhantomData,
},
DeviceIntSize {
width: 512,
height: 512,
_unit: marker::PhantomData,
},
TILE_SIZE_DEFAULT,
TILE_SIZE_SCROLLBAR_VERTICAL,
TILE_SIZE_SCROLLBAR_HORIZONTAL,
];
pub fn tile_cache_sizes(testing: bool) -> &'static [DeviceIntSize] {
if testing {
&TILE_SIZE_FOR_TESTS
} else {
&[
TILE_SIZE_DEFAULT,
TILE_SIZE_SCROLLBAR_HORIZONTAL,
TILE_SIZE_SCROLLBAR_VERTICAL,
]
}
}
const MAX_SURFACE_SIZE: f32 = 4096.0;
const MAX_PRIM_SUB_DEPS: usize = u8::MAX as usize;
static NEXT_TILE_ID: AtomicUsize = AtomicUsize::new(0);
fn clamp(value: i32, low: i32, high: i32) -> i32 {
value.max(low).min(high)
}
fn clampf(value: f32, low: f32, high: f32) -> f32 {
value.max(low).min(high)
}
fn clamp_blur_radius(blur_radius: f32, scale_factors: (f32, f32)) -> f32 {
let largest_scale_factor = f32::max(scale_factors.0, scale_factors.1);
let adjusted_blur_radius = blur_radius * largest_scale_factor;
let clamped_blur_radius = f32::min(adjusted_blur_radius, MAX_BLUR_RADIUS);
clamped_blur_radius / largest_scale_factor
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct PrimitiveDependencyIndex(pub u32);
#[derive(Debug)]
pub struct BindingInfo<T> {
value: T,
changed: bool,
}
#[derive(Debug, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum Binding<T> {
Value(T),
Binding(PropertyBindingId),
}
impl<T> From<PropertyBinding<T>> for Binding<T> {
fn from(binding: PropertyBinding<T>) -> Binding<T> {
match binding {
PropertyBinding::Binding(key, _) => Binding::Binding(key.id),
PropertyBinding::Value(value) => Binding::Value(value),
}
}
}
pub type OpacityBinding = Binding<f32>;
pub type OpacityBindingInfo = BindingInfo<f32>;
pub type ColorBinding = Binding<ColorU>;
pub type ColorBindingInfo = BindingInfo<ColorU>;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct SpatialNodeKey {
spatial_node_index: SpatialNodeIndex,
frame_id: FrameId,
}
struct SpatialNodeComparer {
ref_spatial_node_index: SpatialNodeIndex,
spatial_nodes: FastHashMap<SpatialNodeKey, TransformKey>,
compare_cache: FastHashMap<(SpatialNodeKey, SpatialNodeKey), bool>,
referenced_frames: FastHashSet<FrameId>,
}
impl SpatialNodeComparer {
fn new() -> Self {
SpatialNodeComparer {
ref_spatial_node_index: ROOT_SPATIAL_NODE_INDEX,
spatial_nodes: FastHashMap::default(),
compare_cache: FastHashMap::default(),
referenced_frames: FastHashSet::default(),
}
}
fn next_frame(
&mut self,
ref_spatial_node_index: SpatialNodeIndex,
) {
let referenced_frames = &self.referenced_frames;
self.spatial_nodes.retain(|key, _| {
referenced_frames.contains(&key.frame_id)
});
self.ref_spatial_node_index = ref_spatial_node_index;
self.compare_cache.clear();
self.referenced_frames.clear();
}
fn register_used_transform(
&mut self,
spatial_node_index: SpatialNodeIndex,
frame_id: FrameId,
spatial_tree: &SpatialTree,
) {
let key = SpatialNodeKey {
spatial_node_index,
frame_id,
};
if let Entry::Vacant(entry) = self.spatial_nodes.entry(key) {
entry.insert(
get_transform_key(
spatial_node_index,
self.ref_spatial_node_index,
spatial_tree,
)
);
}
}
fn are_transforms_equivalent(
&mut self,
prev_spatial_node_key: &SpatialNodeKey,
curr_spatial_node_key: &SpatialNodeKey,
) -> bool {
let key = (*prev_spatial_node_key, *curr_spatial_node_key);
let spatial_nodes = &self.spatial_nodes;
*self.compare_cache
.entry(key)
.or_insert_with(|| {
let prev = &spatial_nodes[&prev_spatial_node_key];
let curr = &spatial_nodes[&curr_spatial_node_key];
curr == prev
})
}
fn retain_for_frame(&mut self, frame_id: FrameId) {
self.referenced_frames.insert(frame_id);
}
}
struct TilePreUpdateContext {
pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
fract_offset: PictureVector2D,
background_color: Option<ColorF>,
global_screen_world_rect: WorldRect,
tile_size: PictureSize,
frame_id: FrameId,
}
struct TilePostUpdateContext<'a> {
pic_to_world_mapper: SpaceMapper<PicturePixel, WorldPixel>,
global_device_pixel_scale: DevicePixelScale,
local_clip_rect: PictureRect,
backdrop: BackdropInfo,
opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
current_tile_size: DeviceIntSize,
local_rect: PictureRect,
external_surfaces: &'a [ExternalSurfaceDescriptor],
z_id_opaque: ZBufferId,
z_id_alpha: ZBufferId,
}
struct TilePostUpdateState<'a> {
resource_cache: &'a mut ResourceCache,
composite_state: &'a mut CompositeState,
compare_cache: &'a mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
spatial_node_comparer: &'a mut SpatialNodeComparer,
}
struct PrimitiveDependencyInfo {
clip_by_tile: bool,
prim_uid: ItemUid,
prim_origin: PicturePoint,
prim_clip_rect: PictureRect,
images: SmallVec<[ImageDependency; 8]>,
opacity_bindings: SmallVec<[OpacityBinding; 4]>,
color_binding: Option<ColorBinding>,
clips: SmallVec<[ItemUid; 8]>,
spatial_nodes: SmallVec<[SpatialNodeIndex; 4]>,
is_compositor_surface: bool,
}
impl PrimitiveDependencyInfo {
fn new(
prim_uid: ItemUid,
prim_origin: PicturePoint,
prim_clip_rect: PictureRect,
) -> Self {
PrimitiveDependencyInfo {
prim_uid,
prim_origin,
images: SmallVec::new(),
opacity_bindings: SmallVec::new(),
color_binding: None,
clip_by_tile: false,
prim_clip_rect,
clips: SmallVec::new(),
spatial_nodes: SmallVec::new(),
is_compositor_surface: false,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct TileId(pub usize);
#[derive(Debug)]
pub enum SurfaceTextureDescriptor {
TextureCache {
handle: TextureCacheHandle
},
Native {
id: Option<NativeTileId>,
},
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum ResolvedSurfaceTexture {
TextureCache {
texture: TextureSource,
layer: i32,
},
Native {
id: NativeTileId,
size: DeviceIntSize,
}
}
impl SurfaceTextureDescriptor {
pub fn resolve(
&self,
resource_cache: &ResourceCache,
size: DeviceIntSize,
) -> ResolvedSurfaceTexture {
match self {
SurfaceTextureDescriptor::TextureCache { handle } => {
let cache_item = resource_cache.texture_cache.get(handle);
ResolvedSurfaceTexture::TextureCache {
texture: cache_item.texture_id,
layer: cache_item.texture_layer,
}
}
SurfaceTextureDescriptor::Native { id } => {
ResolvedSurfaceTexture::Native {
id: id.expect("bug: native surface not allocated"),
size,
}
}
}
}
}
#[derive(Debug)]
pub enum TileSurface {
Texture {
descriptor: SurfaceTextureDescriptor,
visibility_mask: PrimitiveVisibilityMask,
},
Color {
color: ColorF,
},
Clear,
}
impl TileSurface {
fn kind(&self) -> &'static str {
match *self {
TileSurface::Color { .. } => "Color",
TileSurface::Texture { .. } => "Texture",
TileSurface::Clear => "Clear",
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum CompareHelperResult<T> {
Equal,
Count {
prev_count: u8,
curr_count: u8,
},
Sentinel,
NotEqual {
prev: T,
curr: T,
},
PredicateTrue {
curr: T
},
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[repr(u8)]
pub enum PrimitiveCompareResult {
Equal,
Descriptor,
Clip,
Transform,
Image,
OpacityBinding,
ColorBinding,
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum PrimitiveCompareResultDetail {
Equal,
Descriptor {
old: PrimitiveDescriptor,
new: PrimitiveDescriptor,
},
Clip {
detail: CompareHelperResult<ItemUid>,
},
Transform {
detail: CompareHelperResult<SpatialNodeKey>,
},
Image {
detail: CompareHelperResult<ImageDependency>,
},
OpacityBinding {
detail: CompareHelperResult<OpacityBinding>,
},
ColorBinding {
detail: CompareHelperResult<ColorBinding>,
},
}
#[derive(Debug,Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum InvalidationReason {
FractionalOffset {
old: PictureVector2D,
new: PictureVector2D,
},
BackgroundColor {
old: Option<ColorF>,
new: Option<ColorF>,
},
SurfaceOpacityChanged{
became_opaque: bool
},
NoTexture,
NoSurface,
PrimCount {
old: Option<Vec<ItemUid>>,
new: Option<Vec<ItemUid>>,
},
Content {
prim_compare_result: PrimitiveCompareResult,
prim_compare_result_detail: Option<PrimitiveCompareResultDetail>,
},
CompositorKindChanged,
ValidRectChanged,
}
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct TileSerializer {
pub rect: PictureRect,
pub current_descriptor: TileDescriptor,
pub fract_offset: PictureVector2D,
pub id: TileId,
pub root: TileNode,
pub background_color: Option<ColorF>,
pub invalidation_reason: Option<InvalidationReason>
}
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct TileCacheInstanceSerializer {
pub slice: usize,
pub tiles: FastHashMap<TileOffset, TileSerializer>,
pub background_color: Option<ColorF>,
pub fract_offset: PictureVector2D,
}
pub struct Tile {
pub tile_offset: TileOffset,
pub world_tile_rect: WorldRect,
pub local_tile_rect: PictureRect,
local_dirty_rect: PictureRect,
pub device_dirty_rect: DeviceRect,
pub device_valid_rect: DeviceRect,
pub current_descriptor: TileDescriptor,
pub prev_descriptor: TileDescriptor,
pub surface: Option<TileSurface>,
pub is_valid: bool,
pub is_visible: bool,
fract_offset: PictureVector2D,
pub id: TileId,
pub is_opaque: bool,
root: TileNode,
background_color: Option<ColorF>,
invalidation_reason: Option<InvalidationReason>,
pub has_compositor_surface: bool,
bg_local_valid_rect: PictureRect,
fg_local_valid_rect: PictureRect,
pub z_id: ZBufferId,
pub last_updated_frame_id: FrameId,
}
impl Tile {
fn new(tile_offset: TileOffset) -> Self {
let id = TileId(NEXT_TILE_ID.fetch_add(1, Ordering::Relaxed));
Tile {
tile_offset,
local_tile_rect: PictureRect::zero(),
world_tile_rect: WorldRect::zero(),
device_valid_rect: DeviceRect::zero(),
local_dirty_rect: PictureRect::zero(),
device_dirty_rect: DeviceRect::zero(),
surface: None,
current_descriptor: TileDescriptor::new(),
prev_descriptor: TileDescriptor::new(),
is_valid: false,
is_visible: false,
fract_offset: PictureVector2D::zero(),
id,
is_opaque: false,
root: TileNode::new_leaf(Vec::new()),
background_color: None,
invalidation_reason: None,
has_compositor_surface: false,
bg_local_valid_rect: PictureRect::zero(),
fg_local_valid_rect: PictureRect::zero(),
z_id: ZBufferId::invalid(),
last_updated_frame_id: FrameId::INVALID,
}
}
fn print(&self, pt: &mut dyn PrintTreePrinter) {
pt.new_level(format!("Tile {:?}", self.id));
pt.add_item(format!("local_tile_rect: {}", self.local_tile_rect));
pt.add_item(format!("fract_offset: {:?}", self.fract_offset));
pt.add_item(format!("background_color: {:?}", self.background_color));
pt.add_item(format!("invalidation_reason: {:?}", self.invalidation_reason));
self.current_descriptor.print(pt);
pt.end_level();
}
fn update_dirty_rects(
&mut self,
ctx: &TilePostUpdateContext,
state: &mut TilePostUpdateState,
invalidation_reason: &mut Option<InvalidationReason>,
frame_context: &FrameVisibilityContext,
) -> PictureRect {
let mut prim_comparer = PrimitiveComparer::new(
&self.prev_descriptor,
&self.current_descriptor,
state.resource_cache,
state.spatial_node_comparer,
ctx.opacity_bindings,
ctx.color_bindings,
);
let mut dirty_rect = PictureRect::zero();
self.root.update_dirty_rects(
&self.prev_descriptor.prims,
&self.current_descriptor.prims,
&mut prim_comparer,
&mut dirty_rect,
state.compare_cache,
invalidation_reason,
frame_context,
);
dirty_rect
}
fn update_content_validity(
&mut self,
ctx: &TilePostUpdateContext,
state: &mut TilePostUpdateState,
frame_context: &FrameVisibilityContext,
) {
state.compare_cache.clear();
let mut invalidation_reason = None;
let dirty_rect = self.update_dirty_rects(
ctx,
state,
&mut invalidation_reason,
frame_context,
);
if !dirty_rect.is_empty() {
self.invalidate(
Some(dirty_rect),
invalidation_reason.expect("bug: no invalidation_reason"),
);
}
if self.current_descriptor.local_valid_rect != self.prev_descriptor.local_valid_rect {
self.invalidate(None, InvalidationReason::ValidRectChanged);
state.composite_state.dirty_rects_are_valid = false;
}
}
fn invalidate(
&mut self,
invalidation_rect: Option<PictureRect>,
reason: InvalidationReason,
) {
self.is_valid = false;
match invalidation_rect {
Some(rect) => {
self.local_dirty_rect = self.local_dirty_rect.union(&rect);
}
None => {
self.local_dirty_rect = self.local_tile_rect;
}
}
if self.invalidation_reason.is_none() {
self.invalidation_reason = Some(reason);
}
}
fn pre_update(
&mut self,
ctx: &TilePreUpdateContext,
) {
self.local_tile_rect = PictureRect::new(
PicturePoint::new(
self.tile_offset.x as f32 * ctx.tile_size.width + ctx.fract_offset.x,
self.tile_offset.y as f32 * ctx.tile_size.height + ctx.fract_offset.y,
),
ctx.tile_size,
);
self.bg_local_valid_rect = PictureRect::zero();
self.fg_local_valid_rect = PictureRect::zero();
self.invalidation_reason = None;
self.has_compositor_surface = false;
self.world_tile_rect = ctx.pic_to_world_mapper
.map(&self.local_tile_rect)
.expect("bug: map local tile rect");
self.is_visible = self.world_tile_rect.intersects(&ctx.global_screen_world_rect);
if !self.is_visible {
return;
}
let fract_changed = (self.fract_offset.x - ctx.fract_offset.x).abs() > 0.01 ||
(self.fract_offset.y - ctx.fract_offset.y).abs() > 0.01;
if fract_changed {
self.invalidate(None, InvalidationReason::FractionalOffset {
old: self.fract_offset,
new: ctx.fract_offset });
self.fract_offset = ctx.fract_offset;
}
if ctx.background_color != self.background_color {
self.invalidate(None, InvalidationReason::BackgroundColor {
old: self.background_color,
new: ctx.background_color });
self.background_color = ctx.background_color;
}
mem::swap(
&mut self.current_descriptor,
&mut self.prev_descriptor,
);
self.current_descriptor.clear();
self.root.clear(self.local_tile_rect);
self.last_updated_frame_id = ctx.frame_id;
}
fn add_prim_dependency(
&mut self,
info: &PrimitiveDependencyInfo,
) {
if !self.is_visible {
return;
}
if info.is_compositor_surface {
self.has_compositor_surface = true;
} else {
if self.has_compositor_surface {
self.fg_local_valid_rect = self.fg_local_valid_rect.union(&info.prim_clip_rect);
} else {
self.bg_local_valid_rect = self.bg_local_valid_rect.union(&info.prim_clip_rect);
}
}
self.current_descriptor.images.extend_from_slice(&info.images);
self.current_descriptor.opacity_bindings.extend_from_slice(&info.opacity_bindings);
self.current_descriptor.clips.extend_from_slice(&info.clips);
for spatial_node_index in &info.spatial_nodes {
self.current_descriptor.transforms.push(
SpatialNodeKey {
spatial_node_index: *spatial_node_index,
frame_id: self.last_updated_frame_id,
}
);
}
if info.color_binding.is_some() {
self.current_descriptor.color_bindings.insert(
self.current_descriptor.color_bindings.len(), info.color_binding.unwrap());
}
let (prim_origin, prim_clip_rect) = if info.clip_by_tile {
let tile_p0 = self.local_tile_rect.origin;
let tile_p1 = self.local_tile_rect.bottom_right();
let clip_p0 = PicturePoint::new(
clampf(info.prim_clip_rect.origin.x, tile_p0.x, tile_p1.x),
clampf(info.prim_clip_rect.origin.y, tile_p0.y, tile_p1.y),
);
let clip_p1 = PicturePoint::new(
clampf(info.prim_clip_rect.origin.x + info.prim_clip_rect.size.width, tile_p0.x, tile_p1.x),
clampf(info.prim_clip_rect.origin.y + info.prim_clip_rect.size.height, tile_p0.y, tile_p1.y),
);
(
PicturePoint::new(
clampf(info.prim_origin.x, tile_p0.x, tile_p1.x),
clampf(info.prim_origin.y, tile_p0.y, tile_p1.y),
),
PictureRect::new(
clip_p0,
PictureSize::new(
clip_p1.x - clip_p0.x,
clip_p1.y - clip_p0.y,
),
),
)
} else {
(info.prim_origin, info.prim_clip_rect)
};
let prim_index = PrimitiveDependencyIndex(self.current_descriptor.prims.len() as u32);
debug_assert!(info.spatial_nodes.len() <= MAX_PRIM_SUB_DEPS);
debug_assert!(info.clips.len() <= MAX_PRIM_SUB_DEPS);
debug_assert!(info.images.len() <= MAX_PRIM_SUB_DEPS);
debug_assert!(info.opacity_bindings.len() <= MAX_PRIM_SUB_DEPS);
self.current_descriptor.prims.push(PrimitiveDescriptor {
prim_uid: info.prim_uid,
origin: prim_origin.into(),
prim_clip_rect: prim_clip_rect.into(),
transform_dep_count: info.spatial_nodes.len() as u8,
clip_dep_count: info.clips.len() as u8,
image_dep_count: info.images.len() as u8,
opacity_binding_dep_count: info.opacity_bindings.len() as u8,
color_binding_dep_count: if info.color_binding.is_some() { 1 } else { 0 } as u8,
});
self.root.add_prim(prim_index, &info.prim_clip_rect);
}
fn post_update(
&mut self,
ctx: &TilePostUpdateContext,
state: &mut TilePostUpdateState,
frame_context: &FrameVisibilityContext,
) -> bool {
state.spatial_node_comparer.retain_for_frame(self.last_updated_frame_id);
if !self.is_visible {
return false;
}
self.current_descriptor.local_valid_rect =
self.bg_local_valid_rect.union(&self.fg_local_valid_rect);
self.current_descriptor.local_valid_rect = self.local_tile_rect
.intersection(&ctx.local_rect)
.and_then(|r| r.intersection(&self.current_descriptor.local_valid_rect))
.unwrap_or_else(PictureRect::zero);
self.update_content_validity(ctx, state, frame_context);
if self.current_descriptor.prims.is_empty() {
if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { mut id, .. }, .. }) = self.surface.take() {
if let Some(id) = id.take() {
state.resource_cache.destroy_compositor_tile(id);
}
}
self.is_visible = false;
return false;
}
let world_valid_rect = ctx.pic_to_world_mapper
.map(&self.current_descriptor.local_valid_rect)
.expect("bug: map local valid rect");
let device_rect = (self.world_tile_rect * ctx.global_device_pixel_scale).round();
self.device_valid_rect = (world_valid_rect * ctx.global_device_pixel_scale)
.round_out()
.intersection(&device_rect)
.unwrap_or_else(DeviceRect::zero);
let clipped_rect = self.current_descriptor.local_valid_rect
.intersection(&ctx.local_clip_rect)
.unwrap_or_else(PictureRect::zero);
let mut is_opaque = ctx.backdrop.opaque_rect.contains_rect(&clipped_rect);
if self.has_compositor_surface {
let fg_world_valid_rect = ctx.pic_to_world_mapper
.map(&self.fg_local_valid_rect)
.expect("bug: map fg local valid rect");
let fg_device_valid_rect = fg_world_valid_rect * ctx.global_device_pixel_scale;
for surface in ctx.external_surfaces {
if surface.device_rect.intersects(&fg_device_valid_rect) {
is_opaque = false;
break;
}
}
}
if is_opaque {
self.z_id = ctx.z_id_opaque;
} else {
self.z_id = ctx.z_id_alpha;
}
if is_opaque != self.is_opaque {
if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = self.surface {
if let Some(id) = id.take() {
state.resource_cache.destroy_compositor_tile(id);
}
}
self.invalidate(None, InvalidationReason::SurfaceOpacityChanged { became_opaque: is_opaque });
self.is_opaque = is_opaque;
}
let (supports_dirty_rects, supports_simple_prims) = match state.composite_state.compositor_kind {
CompositorKind::Draw { .. } => {
(true, true)
}
CompositorKind::Native { max_update_rects, .. } => {
(max_update_rects > 0, false)
}
};
if supports_dirty_rects {
if ctx.current_tile_size == TILE_SIZE_DEFAULT {
let max_split_level = 3;
self.root.maybe_merge_or_split(
0,
&self.current_descriptor.prims,
max_split_level,
);
}
}
if !self.is_valid && !supports_dirty_rects {
self.local_dirty_rect = self.local_tile_rect;
}
let is_simple_prim =
ctx.backdrop.kind.is_some() &&
self.current_descriptor.prims.len() == 1 &&
self.is_opaque &&
supports_simple_prims;
let surface = if is_simple_prim {
match ctx.backdrop.kind {
Some(BackdropKind::Color { color }) => {
TileSurface::Color {
color,
}
}
Some(BackdropKind::Clear) => {
TileSurface::Clear
}
None => {
unreachable!();
}
}
} else {
match self.surface.take() {
Some(TileSurface::Texture { descriptor, visibility_mask }) => {
TileSurface::Texture {
descriptor,
visibility_mask,
}
}
Some(TileSurface::Color { .. }) | Some(TileSurface::Clear) | None => {
let descriptor = match state.composite_state.compositor_kind {
CompositorKind::Draw { .. } => {
SurfaceTextureDescriptor::TextureCache {
handle: TextureCacheHandle::invalid(),
}
}
CompositorKind::Native { .. } => {
SurfaceTextureDescriptor::Native {
id: None,
}
}
};
TileSurface::Texture {
descriptor,
visibility_mask: PrimitiveVisibilityMask::empty(),
}
}
}
};
self.surface = Some(surface);
true
}
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct PrimitiveDescriptor {
pub prim_uid: ItemUid,
pub origin: PointKey,
pub prim_clip_rect: RectangleKey,
transform_dep_count: u8,
image_dep_count: u8,
opacity_binding_dep_count: u8,
clip_dep_count: u8,
color_binding_dep_count: u8,
}
impl PartialEq for PrimitiveDescriptor {
fn eq(&self, other: &Self) -> bool {
const EPSILON: f32 = 0.001;
if self.prim_uid != other.prim_uid {
return false;
}
if !self.origin.x.approx_eq_eps(&other.origin.x, &EPSILON) {
return false;
}
if !self.origin.y.approx_eq_eps(&other.origin.y, &EPSILON) {
return false;
}
if !self.prim_clip_rect.x.approx_eq_eps(&other.prim_clip_rect.x, &EPSILON) {
return false;
}
if !self.prim_clip_rect.y.approx_eq_eps(&other.prim_clip_rect.y, &EPSILON) {
return false;
}
if !self.prim_clip_rect.w.approx_eq_eps(&other.prim_clip_rect.w, &EPSILON) {
return false;
}
if !self.prim_clip_rect.h.approx_eq_eps(&other.prim_clip_rect.h, &EPSILON) {
return false;
}
true
}
}
struct CompareHelper<'a, T> where T: Copy {
offset_curr: usize,
offset_prev: usize,
curr_items: &'a [T],
prev_items: &'a [T],
}
impl<'a, T> CompareHelper<'a, T> where T: Copy + PartialEq {
fn new(
prev_items: &'a [T],
curr_items: &'a [T],
) -> Self {
CompareHelper {
offset_curr: 0,
offset_prev: 0,
curr_items,
prev_items,
}
}
fn reset(&mut self) {
self.offset_prev = 0;
self.offset_curr = 0;
}
fn is_same<F>(
&self,
prev_count: u8,
curr_count: u8,
mut f: F,
opt_detail: Option<&mut CompareHelperResult<T>>,
) -> bool where F: FnMut(&T, &T) -> bool {
if prev_count != curr_count {
if let Some(detail) = opt_detail { *detail = CompareHelperResult::Count{ prev_count, curr_count }; }
return false;
}
if curr_count == 0 {
if let Some(detail) = opt_detail { *detail = CompareHelperResult::Equal; }
return true;
}
if curr_count as usize == MAX_PRIM_SUB_DEPS {
if let Some(detail) = opt_detail { *detail = CompareHelperResult::Sentinel; }
return false;
}
let end_prev = self.offset_prev + prev_count as usize;
let end_curr = self.offset_curr + curr_count as usize;
let curr_items = &self.curr_items[self.offset_curr .. end_curr];
let prev_items = &self.prev_items[self.offset_prev .. end_prev];
for (curr, prev) in curr_items.iter().zip(prev_items.iter()) {
if !f(prev, curr) {
if let Some(detail) = opt_detail { *detail = CompareHelperResult::PredicateTrue{ curr: *curr }; }
return false;
}
}
if let Some(detail) = opt_detail { *detail = CompareHelperResult::Equal; }
true
}
fn advance_prev(&mut self, count: u8) {
self.offset_prev += count as usize;
}
fn advance_curr(&mut self, count: u8) {
self.offset_curr += count as usize;
}
}
#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct TileDescriptor {
pub prims: Vec<PrimitiveDescriptor>,
clips: Vec<ItemUid>,
images: Vec<ImageDependency>,
opacity_bindings: Vec<OpacityBinding>,
transforms: Vec<SpatialNodeKey>,
local_valid_rect: PictureRect,
color_bindings: Vec<ColorBinding>,
}
impl TileDescriptor {
fn new() -> Self {
TileDescriptor {
prims: Vec::new(),
clips: Vec::new(),
opacity_bindings: Vec::new(),
images: Vec::new(),
transforms: Vec::new(),
local_valid_rect: PictureRect::zero(),
color_bindings: Vec::new(),
}
}
fn print(&self, pt: &mut dyn PrintTreePrinter) {
pt.new_level("current_descriptor".to_string());
pt.new_level("prims".to_string());
for prim in &self.prims {
pt.new_level(format!("prim uid={}", prim.prim_uid.get_uid()));
pt.add_item(format!("origin: {},{}", prim.origin.x, prim.origin.y));
pt.add_item(format!("clip: origin={},{} size={}x{}",
prim.prim_clip_rect.x,
prim.prim_clip_rect.y,
prim.prim_clip_rect.w,
prim.prim_clip_rect.h,
));
pt.add_item(format!("deps: t={} i={} o={} c={} color={}",
prim.transform_dep_count,
prim.image_dep_count,
prim.opacity_binding_dep_count,
prim.clip_dep_count,
prim.color_binding_dep_count,
));
pt.end_level();
}
pt.end_level();
if !self.clips.is_empty() {
pt.new_level("clips".to_string());
for clip in &self.clips {
pt.new_level(format!("clip uid={}", clip.get_uid()));
pt.end_level();
}
pt.end_level();
}
if !self.images.is_empty() {
pt.new_level("images".to_string());
for info in &self.images {
pt.new_level(format!("key={:?}", info.key));
pt.add_item(format!("generation={:?}", info.generation));
pt.end_level();
}
pt.end_level();
}
if !self.opacity_bindings.is_empty() {
pt.new_level("opacity_bindings".to_string());
for opacity_binding in &self.opacity_bindings {
pt.new_level(format!("binding={:?}", opacity_binding));
pt.end_level();
}
pt.end_level();
}
if !self.transforms.is_empty() {
pt.new_level("transforms".to_string());
for transform in &self.transforms {
pt.new_level(format!("spatial_node={:?}", transform));
pt.end_level();
}
pt.end_level();
}
if !self.color_bindings.is_empty() {
pt.new_level("color_bindings".to_string());
for color_binding in &self.color_bindings {
pt.new_level(format!("binding={:?}", color_binding));
pt.end_level();
}
pt.end_level();
}
pt.end_level();
}
fn clear(&mut self) {
self.prims.clear();
self.clips.clear();
self.opacity_bindings.clear();
self.images.clear();
self.transforms.clear();
self.local_valid_rect = PictureRect::zero();
self.color_bindings.clear();
}
}
#[derive(Debug, Clone)]
pub struct DirtyRegionRect {
pub world_rect: WorldRect,
pub visibility_mask: PrimitiveVisibilityMask,
}
#[derive(Debug, Clone)]
pub struct DirtyRegion {
pub dirty_rects: Vec<DirtyRegionRect>,
pub combined: WorldRect,
}
impl DirtyRegion {
pub fn new(
) -> Self {
DirtyRegion {
dirty_rects: Vec::with_capacity(PrimitiveVisibilityMask::MAX_DIRTY_REGIONS),
combined: WorldRect::zero(),
}
}
pub fn clear(&mut self) {
self.dirty_rects.clear();
self.combined = WorldRect::zero();
}
pub fn push(
&mut self,
rect: WorldRect,
visibility_mask: PrimitiveVisibilityMask,
) {
self.combined = self.combined.union(&rect);
self.dirty_rects.push(DirtyRegionRect {
world_rect: rect,
visibility_mask,
});
}
pub fn include_rect(
&mut self,
region_index: usize,
rect: WorldRect,
) {
self.combined = self.combined.union(&rect);
let region = &mut self.dirty_rects[region_index];
region.world_rect = region.world_rect.union(&rect);
}
pub fn inflate(
&self,
inflate_amount: f32,
) -> DirtyRegion {
let mut dirty_rects = Vec::with_capacity(self.dirty_rects.len());
let mut combined = WorldRect::zero();
for rect in &self.dirty_rects {
let world_rect = rect.world_rect.inflate(inflate_amount, inflate_amount);
combined = combined.union(&world_rect);
dirty_rects.push(DirtyRegionRect {
world_rect,
visibility_mask: rect.visibility_mask,
});
}
DirtyRegion {
dirty_rects,
combined,
}
}
pub fn record(&self) -> RecordedDirtyRegion {
let mut rects: Vec<WorldRect> =
self.dirty_rects.iter().map(|r| r.world_rect).collect();
rects.sort_unstable_by_key(|r| (r.origin.y as usize, r.origin.x as usize));
RecordedDirtyRegion { rects }
}
}
#[cfg_attr(feature = "capture", derive(Serialize))]
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)
}
}
#[derive(Debug, Copy, Clone)]
pub enum BackdropKind {
Color {
color: ColorF,
},
Clear,
}
#[derive(Debug, Copy, Clone)]
pub struct BackdropInfo {
pub opaque_rect: PictureRect,
pub kind: Option<BackdropKind>,
}
impl BackdropInfo {
fn empty() -> Self {
BackdropInfo {
opaque_rect: PictureRect::zero(),
kind: None,
}
}
}
#[derive(Clone)]
pub struct TileCacheLoggerSlice {
pub serialized_slice: String,
pub local_to_world_transform: Transform3D<f32, PicturePixel, WorldPixel>,
}
#[cfg(any(feature = "capture", feature = "replay"))]
macro_rules! declare_tile_cache_logger_updatelists {
( $( $name:ident : $ty:ty, )+ ) => {
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
struct TileCacheLoggerUpdateListsSerializer {
pub ron_string: Vec<String>,
}
pub struct TileCacheLoggerUpdateLists {
$(
pub $name: (Vec<String>, Vec<UpdateList<<$ty as Internable>::Key>>),
)+
}
impl TileCacheLoggerUpdateLists {
pub fn new() -> Self {
TileCacheLoggerUpdateLists {
$(
$name : ( Vec::new(), Vec::new() ),
)+
}
}
#[cfg(feature = "capture")]
fn serialize_updates(
&mut self,
updates: &InternerUpdates
) {
$(
self.$name.0.push(ron::ser::to_string_pretty(&updates.$name, Default::default()).unwrap());
)+
}
fn is_empty(&self) -> bool {
$(
if !self.$name.0.is_empty() { return false; }
)+
true
}
#[cfg(feature = "capture")]
fn to_ron(&self) -> String {
let mut serializer =
TileCacheLoggerUpdateListsSerializer { ron_string: Vec::new() };
$(
serializer.ron_string.push(
ron::ser::to_string_pretty(&self.$name.0, Default::default()).unwrap());
)+
ron::ser::to_string_pretty(&serializer, Default::default()).unwrap()
}
#[cfg(feature = "replay")]
pub fn from_ron(&mut self, text: &str) {
let serializer : TileCacheLoggerUpdateListsSerializer =
match ron::de::from_str(&text) {
Ok(data) => { data }
Err(e) => {
println!("ERROR: failed to deserialize updatelist: {:?}\n{:?}", &text, e);
return;
}
};
let mut index = 0;
$(
let ron_lists : Vec<String> = ron::de::from_str(&serializer.ron_string[index]).unwrap();
self.$name.1 = ron_lists.iter()
.map( |list| ron::de::from_str(&list).unwrap() )
.collect();
index = index + 1;
)+
let _ = index;
}
#[cfg(feature = "replay")]
pub fn insert_in_lookup(
&mut self,
itemuid_to_string: &mut HashMap<ItemUid, String>)
{
$(
{
for list in &self.$name.1 {
for insertion in &list.insertions {
itemuid_to_string.insert(
insertion.uid,
format!("{:?}", insertion.value));
}
}
}
)+
}
}
}
}
#[cfg(any(feature = "capture", feature = "replay"))]
enumerate_interners!(declare_tile_cache_logger_updatelists);
#[cfg(not(any(feature = "capture", feature = "replay")))]
pub struct TileCacheLoggerUpdateLists {
}
#[cfg(not(any(feature = "capture", feature = "replay")))]
impl TileCacheLoggerUpdateLists {
pub fn new() -> Self { TileCacheLoggerUpdateLists {} }
fn is_empty(&self) -> bool { true }
}
pub struct TileCacheLoggerFrame {
pub slices: Vec<TileCacheLoggerSlice>,
pub update_lists: TileCacheLoggerUpdateLists
}
impl TileCacheLoggerFrame {
pub fn new() -> Self {
TileCacheLoggerFrame {
slices: Vec::new(),
update_lists: TileCacheLoggerUpdateLists::new()
}
}
pub fn is_empty(&self) -> bool {
self.slices.is_empty() && self.update_lists.is_empty()
}
}
pub struct TileCacheLogger {
pub write_index : usize,
pub frames: Vec<TileCacheLoggerFrame>
}
impl TileCacheLogger {
pub fn new(
num_frames: usize
) -> Self {
let mut frames = Vec::with_capacity(num_frames);
for _i in 0..num_frames {
frames.push(TileCacheLoggerFrame::new());
}
TileCacheLogger {
write_index: 0,
frames
}
}
pub fn is_enabled(&self) -> bool {
!self.frames.is_empty()
}
#[cfg(feature = "capture")]
pub fn add(
&mut self,
serialized_slice: String,
local_to_world_transform: Transform3D<f32, PicturePixel, WorldPixel>
) {
if !self.is_enabled() {
return;
}
self.frames[self.write_index].slices.push(
TileCacheLoggerSlice {
serialized_slice,
local_to_world_transform });
}
#[cfg(feature = "capture")]
pub fn serialize_updates(&mut self, updates: &InternerUpdates) {
if !self.is_enabled() {
return;
}
self.frames[self.write_index].update_lists.serialize_updates(updates);
}
pub fn advance(&mut self) {
if !self.is_enabled() || self.frames[self.write_index].is_empty() {
return;
}
self.write_index = self.write_index + 1;
if self.write_index >= self.frames.len() {
self.write_index = 0;
}
self.frames[self.write_index] = TileCacheLoggerFrame::new();
}
#[cfg(feature = "capture")]
pub fn save_capture(
&self, root: &PathBuf
) {
if !self.is_enabled() {
return;
}
use std::fs;
info!("saving tile cache log");
let path_tile_cache = root.join("tile_cache");
if !path_tile_cache.is_dir() {
fs::create_dir(&path_tile_cache).unwrap();
}
let mut files_written = 0;
for ix in 0..self.frames.len() {
let index = (self.write_index + 1 + ix) % self.frames.len();
if self.frames[index].is_empty() {
continue;
}
let filename = path_tile_cache.join(format!("frame{:05}.ron", files_written));
let mut output = File::create(filename).unwrap();
output.write_all(b"// slice data\n").unwrap();
output.write_all(b"[\n").unwrap();
for item in &self.frames[index].slices {
output.write_all(b"( transform:\n").unwrap();
let transform =
ron::ser::to_string_pretty(
&item.local_to_world_transform, Default::default()).unwrap();
output.write_all(transform.as_bytes()).unwrap();
output.write_all(b",\n tile_cache:\n").unwrap();
output.write_all(item.serialized_slice.as_bytes()).unwrap();
output.write_all(b"\n),\n").unwrap();
}
output.write_all(b"]\n\n").unwrap();
output.write_all(b"// @@@ chunk @@@\n\n").unwrap();
output.write_all(b"// interning data\n").unwrap();
output.write_all(self.frames[index].update_lists.to_ron().as_bytes()).unwrap();
files_written = files_written + 1;
}
}
}
pub struct NativeSurface {
pub opaque: NativeSurfaceId,
pub alpha: NativeSurfaceId,
}
#[derive(PartialEq, Eq, Hash)]
pub struct ExternalNativeSurfaceKey {
pub image_keys: [ImageKey; 3],
pub size: DeviceIntSize,
}
pub struct ExternalNativeSurface {
pub used_this_frame: bool,
pub native_surface_id: NativeSurfaceId,
pub image_dependencies: [ImageDependency; 3],
}
pub struct TileCacheInstance {
pub slice: usize,
pub slice_flags: SliceFlags,
pub current_tile_size: DeviceIntSize,
pub spatial_node_index: SpatialNodeIndex,
pub tiles: FastHashMap<TileOffset, Box<Tile>>,
map_local_to_surface: SpaceMapper<LayoutPixel, PicturePixel>,
map_child_pic_to_surface: SpaceMapper<PicturePixel, PicturePixel>,
opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
old_opacity_bindings: FastHashMap<PropertyBindingId, OpacityBindingInfo>,
spatial_node_comparer: SpatialNodeComparer,
color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
old_color_bindings: FastHashMap<PropertyBindingId, ColorBindingInfo>,
pub dirty_region: DirtyRegion,
tile_size: PictureSize,
tile_rect: TileRect,
tile_bounds_p0: TileOffset,
tile_bounds_p1: TileOffset,
pub local_rect: PictureRect,
local_clip_rect: PictureRect,
surface_index: SurfaceIndex,
pub background_color: Option<ColorF>,
pub backdrop: BackdropInfo,
pub subpixel_mode: SubpixelMode,
pub shared_clips: Vec<ClipDataHandle>,
shared_clip_chain: ClipChainId,
root_transform: TransformKey,
frames_until_size_eval: usize,
fract_offset: PictureVector2D,
virtual_offset: DeviceIntPoint,
compare_cache: FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
pub native_surface: Option<NativeSurface>,
pub device_position: DevicePoint,
tile_size_override: Option<DeviceIntSize>,
pub external_surfaces: Vec<ExternalSurfaceDescriptor>,
pub z_id_opaque: ZBufferId,
pub external_native_surface_cache: FastHashMap<ExternalNativeSurfaceKey, ExternalNativeSurface>,
frame_id: FrameId,
}
impl TileCacheInstance {
pub fn new(
slice: usize,
slice_flags: SliceFlags,
spatial_node_index: SpatialNodeIndex,
background_color: Option<ColorF>,
shared_clips: Vec<ClipDataHandle>,
shared_clip_chain: ClipChainId,
fb_config: &FrameBuilderConfig,
) -> Self {
let virtual_surface_size = fb_config.compositor_kind.get_virtual_surface_size();
TileCacheInstance {
slice,
slice_flags,
spatial_node_index,
tiles: FastHashMap::default(),
map_local_to_surface: SpaceMapper::new(
ROOT_SPATIAL_NODE_INDEX,
PictureRect::zero(),
),
map_child_pic_to_surface: SpaceMapper::new(
ROOT_SPATIAL_NODE_INDEX,
PictureRect::zero(),
),
opacity_bindings: FastHashMap::default(),
old_opacity_bindings: FastHashMap::default(),
spatial_node_comparer: SpatialNodeComparer::new(),
color_bindings: FastHashMap::default(),
old_color_bindings: FastHashMap::default(),
dirty_region: DirtyRegion::new(),
tile_size: PictureSize::zero(),
tile_rect: TileRect::zero(),
tile_bounds_p0: TileOffset::zero(),
tile_bounds_p1: TileOffset::zero(),
local_rect: PictureRect::zero(),
local_clip_rect: PictureRect::zero(),
surface_index: SurfaceIndex(0),
background_color,
backdrop: BackdropInfo::empty(),
subpixel_mode: SubpixelMode::Allow,
root_transform: TransformKey::Local,
shared_clips,
shared_clip_chain,
current_tile_size: DeviceIntSize::zero(),
frames_until_size_eval: 0,
fract_offset: PictureVector2D::zero(),
virtual_offset: DeviceIntPoint::new(
virtual_surface_size / 2,
virtual_surface_size / 2,
),
compare_cache: FastHashMap::default(),
native_surface: None,
device_position: DevicePoint::zero(),
tile_size_override: None,
external_surfaces: Vec::new(),
z_id_opaque: ZBufferId::invalid(),
external_native_surface_cache: FastHashMap::default(),
frame_id: FrameId::INVALID,
}
}
pub fn is_opaque(&self) -> bool {
match self.background_color {
Some(color) => color.a >= 1.0,
None => false
}
}
fn get_tile_coords_for_rect(
&self,
rect: &PictureRect,
) -> (TileOffset, TileOffset) {
let mut p0 = TileOffset::new(
(rect.origin.x / self.tile_size.width).floor() as i32,
(rect.origin.y / self.tile_size.height).floor() as i32,
);
let mut p1 = TileOffset::new(
((rect.origin.x + rect.size.width) / self.tile_size.width).ceil() as i32,
((rect.origin.y + rect.size.height) / self.tile_size.height).ceil() as i32,
);
p0.x = clamp(p0.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
p0.y = clamp(p0.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
p1.x = clamp(p1.x, self.tile_bounds_p0.x, self.tile_bounds_p1.x);
p1.y = clamp(p1.y, self.tile_bounds_p0.y, self.tile_bounds_p1.y);
(p0, p1)
}
pub fn pre_update(
&mut self,
pic_rect: PictureRect,
surface_index: SurfaceIndex,
frame_context: &FrameVisibilityContext,
frame_state: &mut FrameVisibilityState,
) -> WorldRect {
self.external_surfaces.clear();
self.surface_index = surface_index;
self.local_rect = pic_rect;
self.local_clip_rect = PictureRect::max_rect();
self.z_id_opaque = frame_state.composite_state.z_generator.next();
self.backdrop = BackdropInfo::empty();
self.subpixel_mode = SubpixelMode::Allow;
self.map_local_to_surface = SpaceMapper::new(
self.spatial_node_index,
pic_rect,
);
self.map_child_pic_to_surface = SpaceMapper::new(
self.spatial_node_index,
pic_rect,
);
let pic_to_world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
self.spatial_node_index,
frame_context.global_screen_world_rect,
frame_context.spatial_tree,
);
if self.shared_clip_chain != ClipChainId::NONE {
let mut shared_clips = Vec::new();
let mut current_clip_chain_id = self.shared_clip_chain;
while current_clip_chain_id != ClipChainId::NONE {
shared_clips.push(current_clip_chain_id);
let clip_chain_node = &frame_state.clip_store.clip_chain_nodes[current_clip_chain_id.0 as usize];
current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
}
frame_state.clip_store.set_active_clips(
LayoutRect::max_rect(),
self.spatial_node_index,
&shared_clips,
frame_context.spatial_tree,
&mut frame_state.data_stores.clip,
);
let clip_chain_instance = frame_state.clip_store.build_clip_chain_instance(
pic_rect.cast_unit(),
&self.map_local_to_surface,
&pic_to_world_mapper,
frame_context.spatial_tree,
frame_state.gpu_cache,
frame_state.resource_cache,
frame_context.global_device_pixel_scale,
&frame_context.global_screen_world_rect,
&mut frame_state.data_stores.clip,
true,
false,
);
self.local_clip_rect = clip_chain_instance.map_or(PictureRect::zero(), |clip_chain_instance| {
clip_chain_instance.pic_clip_rect
});
}
if let Some(prev_state) = frame_state.retained_tiles.caches.remove(&self.slice) {
self.tiles.extend(prev_state.tiles);
self.root_transform = prev_state.root_transform;
self.spatial_node_comparer = prev_state.spatial_node_comparer;
self.opacity_bindings = prev_state.opacity_bindings;
self.color_bindings = prev_state.color_bindings;
self.current_tile_size = prev_state.current_tile_size;
self.native_surface = prev_state.native_surface;
self.external_native_surface_cache = prev_state.external_native_surface_cache;
self.virtual_offset = prev_state.virtual_offset;
self.frame_id = prev_state.frame_id;
fn recycle_map<K: std::cmp::Eq + std::hash::Hash, V>(
ideal_len: usize,
dest: &mut FastHashMap<K, V>,
src: FastHashMap<K, V>,
) {
if dest.capacity() < src.capacity() {
if src.capacity() < 3 * ideal_len {
*dest = src;
} else {
dest.clear();
dest.reserve(ideal_len);
}
}
}
recycle_map(
self.opacity_bindings.len(),
&mut self.old_opacity_bindings,
prev_state.allocations.old_opacity_bindings,
);
recycle_map(
self.color_bindings.len(),
&mut self.old_color_bindings,
prev_state.allocations.old_color_bindings,
);
recycle_map(
prev_state.allocations.compare_cache.len(),
&mut self.compare_cache,
prev_state.allocations.compare_cache,
);
}
self.frame_id.advance();
self.spatial_node_comparer.next_frame(self.spatial_node_index);
for external_native_surface in self.external_native_surface_cache.values_mut() {
external_native_surface.used_this_frame = false;
}
if self.frames_until_size_eval == 0 ||
self.tile_size_override != frame_context.config.tile_size_override {
let desired_tile_size = match frame_context.config.tile_size_override {
Some(tile_size_override) => {
tile_size_override
}
None => {
if self.slice_flags.contains(SliceFlags::IS_SCROLLBAR) {
if pic_rect.size.width <= pic_rect.size.height {
TILE_SIZE_SCROLLBAR_VERTICAL
} else {
TILE_SIZE_SCROLLBAR_HORIZONTAL
}
} else {
TILE_SIZE_DEFAULT
}
}
};
if desired_tile_size != self.current_tile_size {
if let Some(native_surface) = self.native_surface.take() {
frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
}
self.tiles.clear();
self.current_tile_size = desired_tile_size;
}
self.frames_until_size_eval = 120;
self.tile_size_override = frame_context.config.tile_size_override;
}
let world_origin = pic_to_world_mapper
.map(&PictureRect::new(PicturePoint::zero(), PictureSize::new(1.0, 1.0)))
.expect("bug: unable to map origin to world space")
.origin;
let device_origin = world_origin * frame_context.global_device_pixel_scale;
let desired_device_origin = device_origin.round();
self.device_position = desired_device_origin;
let ref_world_rect = WorldRect::new(
desired_device_origin / frame_context.global_device_pixel_scale,
WorldSize::new(1.0, 1.0),
);
let ref_point = pic_to_world_mapper
.unmap(&ref_world_rect)
.expect("bug: unable to unmap ref world rect")
.origin;
self.fract_offset = PictureVector2D::new(
ref_point.x.fract(),
ref_point.y.fract(),
);
let current_properties = frame_context.scene_properties.float_properties();
mem::swap(&mut self.opacity_bindings, &mut self.old_opacity_bindings);
self.opacity_bindings.clear();
for (id, value) in current_properties {
let changed = match self.old_opacity_bindings.get(id) {
Some(old_property) => !old_property.value.approx_eq(value),
None => true,
};
self.opacity_bindings.insert(*id, OpacityBindingInfo {
value: *value,
changed,
});
}
let current_properties = frame_context.scene_properties.color_properties();
mem::swap(&mut self.color_bindings, &mut self.old_color_bindings);
self.color_bindings.clear();
for (id, value) in current_properties {
let changed = match self.old_color_bindings.get(id) {
Some(old_property) => old_property.value != (*value).into(),
None => true,
};
self.color_bindings.insert(*id, ColorBindingInfo {
value: (*value).into(),
changed,
});
}
let world_tile_size = WorldSize::new(
self.current_tile_size.width as f32 / frame_context.global_device_pixel_scale.0,
self.current_tile_size.height as f32 / frame_context.global_device_pixel_scale.0,
);
let local_tile_rect = pic_to_world_mapper
.unmap(&WorldRect::new(WorldPoint::zero(), world_tile_size))
.expect("bug: unable to get local tile rect");
self.tile_size = local_tile_rect.size;
let screen_rect_in_pic_space = pic_to_world_mapper
.unmap(&frame_context.global_screen_world_rect)
.expect("unable to unmap screen rect");
let desired_rect_in_pic_space = screen_rect_in_pic_space
.inflate(0.0, 1.0 * self.tile_size.height);
let needed_rect_in_pic_space = desired_rect_in_pic_space
.intersection(&pic_rect)
.unwrap_or_else(PictureRect::zero);
let p0 = needed_rect_in_pic_space.origin;
let p1 = needed_rect_in_pic_space.bottom_right();
let x0 = (p0.x / local_tile_rect.size.width).floor() as i32;
let x1 = (p1.x / local_tile_rect.size.width).ceil() as i32;
let y0 = (p0.y / local_tile_rect.size.height).floor() as i32;
let y1 = (p1.y / local_tile_rect.size.height).ceil() as i32;
let x_tiles = x1 - x0;
let y_tiles = y1 - y0;
let new_tile_rect = TileRect::new(
TileOffset::new(x0, y0),
TileSize::new(x_tiles, y_tiles),
);
if let CompositorKind::Native { virtual_surface_size, .. } = frame_context.config.compositor_kind {
if virtual_surface_size > 0 {
let tx0 = self.virtual_offset.x + x0 * self.current_tile_size.width;
let ty0 = self.virtual_offset.y + y0 * self.current_tile_size.height;
let tx1 = self.virtual_offset.x + (x1+1) * self.current_tile_size.width;
let ty1 = self.virtual_offset.y + (y1+1) * self.current_tile_size.height;
let need_new_virtual_offset = tx0 < 0 ||
ty0 < 0 ||
tx1 >= virtual_surface_size ||
ty1 >= virtual_surface_size;
if need_new_virtual_offset {
self.virtual_offset = DeviceIntPoint::new(
(virtual_surface_size/2) - ((x0 + x1) / 2) * self.current_tile_size.width,
(virtual_surface_size/2) - ((y0 + y1) / 2) * self.current_tile_size.height,
);
for tile in self.tiles.values_mut() {
if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
if let Some(id) = id.take() {
frame_state.resource_cache.destroy_compositor_tile(id);
tile.surface = None;
tile.invalidate(None, InvalidationReason::CompositorKindChanged);
}
}
}
if let Some(native_surface) = self.native_surface.take() {
frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
}
}
}
}
if new_tile_rect != self.tile_rect {
let mut old_tiles = mem::replace(&mut self.tiles, FastHashMap::default());
self.tiles.reserve(new_tile_rect.size.area() as usize);
for y in y0 .. y1 {
for x in x0 .. x1 {
let key = TileOffset::new(x, y);
let tile = old_tiles
.remove(&key)
.unwrap_or_else(|| {
Box::new(Tile::new(key))
});
self.tiles.insert(key, tile);
}
}
if !old_tiles.is_empty() {
frame_state.composite_state.dirty_rects_are_valid = false;
}
frame_state.composite_state.destroy_native_tiles(
old_tiles.values_mut(),
frame_state.resource_cache,
);
}
self.tile_bounds_p0 = TileOffset::new(x0, y0);
self.tile_bounds_p1 = TileOffset::new(x1, y1);
self.tile_rect = new_tile_rect;
let mut world_culling_rect = WorldRect::zero();
let ctx = TilePreUpdateContext {
pic_to_world_mapper,
fract_offset: self.fract_offset,
background_color: self.background_color,
global_screen_world_rect: frame_context.global_screen_world_rect,
tile_size: self.tile_size,
frame_id: self.frame_id,
};
for tile in self.tiles.values_mut() {
tile.pre_update(&ctx);
if tile.is_visible {
world_culling_rect = world_culling_rect.union(&tile.world_tile_rect);
}
}
match frame_context.config.compositor_kind {
CompositorKind::Draw { .. } => {
for tile in self.tiles.values_mut() {
if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
if let Some(id) = id.take() {
frame_state.resource_cache.destroy_compositor_tile(id);
}
tile.surface = None;
tile.invalidate(None, InvalidationReason::CompositorKindChanged);
}
}
if let Some(native_surface) = self.native_surface.take() {
frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque);
frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha);
}
for (_, external_surface) in self.external_native_surface_cache.drain() {
frame_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id)
}
}
CompositorKind::Native { .. } => {
for tile in self.tiles.values_mut() {
if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::TextureCache { .. }, .. }) = tile.surface {
tile.surface = None;
tile.invalidate(None, InvalidationReason::CompositorKindChanged);
}
}
}
}
world_culling_rect
}
pub fn update_prim_dependencies(
&mut self,
prim_instance: &mut PrimitiveInstance,
prim_spatial_node_index: SpatialNodeIndex,
prim_clip_chain: Option<&ClipChainInstance>,
local_prim_rect: LayoutRect,
frame_context: &FrameVisibilityContext,
data_stores: &DataStores,
clip_store: &ClipStore,
pictures: &[PicturePrimitive],
resource_cache: &mut ResourceCache,
opacity_binding_store: &OpacityBindingStorage,
color_bindings: &ColorBindingStorage,
image_instances: &ImageInstanceStorage,
surface_stack: &[SurfaceIndex],
composite_state: &mut CompositeState,
) -> Option<PrimitiveVisibilityFlags> {
let prim_surface_index = *surface_stack.last().unwrap();
let prim_clip_chain = match prim_clip_chain {
Some(prim_clip_chain) => prim_clip_chain,
None => return None,
};
self.map_local_to_surface.set_target_spatial_node(
prim_spatial_node_index,
frame_context.spatial_tree,
);
let prim_rect = match self.map_local_to_surface.map(&local_prim_rect) {
Some(rect) => rect,
None => return None,
};
if prim_rect.size.is_empty_or_negative() {
return None;
}
let on_picture_surface = prim_surface_index == self.surface_index;
let pic_clip_rect = if on_picture_surface {
prim_clip_chain.pic_clip_rect
} else {
let mut current_pic_clip_rect = prim_clip_chain.pic_clip_rect;
let mut current_spatial_node_index = frame_context
.surfaces[prim_surface_index.0]
.surface_spatial_node_index;
for surface_index in surface_stack.iter().rev() {
let surface = &frame_context.surfaces[surface_index.0];
let map_local_to_surface = SpaceMapper::new_with_target(
surface.surface_spatial_node_index,
current_spatial_node_index,
surface.rect,
frame_context.spatial_tree,
);
current_pic_clip_rect = match map_local_to_surface.map(¤t_pic_clip_rect) {
Some(rect) => {
rect.inflate(surface.inflation_factor, surface.inflation_factor)
}
None => {
return None;
}
};
current_spatial_node_index = surface.surface_spatial_node_index;
}
current_pic_clip_rect
};
let (p0, p1) = self.get_tile_coords_for_rect(&pic_clip_rect);
if p0.x == p1.x || p0.y == p1.y {
return None;
}
let mut prim_info = PrimitiveDependencyInfo::new(
prim_instance.uid(),
prim_rect.origin,
pic_clip_rect,
);
if prim_spatial_node_index != self.spatial_node_index {
prim_info.spatial_nodes.push(prim_spatial_node_index);
}
let clip_instances = &clip_store
.clip_node_instances[prim_clip_chain.clips_range.to_range()];
for clip_instance in clip_instances {
prim_info.clips.push(clip_instance.handle.uid());
let clip_node = &data_stores.clip[clip_instance.handle];
if clip_node.item.spatial_node_index != self.spatial_node_index
&& !prim_info.spatial_nodes.contains(&clip_node.item.spatial_node_index) {
prim_info.spatial_nodes.push(clip_node.item.spatial_node_index);
}
}
let mut backdrop_candidate = None;
match prim_instance.kind {
PrimitiveInstanceKind::Picture { pic_index,.. } => {
let pic = &pictures[pic_index.0];
if let Some(PictureCompositeMode::Filter(Filter::Opacity(binding, _))) = pic.requested_composite_mode {
prim_info.opacity_bindings.push(binding.into());
}
}
PrimitiveInstanceKind::Rectangle { data_handle, opacity_binding_index, color_binding_index, .. } => {
if opacity_binding_index == OpacityBindingIndex::INVALID {
let color = match data_stores.prim[data_handle].kind {
PrimitiveTemplateKind::Rectangle { color, .. } => {
frame_context.scene_properties.resolve_color(&color)
}
_ => unreachable!(),
};
if color.a >= 1.0 {
backdrop_candidate = Some(BackdropInfo {
opaque_rect: pic_clip_rect,
kind: Some(BackdropKind::Color { color }),
});
}
} else {
let opacity_binding = &opacity_binding_store[opacity_binding_index];
for binding in &opacity_binding.bindings {
prim_info.opacity_bindings.push(OpacityBinding::from(*binding));
}
}
if color_binding_index != ColorBindingIndex::INVALID {
prim_info.color_binding = Some(color_bindings[color_binding_index].into());
}
prim_info.clip_by_tile = 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 {
if let Some(image_properties) = resource_cache.get_image_properties(image_data.key) {
if image_properties.descriptor.is_opaque() &&
image_properties.tiling.is_none() &&
image_data.tile_spacing == LayoutSize::zero() {
backdrop_candidate = Some(BackdropInfo {
opaque_rect: pic_clip_rect,
kind: None,
});
}
}
} else {
let opacity_binding = &opacity_binding_store[opacity_binding_index];
for binding in &opacity_binding.bindings {
prim_info.opacity_bindings.push(OpacityBinding::from(*binding));
}
}
prim_info.images.push(ImageDependency {
key: image_data.key,
generation: resource_cache.get_image_generation(image_data.key),
});
}
PrimitiveInstanceKind::YuvImage { data_handle, ref mut is_compositor_surface, .. } => {
let prim_data = &data_stores.yuv_image[data_handle];
let mut promote_to_surface = false;
if composite_state.picture_caching_is_enabled {
if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
promote_to_surface = true;
if self.external_surfaces.len() == MAX_COMPOSITOR_SURFACES {
promote_to_surface = false;
}
if prim_clip_chain.needs_mask {
promote_to_surface = false;
}
if !on_picture_surface {
promote_to_surface = false;
}
let prim_spatial_node = &frame_context.spatial_tree
.spatial_nodes[prim_spatial_node_index.0 as usize];
if prim_spatial_node.coordinate_system_id != CoordinateSystemId::root() {
promote_to_surface = false;
}
if !self.map_local_to_surface.get_transform().is_simple_2d_translation() {
promote_to_surface = false;
}
}
}
*is_compositor_surface = promote_to_surface;
if promote_to_surface {
prim_info.is_compositor_surface = true;
let pic_to_world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
self.spatial_node_index,
frame_context.global_screen_world_rect,
frame_context.spatial_tree,
);
let world_rect = pic_to_world_mapper
.map(&prim_rect)
.expect("bug: unable to map the primitive to world space");
let world_clip_rect = pic_to_world_mapper
.map(&prim_info.prim_clip_rect)
.expect("bug: unable to map clip to world space");
let is_visible = world_clip_rect.intersects(&frame_context.global_screen_world_rect);
if is_visible {
let device_rect = (world_rect * frame_context.global_device_pixel_scale).round();
let clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
let mut image_dependencies = [ImageDependency::INVALID; 3];
for (key, dep) in prim_data.kind.yuv_key.iter().cloned().zip(image_dependencies.iter_mut()) {
*dep = ImageDependency {
key,
generation: resource_cache.get_image_generation(key),
}
}
let (native_surface_id, update_params) = match composite_state.compositor_kind {
CompositorKind::Draw { .. } => {
(None, None)
}
CompositorKind::Native { .. } => {
let native_surface_size = device_rect.size.round().to_i32();
let key = ExternalNativeSurfaceKey {
image_keys: prim_data.kind.yuv_key,
size: native_surface_size,
};
let native_surface = self.external_native_surface_cache
.entry(key)
.or_insert_with(|| {
let native_surface_id = resource_cache.create_compositor_surface(
DeviceIntPoint::zero(),
native_surface_size,
true,
);
let tile_id = NativeTileId {
surface_id: native_surface_id,
x: 0,
y: 0,
};
resource_cache.create_compositor_tile(tile_id);
ExternalNativeSurface {
used_this_frame: true,
native_surface_id,
image_dependencies: [ImageDependency::INVALID; 3],
}
});
native_surface.used_this_frame = true;
let update_params = if image_dependencies == native_surface.image_dependencies {
None
} else {
Some(native_surface_size)
};
(Some(native_surface.native_surface_id), update_params)
}
};
self.external_surfaces.push(ExternalSurfaceDescriptor {
local_rect: prim_info.prim_clip_rect,
world_rect,
local_clip_rect: prim_info.prim_clip_rect,
image_dependencies,
image_rendering: prim_data.kind.image_rendering,
device_rect,
clip_rect,
yuv_color_space: prim_data.kind.color_space,
yuv_format: prim_data.kind.format,
yuv_rescale: prim_data.kind.color_depth.rescaling_factor(),
z_id: composite_state.z_generator.next(),
native_surface_id,
update_params,
});
}
} else {
prim_info.images.extend(
prim_data.kind.yuv_key.iter().map(|key| {
ImageDependency {
key: *key,
generation: resource_cache.get_image_generation(*key),
}
})
);
}
}
PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
let border_data = &data_stores.image_border[data_handle].kind;
prim_info.images.push(ImageDependency {
key: border_data.request.key,
generation: resource_cache.get_image_generation(border_data.request.key),
});
}
PrimitiveInstanceKind::PushClipChain |
PrimitiveInstanceKind::PopClipChain => {
return None;
}
PrimitiveInstanceKind::TextRun { data_handle, .. } => {
if self.subpixel_mode == SubpixelMode::Allow && !self.is_opaque() {
let run_data = &data_stores.text_run[data_handle];
let subpx_requested = match run_data.font.render_mode {
FontRenderMode::Subpixel => true,
FontRenderMode::Alpha | FontRenderMode::Mono => false,
};
if on_picture_surface
&& subpx_requested
&& !self.backdrop.opaque_rect.contains_rect(&pic_clip_rect) {
self.subpixel_mode = SubpixelMode::Deny;
}
}
}
PrimitiveInstanceKind::Clear { .. } => {
backdrop_candidate = Some(BackdropInfo {
opaque_rect: pic_clip_rect,
kind: Some(BackdropKind::Clear),
});
}
PrimitiveInstanceKind::LineDecoration { .. } |
PrimitiveInstanceKind::NormalBorder { .. } |
PrimitiveInstanceKind::LinearGradient { .. } |
PrimitiveInstanceKind::RadialGradient { .. } |
PrimitiveInstanceKind::ConicGradient { .. } |
PrimitiveInstanceKind::Backdrop { .. } => {
}
};
let mut vis_flags = PrimitiveVisibilityFlags::empty();
if let Some(backdrop_candidate) = backdrop_candidate {
let is_suitable_backdrop = match backdrop_candidate.kind {
Some(BackdropKind::Clear) => {
true
}
Some(BackdropKind::Color { .. }) | None => {
let same_coord_system = {
let prim_spatial_node = &frame_context.spatial_tree
.spatial_nodes[prim_spatial_node_index.0 as usize];
let surface_spatial_node = &frame_context.spatial_tree
.spatial_nodes[self.spatial_node_index.0 as usize];
prim_spatial_node.coordinate_system_id == surface_spatial_node.coordinate_system_id
};
same_coord_system && on_picture_surface
}
};
if is_suitable_backdrop
&& self.external_surfaces.is_empty()
&& !prim_clip_chain.needs_mask {
if backdrop_candidate.opaque_rect.contains_rect(&self.backdrop.opaque_rect) {
self.backdrop.opaque_rect = backdrop_candidate.opaque_rect;
}
if let Some(kind) = backdrop_candidate.kind {
if backdrop_candidate.opaque_rect.contains_rect(&self.local_rect) {
if let BackdropKind::Color { .. } = kind {
vis_flags |= PrimitiveVisibilityFlags::IS_BACKDROP;
}
self.backdrop.kind = Some(kind);
}
}
}
}
for spatial_node_index in &prim_info.spatial_nodes {
self.spatial_node_comparer.register_used_transform(
*spatial_node_index,
self.frame_id,
frame_context.spatial_tree,
);
}
prim_info.clips.truncate(MAX_PRIM_SUB_DEPS);
prim_info.opacity_bindings.truncate(MAX_PRIM_SUB_DEPS);
prim_info.spatial_nodes.truncate(MAX_PRIM_SUB_DEPS);
prim_info.images.truncate(MAX_PRIM_SUB_DEPS);
for y in p0.y .. p1.y {
for x in p0.x .. p1.x {
let key = TileOffset::new(x, y);
let tile = self.tiles.get_mut(&key).expect("bug: no tile");
tile.add_prim_dependency(&prim_info);
}
}
Some(vis_flags)
}
fn print(&self) {
let mut pt = PrintTree::new("Picture Cache");
pt.new_level(format!("Slice {}", self.slice));
pt.add_item(format!("fract_offset: {:?}", self.fract_offset));
pt.add_item(format!("background_color: {:?}", self.background_color));
for y in self.tile_bounds_p0.y .. self.tile_bounds_p1.y {
for x in self.tile_bounds_p0.x .. self.tile_bounds_p1.x {
let key = TileOffset::new(x, y);
let tile = &self.tiles[&key];
tile.print(&mut pt);
}
}
pt.end_level();
}
pub fn post_update(
&mut self,
frame_context: &FrameVisibilityContext,
frame_state: &mut FrameVisibilityState,
) {
self.dirty_region.clear();
if !self.external_surfaces.is_empty() && self.subpixel_mode == SubpixelMode::Allow {
let excluded_rects = self.external_surfaces
.iter()
.map(|s| {
s.local_rect
})
.collect();
self.subpixel_mode = SubpixelMode::Conditional {
excluded_rects,
};
}
let map_pic_to_world = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
self.spatial_node_index,
frame_context.global_screen_world_rect,
frame_context.spatial_tree,
);
if self.backdrop.opaque_rect.is_well_formed_and_nonempty() {
let backdrop_rect = self.backdrop.opaque_rect
.intersection(&self.local_rect)
.and_then(|r| {
r.intersection(&self.local_clip_rect)
});
if let Some(backdrop_rect) = backdrop_rect {
let world_backdrop_rect = map_pic_to_world
.map(&backdrop_rect)
.expect("bug: unable to map backdrop to world space");
frame_state.composite_state.register_occluder(
self.z_id_opaque,
world_backdrop_rect,
);
}
}
for external_surface in &self.external_surfaces {
let local_surface_rect = external_surface.local_rect
.intersection(&external_surface.local_clip_rect)
.and_then(|r| {
r.intersection(&self.local_clip_rect)
});
if let Some(local_surface_rect) = local_surface_rect {
let world_surface_rect = map_pic_to_world
.map(&local_surface_rect)
.expect("bug: unable to map external surface to world space");
frame_state.composite_state.register_occluder(
external_surface.z_id,
world_surface_rect,
);
}
}
self.external_native_surface_cache.retain(|_, surface| {
if !surface.used_this_frame {
frame_state.resource_cache.destroy_compositor_surface(surface.native_surface_id);
}
surface.used_this_frame
});
let root_transform = frame_context
.spatial_tree
.get_relative_transform(
self.spatial_node_index,
ROOT_SPATIAL_NODE_INDEX,
)
.into();
let root_transform_changed = root_transform != self.root_transform;
if root_transform_changed {
self.root_transform = root_transform;
frame_state.composite_state.dirty_rects_are_valid = false;
}
let pic_to_world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
self.spatial_node_index,
frame_context.global_screen_world_rect,
frame_context.spatial_tree,
);
let z_id_alpha = frame_state.composite_state.z_generator.next();
let ctx = TilePostUpdateContext {
pic_to_world_mapper,
global_device_pixel_scale: frame_context.global_device_pixel_scale,
local_clip_rect: self.local_clip_rect,
backdrop: self.backdrop,
opacity_bindings: &self.opacity_bindings,
color_bindings: &self.color_bindings,
current_tile_size: self.current_tile_size,
local_rect: self.local_rect,
external_surfaces: &self.external_surfaces,
z_id_opaque: self.z_id_opaque,
z_id_alpha,
};
let mut state = TilePostUpdateState {
resource_cache: frame_state.resource_cache,
composite_state: frame_state.composite_state,
compare_cache: &mut self.compare_cache,
spatial_node_comparer: &mut self.spatial_node_comparer,
};
for tile in self.tiles.values_mut() {
tile.post_update(&ctx, &mut state, frame_context);
}
if frame_context.config.testing {
frame_state.scratch.recorded_dirty_regions.push(self.dirty_region.record());
}
}
}
pub struct PictureUpdateState<'a> {
surfaces: &'a mut Vec<SurfaceInfo>,
surface_stack: Vec<SurfaceIndex>,
picture_stack: Vec<PictureInfo>,
are_raster_roots_assigned: bool,
composite_state: &'a CompositeState,
}
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,
data_stores: &mut DataStores,
composite_state: &CompositeState,
) {
profile_marker!("UpdatePictures");
let mut state = PictureUpdateState {
surfaces,
surface_stack: vec![SurfaceIndex(0)],
picture_stack: Vec::new(),
are_raster_roots_assigned: true,
composite_state,
};
state.update(
pic_index,
picture_primitives,
frame_context,
gpu_cache,
clip_store,
data_stores,
);
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 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,
picture_primitives: &mut [PicturePrimitive],
frame_context: &FrameBuildingContext,
gpu_cache: &mut GpuCache,
clip_store: &ClipStore,
data_stores: &mut DataStores,
) {
if let Some(prim_list) = picture_primitives[pic_index.0].pre_update(
self,
frame_context,
) {
for cluster in &prim_list.clusters {
if cluster.flags.contains(ClusterFlags::IS_PICTURE) {
for prim_instance in &cluster.prim_instances {
let child_pic_index = match prim_instance.kind {
PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index,
_ => unreachable!(),
};
self.update(
child_pic_index,
picture_primitives,
frame_context,
gpu_cache,
clip_store,
data_stores,
);
}
}
}
picture_primitives[pic_index.0].post_update(
prim_list,
self,
frame_context,
data_stores,
);
}
}
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 cluster in &picture.prim_list.clusters {
if cluster.flags.contains(ClusterFlags::IS_PICTURE) {
for instance in &cluster.prim_instances {
let child_pic_index = match instance.kind {
PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index,
_ => unreachable!(),
};
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, Copy, Clone)]
pub struct SurfaceRenderTasks {
pub root: RenderTaskId,
pub port: RenderTaskId,
}
#[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 render_tasks: Option<SurfaceRenderTasks>,
pub inflation_factor: f32,
pub device_pixel_scale: DevicePixelScale,
pub scale_factors: (f32, f32),
}
impl SurfaceInfo {
pub fn new(
surface_spatial_node_index: SpatialNodeIndex,
raster_spatial_node_index: SpatialNodeIndex,
inflation_factor: f32,
world_rect: WorldRect,
spatial_tree: &SpatialTree,
device_pixel_scale: DevicePixelScale,
scale_factors: (f32, f32),
) -> Self {
let map_surface_to_world = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
surface_spatial_node_index,
world_rect,
spatial_tree,
);
let pic_bounds = map_surface_to_world
.unmap(&map_surface_to_world.bounds)
.unwrap_or_else(PictureRect::max_rect);
let map_local_to_surface = SpaceMapper::new(
surface_spatial_node_index,
pic_bounds,
);
SurfaceInfo {
rect: PictureRect::zero(),
map_local_to_surface,
render_tasks: None,
raster_spatial_node_index,
surface_spatial_node_index,
inflation_factor,
device_pixel_scale,
scale_factors,
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct RasterConfig {
pub composite_mode: PictureCompositeMode,
pub surface_index: SurfaceIndex,
pub establishes_raster_root: bool,
pub root_scaling_factor: f32,
}
bitflags! {
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct BlitReason: u32 {
const ISOLATE = 1;
const CLIP = 2;
const PRESERVE3D = 4;
const BACKDROP = 8;
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub enum PictureCompositeMode {
MixBlend(MixBlendMode),
Filter(Filter),
ComponentTransferFilter(FilterDataHandle),
Blit(BlitReason),
TileCache {
},
SvgFilter(Vec<FilterPrimitive>, Vec<SFilterData>),
}
impl PictureCompositeMode {
pub fn inflate_picture_rect(&self, picture_rect: PictureRect, scale_factors: (f32, f32)) -> PictureRect {
let mut result_rect = picture_rect;
match self {
PictureCompositeMode::Filter(filter) => match filter {
Filter::Blur(blur_radius) => {
let inflation_factor = clamp_blur_radius(*blur_radius, scale_factors).ceil() * BLUR_SAMPLE_SCALE;
result_rect = picture_rect.inflate(inflation_factor, inflation_factor);
},
Filter::DropShadows(shadows) => {
let mut max_inflation: f32 = 0.0;
for shadow in shadows {
max_inflation = max_inflation.max(shadow.blur_radius);
}
max_inflation = clamp_blur_radius(max_inflation, scale_factors).ceil() * BLUR_SAMPLE_SCALE;
result_rect = picture_rect.inflate(max_inflation, max_inflation);
},
_ => {}
}
PictureCompositeMode::SvgFilter(primitives, _) => {
let mut output_rects = Vec::with_capacity(primitives.len());
for (cur_index, primitive) in primitives.iter().enumerate() {
let output_rect = match primitive.kind {
FilterPrimitiveKind::Blur(ref primitive) => {
let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
let inflation_factor = primitive.radius.round() * BLUR_SAMPLE_SCALE;
input.inflate(inflation_factor, inflation_factor)
}
FilterPrimitiveKind::DropShadow(ref primitive) => {
let inflation_factor = primitive.shadow.blur_radius.ceil() * BLUR_SAMPLE_SCALE;
let input = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
let shadow_rect = input.inflate(inflation_factor, inflation_factor);
input.union(&shadow_rect.translate(primitive.shadow.offset * Scale::new(1.0)))
}
FilterPrimitiveKind::Blend(ref primitive) => {
primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect)
.union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect))
}
FilterPrimitiveKind::Composite(ref primitive) => {
primitive.input1.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect)
.union(&primitive.input2.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect))
}
FilterPrimitiveKind::Identity(ref primitive) =>
primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
FilterPrimitiveKind::Opacity(ref primitive) =>
primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
FilterPrimitiveKind::ColorMatrix(ref primitive) =>
primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
FilterPrimitiveKind::ComponentTransfer(ref primitive) =>
primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect),
FilterPrimitiveKind::Offset(ref primitive) => {
let input_rect = primitive.input.to_index(cur_index).map(|index| output_rects[index]).unwrap_or(picture_rect);
input_rect.translate(primitive.offset * Scale::new(1.0))
},
FilterPrimitiveKind::Flood(..) => picture_rect,
};
output_rects.push(output_rect);
result_rect = result_rect.union(&output_rect);
}
}
_ => {},
}
result_rect
}
}
#[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: PlaneSplitAnchor,
pub spatial_node_index: SpatialNodeIndex,
pub gpu_address: GpuCacheAddress,
}
bitflags! {
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct ClusterFlags: u32 {
const IS_PICTURE = 1;
const IS_BACKFACE_VISIBLE = 2;
const IS_VISIBLE = 4;
const IS_BACKDROP_FILTER = 8;
const CREATE_PICTURE_CACHE_PRE = 16;
const CREATE_PICTURE_CACHE_POST = 32;
const SCROLLBAR_CONTAINER = 64;
const IS_CLEAR_PRIMITIVE = 128;
const PREFER_COMPOSITOR_SURFACE = 256;
}
}
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct PrimitiveCluster {
pub spatial_node_index: SpatialNodeIndex,
bounding_rect: LayoutRect,
pub prim_instances: Vec<PrimitiveInstance>,
pub flags: ClusterFlags,
pub cache_scroll_root: Option<SpatialNodeIndex>,
}
#[derive(Debug, Copy, Clone)]
enum PrimitiveListPosition {
Begin,
End,
}
impl PrimitiveCluster {
fn new(
spatial_node_index: SpatialNodeIndex,
flags: ClusterFlags,
) -> Self {
PrimitiveCluster {
bounding_rect: LayoutRect::zero(),
spatial_node_index,
flags,
prim_instances: Vec::new(),
cache_scroll_root: None,
}
}
pub fn is_compatible(
&self,
spatial_node_index: SpatialNodeIndex,
flags: ClusterFlags,
) -> bool {
if self.flags.contains(ClusterFlags::SCROLLBAR_CONTAINER) {
return false;
}
self.flags == flags && self.spatial_node_index == spatial_node_index
}
fn push(
&mut self,
prim_instance: PrimitiveInstance,
prim_rect: LayoutRect,
) {
let culling_rect = prim_instance.local_clip_rect
.intersection(&prim_rect)
.unwrap_or_else(LayoutRect::zero);
self.bounding_rect = self.bounding_rect.union(&culling_rect);
self.prim_instances.push(prim_instance);
}
}
#[cfg_attr(feature = "capture", derive(Serialize))]
pub struct PrimitiveList {
pub clusters: Vec<PrimitiveCluster>,
}
impl PrimitiveList {
pub fn empty() -> Self {
PrimitiveList {
clusters: Vec::new(),
}
}
fn push(
&mut self,
prim_instance: PrimitiveInstance,
prim_rect: LayoutRect,
spatial_node_index: SpatialNodeIndex,
prim_flags: PrimitiveFlags,
insert_position: PrimitiveListPosition,
) {
let mut flags = ClusterFlags::empty();
match prim_instance.kind {
PrimitiveInstanceKind::Picture { .. } => {
flags.insert(ClusterFlags::IS_PICTURE);
}
PrimitiveInstanceKind::Backdrop { .. } => {
flags.insert(ClusterFlags::IS_BACKDROP_FILTER);
}
PrimitiveInstanceKind::Clear { .. } => {
flags.insert(ClusterFlags::IS_CLEAR_PRIMITIVE);
}
_ => {}
}
if prim_flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE) {
flags.insert(ClusterFlags::IS_BACKFACE_VISIBLE);
}
if prim_flags.contains(PrimitiveFlags::IS_SCROLLBAR_CONTAINER) {
flags.insert(ClusterFlags::SCROLLBAR_CONTAINER);
}
if prim_flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) {
flags.insert(ClusterFlags::PREFER_COMPOSITOR_SURFACE);
}
match insert_position {
PrimitiveListPosition::Begin => {
let mut cluster = PrimitiveCluster::new(
spatial_node_index,
flags,
);
cluster.push(prim_instance, prim_rect);
self.clusters.insert(0, cluster);
}
PrimitiveListPosition::End => {
if let Some(cluster) = self.clusters.last_mut() {
if cluster.is_compatible(spatial_node_index, flags) {
cluster.push(prim_instance, prim_rect);
return;
}
}
let mut cluster = PrimitiveCluster::new(
spatial_node_index,
flags,
);
cluster.push(prim_instance, prim_rect);
self.clusters.push(cluster);
}
}
}
pub fn add_prim_to_start(
&mut self,
prim_instance: PrimitiveInstance,
prim_rect: LayoutRect,
spatial_node_index: SpatialNodeIndex,
flags: PrimitiveFlags,
) {
self.push(
prim_instance,
prim_rect,
spatial_node_index,
flags,
PrimitiveListPosition::Begin,
)
}
pub fn add_prim(
&mut self,
prim_instance: PrimitiveInstance,
prim_rect: LayoutRect,
spatial_node_index: SpatialNodeIndex,
flags: PrimitiveFlags,
) {
self.push(
prim_instance,
prim_rect,
spatial_node_index,
flags,
PrimitiveListPosition::End,
)
}
pub fn is_empty(&self) -> bool {
self.clusters.is_empty()
}
pub fn add_cluster(&mut self, cluster: PrimitiveCluster) {
self.clusters.push(cluster);
}
pub fn extend(&mut self, prim_list: PrimitiveList) {
self.clusters.extend(prim_list.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>,
pub apply_local_clip_rect: bool,
pub is_backface_visible: 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_handles: SmallVec<[GpuCacheHandle; 1]>,
pub spatial_node_index: SpatialNodeIndex,
pub estimated_local_rect: LayoutRect,
pub precise_local_rect: LayoutRect,
pub segments_are_valid: bool,
#[cfg_attr(feature = "capture", serde(skip))]
pub tile_cache: Option<Box<TileCacheInstance>>,
pub options: PictureOptions,
num_render_tasks: usize,
}
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!("cluster_count: {:?}", self.prim_list.clusters.len()));
pt.add_item(format!("estimated_local_rect: {:?}", self.estimated_local_rect));
pt.add_item(format!("precise_local_rect: {:?}", self.precise_local_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 cluster in &self.prim_list.clusters {
if cluster.flags.contains(ClusterFlags::IS_PICTURE) {
for instance in &cluster.prim_instances {
let index = match instance.kind {
PrimitiveInstanceKind::Picture { pic_index, .. } => pic_index,
_ => unreachable!(),
};
pictures[index.0].print(pictures, index, pt);
}
}
}
pt.end_level();
}
pub fn can_use_segments(&self) -> bool {
match self.raster_config {
Some(RasterConfig { composite_mode: PictureCompositeMode::MixBlend(..), .. }) |
Some(RasterConfig { composite_mode: PictureCompositeMode::Filter(..), .. }) |
Some(RasterConfig { composite_mode: PictureCompositeMode::ComponentTransferFilter(..), .. }) |
Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) |
Some(RasterConfig { composite_mode: PictureCompositeMode::SvgFilter(..), .. }) |
None => {
false
}
Some(RasterConfig { composite_mode: PictureCompositeMode::Blit(reason), ..}) => {
reason == BlitReason::CLIP
}
}
}
fn resolve_scene_properties(&mut self, properties: &SceneProperties) -> bool {
match self.requested_composite_mode {
Some(PictureCompositeMode::Filter(ref mut filter)) => {
match *filter {
Filter::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,
) {
if let Some(tile_cache) = self.tile_cache.take() {
if !tile_cache.tiles.is_empty() {
retained_tiles.caches.insert(
tile_cache.slice,
PictureCacheState {
tiles: tile_cache.tiles,
spatial_node_comparer: tile_cache.spatial_node_comparer,
opacity_bindings: tile_cache.opacity_bindings,
color_bindings: tile_cache.color_bindings,
root_transform: tile_cache.root_transform,
current_tile_size: tile_cache.current_tile_size,
native_surface: tile_cache.native_surface,
external_native_surface_cache: tile_cache.external_native_surface_cache,
virtual_offset: tile_cache.virtual_offset,
frame_id: tile_cache.frame_id,
allocations: PictureCacheRecycledAllocations {
old_opacity_bindings: tile_cache.old_opacity_bindings,
old_color_bindings: tile_cache.old_color_bindings,
compare_cache: tile_cache.compare_cache,
},
},
);
}
}
}
pub fn new_image(
requested_composite_mode: Option<PictureCompositeMode>,
context_3d: Picture3DContext<OrderedPictureChild>,
frame_output_pipeline_id: Option<PipelineId>,
apply_local_clip_rect: bool,
flags: PrimitiveFlags,
requested_raster_space: RasterSpace,
prim_list: PrimitiveList,
spatial_node_index: SpatialNodeIndex,
tile_cache: Option<Box<TileCacheInstance>>,
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_handles: SmallVec::new(),
apply_local_clip_rect,
is_backface_visible: flags.contains(PrimitiveFlags::IS_BACKFACE_VISIBLE),
requested_raster_space,
spatial_node_index,
estimated_local_rect: LayoutRect::zero(),
precise_local_rect: LayoutRect::zero(),
tile_cache,
options,
segments_are_valid: false,
num_render_tasks: 0,
}
}
pub fn get_raster_space(&self, spatial_tree: &SpatialTree) -> RasterSpace {
let spatial_node = &spatial_tree.spatial_nodes[self.spatial_node_index.0 as usize];
if spatial_node.is_ancestor_or_self_zooming {
let scale_factors = spatial_tree
.get_relative_transform(self.spatial_node_index, ROOT_SPATIAL_NODE_INDEX)
.scale_factors();
let scale = scale_factors.0.max(scale_factors.1).min(8.0);
let rounded_up = 2.0f32.powf(scale.log2().ceil());
RasterSpace::Local(rounded_up)
} else {
self.requested_raster_space
}
}
pub fn take_context(
&mut self,
pic_index: PictureIndex,
clipped_prim_bounding_rect: WorldRect,
surface_spatial_node_index: SpatialNodeIndex,
raster_spatial_node_index: SpatialNodeIndex,
parent_surface_index: SurfaceIndex,
parent_subpixel_mode: &SubpixelMode,
frame_state: &mut FrameBuildingState,
frame_context: &FrameBuildingContext,
scratch: &mut PrimitiveScratchBuffer,
tile_cache_logger: &mut TileCacheLogger,
) -> Option<(PictureContext, PictureState, PrimitiveList)> {
if !self.is_visible() {
return None;
}
let task_id = frame_state.surfaces[parent_surface_index.0].render_tasks.unwrap().port;
frame_state.render_tasks[task_id].children.reserve(self.num_render_tasks);
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,
parent_surface_index,
0.0,
)
}
};
let map_pic_to_world = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
surface_spatial_node_index,
frame_context.global_screen_world_rect,
frame_context.spatial_tree,
);
let pic_bounds = map_pic_to_world.unmap(&map_pic_to_world.bounds)
.unwrap_or_else(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.global_screen_world_rect,
frame_context.spatial_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
}
};
match self.raster_config {
Some(ref mut raster_config) => {
let pic_rect = self.precise_local_rect.cast_unit();
let mut device_pixel_scale = frame_state
.surfaces[raster_config.surface_index.0]
.device_pixel_scale;
let scale_factors = frame_state
.surfaces[raster_config.surface_index.0]
.scale_factors;
let clip_inflation = match raster_config.composite_mode {
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
let mut max_offset = vec2(0.0, 0.0);
let mut min_offset = vec2(0.0, 0.0);
for shadow in shadows {
let offset = layout_vector_as_picture_vector(shadow.offset);
max_offset = max_offset.max(offset);
min_offset = min_offset.min(offset);
}
let raster_min = map_pic_to_raster.map_vector(min_offset);
let raster_max = map_pic_to_raster.map_vector(max_offset);
let world_min = map_raster_to_world.map_vector(raster_min);
let world_max = map_raster_to_world.map_vector(raster_max);
SideOffsets2D::from_vectors_outer(
-world_max.max(vec2(0.0, 0.0)),
-world_min.min(vec2(0.0, 0.0)),
)
}
_ => SideOffsets2D::zero(),
};
let (mut clipped, mut unclipped) = match get_raster_rects(
pic_rect,
&map_pic_to_raster,
&map_raster_to_world,
clipped_prim_bounding_rect.outer_rect(clip_inflation),
device_pixel_scale,
) {
Some(info) => info,
None => {
return None
}
};
let transform = map_pic_to_raster.get_transform();
fn adjust_scale_for_max_surface_size(
raster_config: &RasterConfig,
max_target_size: i32,
pic_rect: PictureRect,
map_pic_to_raster: &SpaceMapper<PicturePixel, RasterPixel>,
map_raster_to_world: &SpaceMapper<RasterPixel, WorldPixel>,
clipped_prim_bounding_rect: WorldRect,
device_pixel_scale : &mut DevicePixelScale,
device_rect: &mut DeviceIntRect,
unclipped: &mut DeviceRect) -> Option<f32>
{
let limit = if raster_config.establishes_raster_root {
MAX_SURFACE_SIZE as i32
} else {
max_target_size
};
if device_rect.size.width > limit || device_rect.size.height > limit {
let scale = (limit as f32 - 1.0) /
(i32::max(device_rect.size.width, device_rect.size.height) as f32);
*device_pixel_scale = *device_pixel_scale * Scale::new(scale);
let new_device_rect = device_rect.to_f32() * Scale::new(scale);
*device_rect = new_device_rect.round_out().try_cast::<i32>().unwrap();
*unclipped = match get_raster_rects(
pic_rect,
&map_pic_to_raster,
&map_raster_to_world,
clipped_prim_bounding_rect,
*device_pixel_scale
) {
Some(info) => info.1,
None => {
return None
}
};
Some(scale)
}
else
{
None
}
}
let dep_info = match raster_config.composite_mode {
PictureCompositeMode::Filter(Filter::Blur(blur_radius)) => {
let blur_std_deviation = clamp_blur_radius(blur_radius, scale_factors) * device_pixel_scale.0;
let mut blur_std_deviation = DeviceSize::new(
blur_std_deviation * scale_factors.0,
blur_std_deviation * scale_factors.1
);
let mut device_rect = if self.options.inflate_if_required {
let inflation_factor = frame_state.surfaces[raster_config.surface_index.0].inflation_factor;
let inflation_factor = inflation_factor * device_pixel_scale.0;
let device_rect = clipped.to_f32()
.inflate(inflation_factor * scale_factors.0, inflation_factor * scale_factors.1)
.intersection(&unclipped)
.unwrap();
match device_rect.try_cast::<i32>() {
Some(rect) => rect,
None => {
return None
}
}
} else {
clipped
};
let mut original_size = device_rect.size;
device_rect.size = RenderTask::adjusted_blur_source_size(
device_rect.size,
blur_std_deviation,
);
if let Some(scale) = adjust_scale_for_max_surface_size(
raster_config, frame_context.fb_config.max_target_size,
pic_rect, &map_pic_to_raster, &map_raster_to_world,
clipped_prim_bounding_rect,
&mut device_pixel_scale, &mut device_rect, &mut unclipped,
) {
blur_std_deviation = blur_std_deviation * scale;
original_size = (original_size.to_f32() * scale).try_cast::<i32>().unwrap();
raster_config.root_scaling_factor = scale;
}
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&device_rect,
device_pixel_scale,
);
let picture_task_id = frame_state.render_tasks.add().init(
RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, device_rect.size),
unclipped.size,
pic_index,
device_rect.origin,
uv_rect_kind,
surface_spatial_node_index,
device_pixel_scale,
PrimitiveVisibilityMask::all(),
None,
None,
)
);
let blur_render_task_id = RenderTask::new_blur(
blur_std_deviation,
picture_task_id,
frame_state.render_tasks,
RenderTargetKind::Color,
ClearMode::Transparent,
None,
original_size,
);
Some((blur_render_task_id, picture_task_id))
}
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
let mut max_std_deviation = 0.0;
for shadow in shadows {
max_std_deviation = f32::max(max_std_deviation, shadow.blur_radius);
}
max_std_deviation = clamp_blur_radius(max_std_deviation, scale_factors) * device_pixel_scale.0;
let max_blur_range = max_std_deviation * BLUR_SAMPLE_SCALE;
let device_rect = clipped.to_f32()
.inflate(max_blur_range * scale_factors.0, max_blur_range * scale_factors.1)
.intersection(&unclipped)
.unwrap();
let mut device_rect = match device_rect.try_cast::<i32>() {
Some(rect) => rect,
None => {
return None
}
};
device_rect.size = RenderTask::adjusted_blur_source_size(
device_rect.size,
DeviceSize::new(
max_std_deviation * scale_factors.0,
max_std_deviation * scale_factors.1
),
);
if let Some(scale) = adjust_scale_for_max_surface_size(
raster_config, frame_context.fb_config.max_target_size,
pic_rect, &map_pic_to_raster, &map_raster_to_world,
clipped_prim_bounding_rect,
&mut device_pixel_scale, &mut device_rect, &mut unclipped,
) {
raster_config.root_scaling_factor = scale;
}
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&device_rect,
device_pixel_scale,
);
let picture_task_id = frame_state.render_tasks.add().init({
let mut picture_task = RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, device_rect.size),
unclipped.size,
pic_index,
device_rect.origin,
uv_rect_kind,
surface_spatial_node_index,
device_pixel_scale,
PrimitiveVisibilityMask::all(),
None,
None,
);
picture_task.mark_for_saving();
picture_task
});
self.secondary_render_task_id = Some(picture_task_id);
let mut blur_tasks = BlurTaskCache::default();
self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new());
let mut blur_render_task_id = picture_task_id;
for shadow in shadows {
let blur_radius = clamp_blur_radius(shadow.blur_radius, scale_factors) * device_pixel_scale.0;
blur_render_task_id = RenderTask::new_blur(
DeviceSize::new(
blur_radius * scale_factors.0,
blur_radius * scale_factors.1,
),
picture_task_id,
frame_state.render_tasks,
RenderTargetKind::Color,
ClearMode::Transparent,
Some(&mut blur_tasks),
device_rect.size,
);
}
Some((blur_render_task_id, picture_task_id))
}
PictureCompositeMode::MixBlend(..) if !frame_context.fb_config.gpu_supports_advanced_blend => {
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&clipped,
device_pixel_scale,
);
let readback_task_id = frame_state.render_tasks.add().init(
RenderTask::new_readback(clipped)
);
frame_state.render_tasks.add_dependency(
frame_state.surfaces[parent_surface_index.0].render_tasks.unwrap().port,
readback_task_id,
);
self.secondary_render_task_id = Some(readback_task_id);
let render_task_id = frame_state.render_tasks.add().init(
RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, clipped.size),
unclipped.size,
pic_index,
clipped.origin,
uv_rect_kind,
surface_spatial_node_index,
device_pixel_scale,
PrimitiveVisibilityMask::all(),
None,
None,
)
);
Some((render_task_id, render_task_id))
}
PictureCompositeMode::Filter(..) => {
if let Some(scale) = adjust_scale_for_max_surface_size(
raster_config, frame_context.fb_config.max_target_size,
pic_rect, &map_pic_to_raster, &map_raster_to_world,
clipped_prim_bounding_rect,
&mut device_pixel_scale, &mut clipped, &mut unclipped,
) {
raster_config.root_scaling_factor = scale;
}
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&clipped,
device_pixel_scale,
);
let render_task_id = frame_state.render_tasks.add().init(
RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, clipped.size),
unclipped.size,
pic_index,
clipped.origin,
uv_rect_kind,
surface_spatial_node_index,
device_pixel_scale,
PrimitiveVisibilityMask::all(),
None,
None,
)
);
Some((render_task_id, render_task_id))
}
PictureCompositeMode::ComponentTransferFilter(..) => {
if let Some(scale) = adjust_scale_for_max_surface_size(
raster_config, frame_context.fb_config.max_target_size,
pic_rect, &map_pic_to_raster, &map_raster_to_world,
clipped_prim_bounding_rect,
&mut device_pixel_scale, &mut clipped, &mut unclipped,
) {
raster_config.root_scaling_factor = scale;
}
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&clipped,
device_pixel_scale,
);
let render_task_id = frame_state.render_tasks.add().init(
RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, clipped.size),
unclipped.size,
pic_index,
clipped.origin,
uv_rect_kind,
surface_spatial_node_index,
device_pixel_scale,
PrimitiveVisibilityMask::all(),
None,
None,
)
);
Some((render_task_id, render_task_id))
}
PictureCompositeMode::TileCache { .. } => {
let tile_cache = self.tile_cache.as_mut().unwrap();
let mut first = true;
let world_clip_rect = map_pic_to_world
.map(&tile_cache.local_clip_rect)
.expect("bug: unable to map clip rect");
let device_clip_rect = (world_clip_rect * frame_context.global_device_pixel_scale).round();
for tile in tile_cache.tiles.values_mut() {
if tile.is_visible {
let device_draw_rect = device_clip_rect.intersection(&tile.device_valid_rect);
match device_draw_rect {
Some(device_draw_rect) => {
if tile_cache.spatial_node_index == ROOT_SPATIAL_NODE_INDEX &&
frame_state.composite_state.is_tile_occluded(tile.z_id, device_draw_rect) {
let surface = tile.surface.as_mut().expect("no tile surface set!");
if let TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { id, .. }, .. } = surface {
if let Some(id) = id.take() {
frame_state.resource_cache.destroy_compositor_tile(id);
}
}
tile.is_visible = false;
continue;
}
}
None => {
tile.is_visible = false;
}
}
}
if let Some(TileSurface::Texture { descriptor, .. }) = tile.surface.as_ref() {
if let SurfaceTextureDescriptor::TextureCache { ref handle, .. } = descriptor {
frame_state.resource_cache.texture_cache.request(
handle,
frame_state.gpu_cache,
);
}
}
if !tile.is_visible {
continue;
}
if frame_context.debug_flags.contains(DebugFlags::PICTURE_CACHING_DBG) {
tile.root.draw_debug_rects(
&map_pic_to_world,
tile.is_opaque,
tile.current_descriptor.local_valid_rect,
scratch,
frame_context.global_device_pixel_scale,
);
let label_offset = DeviceVector2D::new(20.0, 30.0);
let tile_device_rect = tile.world_tile_rect * frame_context.global_device_pixel_scale;
if tile_device_rect.size.height >= label_offset.y {
let surface = tile.surface.as_ref().expect("no tile surface set!");
scratch.push_debug_string(
tile_device_rect.origin + label_offset,
debug_colors::RED,
format!("{:?}: s={} is_opaque={} surface={}",
tile.id,
tile_cache.slice,
tile.is_opaque,
surface.kind(),
),
);
}
}
if let TileSurface::Texture { descriptor, .. } = tile.surface.as_mut().unwrap() {
match descriptor {
SurfaceTextureDescriptor::TextureCache { ref handle, .. } => {
if frame_state.resource_cache.texture_cache.is_allocated(handle) {
frame_state.resource_cache.texture_cache.request(handle, frame_state.gpu_cache);
} else {
tile.invalidate(None, InvalidationReason::NoTexture);
}
}
SurfaceTextureDescriptor::Native { id, .. } => {
if id.is_none() {
tile.invalidate(None, InvalidationReason::NoSurface);
}
}
}
}
tile.local_dirty_rect = tile.local_dirty_rect
.intersection(&tile.current_descriptor.local_valid_rect)
.unwrap_or_else(PictureRect::zero);
let world_dirty_rect = map_pic_to_world.map(&tile.local_dirty_rect).expect("bug");
let device_rect = (tile.world_tile_rect * frame_context.global_device_pixel_scale).round();
tile.device_dirty_rect = (world_dirty_rect * frame_context.global_device_pixel_scale)
.round_out()
.intersection(&device_rect)
.unwrap_or_else(DeviceRect::zero);
if tile.is_valid {
continue;
}
if let TileSurface::Texture { ref mut descriptor, ref mut visibility_mask } = tile.surface.as_mut().unwrap() {
match descriptor {
SurfaceTextureDescriptor::TextureCache { ref mut handle } => {
if !frame_state.resource_cache.texture_cache.is_allocated(handle) {
frame_state.resource_cache.texture_cache.update_picture_cache(
tile_cache.current_tile_size,
handle,
frame_state.gpu_cache,
);
}
}
SurfaceTextureDescriptor::Native { id } => {
if id.is_none() {
if tile_cache.native_surface.is_none() {
let opaque = frame_state
.resource_cache
.create_compositor_surface(
tile_cache.virtual_offset,
tile_cache.current_tile_size,
true,
);
let alpha = frame_state
.resource_cache
.create_compositor_surface(
tile_cache.virtual_offset,
tile_cache.current_tile_size,
false,
);
tile_cache.native_surface = Some(NativeSurface {
opaque,
alpha,
});
}
let surface_id = if tile.is_opaque {
tile_cache.native_surface.as_ref().unwrap().opaque
} else {
tile_cache.native_surface.as_ref().unwrap().alpha
};
let tile_id = NativeTileId {
surface_id,
x: tile.tile_offset.x,
y: tile.tile_offset.y,
};
frame_state.resource_cache.create_compositor_tile(tile_id);
*id = Some(tile_id);
}
}
}
*visibility_mask = PrimitiveVisibilityMask::empty();
let dirty_region_index = tile_cache.dirty_region.dirty_rects.len();
if dirty_region_index < PrimitiveVisibilityMask::MAX_DIRTY_REGIONS {
visibility_mask.set_visible(dirty_region_index);
tile_cache.dirty_region.push(
world_dirty_rect,
*visibility_mask,
);
} else {
visibility_mask.set_visible(PrimitiveVisibilityMask::MAX_DIRTY_REGIONS - 1);
tile_cache.dirty_region.include_rect(
PrimitiveVisibilityMask::MAX_DIRTY_REGIONS - 1,
world_dirty_rect,
);
}
let content_origin_f = tile.world_tile_rect.origin * device_pixel_scale;
let content_origin = content_origin_f.round();
debug_assert!((content_origin_f.x - content_origin.x).abs() < 0.01);
debug_assert!((content_origin_f.y - content_origin.y).abs() < 0.01);
let surface = descriptor.resolve(
frame_state.resource_cache,
tile_cache.current_tile_size,
);
let scissor_rect = tile.device_dirty_rect
.translate(-device_rect.origin.to_vector())
.round()
.to_i32();
let valid_rect = tile.device_valid_rect
.translate(-device_rect.origin.to_vector())
.round()
.to_i32();
let render_task_id = frame_state.render_tasks.add().init(
RenderTask::new_picture(
RenderTaskLocation::PictureCache {
size: tile_cache.current_tile_size,
surface,
},
tile_cache.current_tile_size.to_f32(),
pic_index,
content_origin.to_i32(),
UvRectKind::Rect,
surface_spatial_node_index,
device_pixel_scale,
*visibility_mask,
Some(scissor_rect),
Some(valid_rect),
)
);
frame_state.render_tasks.add_dependency(
frame_state.surfaces[parent_surface_index.0].render_tasks.unwrap().port,
render_task_id,
);
if first {
frame_state.surfaces[raster_config.surface_index.0].render_tasks = Some(SurfaceRenderTasks {
root: render_task_id,
port: render_task_id,
});
first = false;
}
}
tile.local_dirty_rect = PictureRect::zero();
tile.is_valid = true;
}
if frame_context.debug_flags.contains(DebugFlags::INVALIDATION_DBG) {
tile_cache.print();
}
None
}
PictureCompositeMode::MixBlend(..) |
PictureCompositeMode::Blit(_) => {
if let Some(scale) = adjust_scale_for_max_surface_size(
raster_config, frame_context.fb_config.max_target_size,
pic_rect, &map_pic_to_raster, &map_raster_to_world,
clipped_prim_bounding_rect,
&mut device_pixel_scale, &mut clipped, &mut unclipped,
) {
raster_config.root_scaling_factor = scale;
}
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&clipped,
device_pixel_scale,
);
let render_task_id = frame_state.render_tasks.add().init(
RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, clipped.size),
unclipped.size,
pic_index,
clipped.origin,
uv_rect_kind,
surface_spatial_node_index,
device_pixel_scale,
PrimitiveVisibilityMask::all(),
None,
None,
)
);
Some((render_task_id, render_task_id))
}
PictureCompositeMode::SvgFilter(ref primitives, ref filter_datas) => {
if let Some(scale) = adjust_scale_for_max_surface_size(
raster_config, frame_context.fb_config.max_target_size,
pic_rect, &map_pic_to_raster, &map_raster_to_world,
clipped_prim_bounding_rect,
&mut device_pixel_scale, &mut clipped, &mut unclipped,
) {
raster_config.root_scaling_factor = scale;
}
let uv_rect_kind = calculate_uv_rect_kind(
&pic_rect,
&transform,
&clipped,
device_pixel_scale,
);
let picture_task_id = frame_state.render_tasks.add().init(
RenderTask::new_picture(
RenderTaskLocation::Dynamic(None, clipped.size),
unclipped.size,
pic_index,
clipped.origin,
uv_rect_kind,
surface_spatial_node_index,
device_pixel_scale,
PrimitiveVisibilityMask::all(),
None,
None,
)
);
let filter_task_id = RenderTask::new_svg_filter(
primitives,
filter_datas,
&mut frame_state.render_tasks,
clipped.size,
uv_rect_kind,
picture_task_id,
device_pixel_scale,
);
Some((filter_task_id, picture_task_id))
}
};
if let Some((root, port)) = dep_info {
frame_state.surfaces[raster_config.surface_index.0].render_tasks = Some(SurfaceRenderTasks {
root,
port,
});
frame_state.render_tasks.add_dependency(
frame_state.surfaces[parent_surface_index.0].render_tasks.unwrap().port,
root,
);
}
}
None => {}
};
#[cfg(feature = "capture")]
{
if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) {
if let Some(ref tile_cache) = self.tile_cache
{
let mut tile_cache_tiny = TileCacheInstanceSerializer {
slice: tile_cache.slice,
tiles: FastHashMap::default(),
background_color: tile_cache.background_color,
fract_offset: tile_cache.fract_offset
};
for (key, tile) in &tile_cache.tiles {
tile_cache_tiny.tiles.insert(*key, TileSerializer {
rect: tile.local_tile_rect,
current_descriptor: tile.current_descriptor.clone(),
fract_offset: tile.fract_offset,
id: tile.id,
root: tile.root.clone(),
background_color: tile.background_color,
invalidation_reason: tile.invalidation_reason.clone()
});
}
let text = ron::ser::to_string_pretty(&tile_cache_tiny, Default::default()).unwrap();
tile_cache_logger.add(text, map_pic_to_world.get_transform());
}
}
}
#[cfg(not(feature = "capture"))]
{
let _tile_cache_logger = tile_cache_logger;
}
let state = PictureState {
map_local_to_pic,
map_pic_to_world,
map_pic_to_raster,
map_raster_to_world,
plane_splitter,
};
let mut dirty_region_count = 0;
if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) = self.raster_config {
let dirty_region = self.tile_cache.as_ref().unwrap().dirty_region.clone();
frame_state.push_dirty_region(dirty_region);
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 (is_passthrough, subpixel_mode) = match self.raster_config {
Some(RasterConfig { ref composite_mode, .. }) => {
let subpixel_mode = match composite_mode {
PictureCompositeMode::TileCache { .. } => {
self.tile_cache.as_ref().unwrap().subpixel_mode.clone()
}
PictureCompositeMode::Blit(..) |
PictureCompositeMode::ComponentTransferFilter(..) |
PictureCompositeMode::Filter(..) |
PictureCompositeMode::MixBlend(..) |
PictureCompositeMode::SvgFilter(..) => {
SubpixelMode::Deny
}
};
(false, subpixel_mode)
}
None => {
(true, SubpixelMode::Allow)
}
};
let subpixel_mode = match (parent_subpixel_mode, subpixel_mode) {
(SubpixelMode::Allow, SubpixelMode::Allow) => {
SubpixelMode::Allow
}
(SubpixelMode::Allow, SubpixelMode::Conditional { excluded_rects }) => {
SubpixelMode::Conditional {
excluded_rects: excluded_rects,
}
}
(SubpixelMode::Conditional { excluded_rects }, SubpixelMode::Allow) => {
SubpixelMode::Conditional {
excluded_rects: excluded_rects.clone(),
}
}
(SubpixelMode::Conditional { .. }, SubpixelMode::Conditional { ..}) => {
unreachable!("bug: only top level picture caches have conditional subpixel");
}
(SubpixelMode::Deny, _) | (_, SubpixelMode::Deny) => {
SubpixelMode::Deny
}
};
let context = PictureContext {
pic_index,
apply_local_clip_rect: self.apply_local_clip_rect,
is_passthrough,
raster_spatial_node_index,
surface_spatial_node_index,
surface_index,
dirty_region_count,
subpixel_mode,
};
let prim_list = mem::replace(&mut self.prim_list, PrimitiveList::empty());
Some((context, state, prim_list))
}
pub fn restore_context(
&mut self,
parent_surface_index: SurfaceIndex,
prim_list: PrimitiveList,
context: PictureContext,
state: PictureState,
frame_state: &mut FrameBuildingState,
) {
for _ in 0 .. context.dirty_region_count {
frame_state.pop_dirty_region();
}
let task_id = frame_state.surfaces[parent_surface_index.0].render_tasks.unwrap().port;
self.num_render_tasks = frame_state.render_tasks[task_id].children.len();
self.prim_list = prim_list;
self.state = Some(state);
}
pub fn take_state(&mut self) -> PictureState {
self.state.take().expect("bug: no state present!")
}
pub fn add_split_plane(
splitter: &mut PlaneSplitter,
spatial_tree: &SpatialTree,
prim_spatial_node_index: SpatialNodeIndex,
original_local_rect: LayoutRect,
combined_local_clip_rect: &LayoutRect,
world_rect: WorldRect,
plane_split_anchor: PlaneSplitAnchor,
) -> bool {
let transform = spatial_tree
.get_world_transform(prim_spatial_node_index);
let matrix = transform.clone().into_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 {
CoordinateSpaceMapping::Local => {
let polygon = Polygon::from_rect(
local_rect * Scale::new(1.0),
plane_split_anchor,
);
splitter.add(polygon);
}
CoordinateSpaceMapping::ScaleOffset(scale_offset) if scale_offset.scale == Vector2D::new(1.0, 1.0) => {
let inv_matrix = scale_offset.inverse().to_transform().cast();
let polygon = Polygon::from_transformed_rect_with_inverse(
local_rect,
&matrix,
&inv_matrix,
plane_split_anchor,
).unwrap();
splitter.add(polygon);
}
CoordinateSpaceMapping::ScaleOffset(_) |
CoordinateSpaceMapping::Transform(_) => {
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,
gpu_cache: &mut GpuCache,
spatial_tree: &SpatialTree,
) {
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 cluster = &self.prim_list.clusters[poly.anchor.cluster_index];
let spatial_node_index = cluster.spatial_node_index;
let transform = match spatial_tree
.get_world_transform(spatial_node_index)
.inverse()
{
Some(transform) => transform.into_transform(),
None => continue,
};
let local_points = [
transform.transform_point3d(poly.points[0].cast()),
transform.transform_point3d(poly.points[1].cast()),
transform.transform_point3d(poly.points[2].cast()),
transform.transform_point3d(poly.points[3].cast()),
];
if local_points.iter().any(|p| p.is_none()) {
continue;
}
let p0 = local_points[0].unwrap();
let p1 = local_points[1].unwrap();
let p2 = local_points[2].unwrap();
let p3 = local_points[3].unwrap();
let gpu_blocks = [
[p0.x, p0.y, p1.x, p1.y].into(),
[p2.x, p2.y, p3.x, p3.y].into(),
];
let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
let gpu_address = gpu_cache.get_address(&gpu_handle);
ordered.push(OrderedPictureChild {
anchor: poly.anchor,
spatial_node_index,
gpu_address,
});
}
}
fn pre_update(
&mut self,
state: &mut PictureUpdateState,
frame_context: &FrameBuildingContext,
) -> Option<PrimitiveList> {
self.raster_config = None;
if !self.resolve_scene_properties(frame_context.scene_properties) {
return None;
}
if !self.is_backface_visible {
if let Picture3DContext::Out = self.context_3d {
match frame_context.spatial_tree.get_local_visible_face(self.spatial_node_index) {
VisibleFace::Front => {}
VisibleFace::Back => 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(ref filter)) if filter.is_noop() => None,
Some(PictureCompositeMode::TileCache { .. }) => {
if state.composite_state.picture_caching_is_enabled {
Some(PictureCompositeMode::TileCache { })
} else {
None
}
},
ref mode => mode.clone(),
};
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 has_svg_filter = if let PictureCompositeMode::SvgFilter(..) = composite_mode {
true
} else {
false
};
let establishes_raster_root = has_svg_filter || frame_context.spatial_tree
.get_relative_transform(surface_spatial_node_index, parent_raster_node_index)
.is_perspective();
let raster_spatial_node_index = if establishes_raster_root {
surface_spatial_node_index
} else {
parent_raster_node_index
};
let scale_factors = frame_context
.spatial_tree
.get_relative_transform(surface_spatial_node_index, raster_spatial_node_index)
.scale_factors();
let mut inflation_factor = 0.0;
if self.options.inflate_if_required {
match composite_mode {
PictureCompositeMode::Filter(Filter::Blur(blur_radius)) => {
let blur_radius = clamp_blur_radius(blur_radius, scale_factors);
inflation_factor = blur_radius * BLUR_SAMPLE_SCALE;
}
PictureCompositeMode::SvgFilter(ref primitives, _) => {
let mut max = 0.0;
for primitive in primitives {
if let FilterPrimitiveKind::Blur(ref blur) = primitive.kind {
max = f32::max(max, blur.radius);
}
}
inflation_factor = clamp_blur_radius(max, scale_factors) * BLUR_SAMPLE_SCALE;
}
_ => {}
}
}
let surface = SurfaceInfo::new(
surface_spatial_node_index,
raster_spatial_node_index,
inflation_factor,
frame_context.global_screen_world_rect,
&frame_context.spatial_tree,
frame_context.global_device_pixel_scale,
scale_factors,
);
self.raster_config = Some(RasterConfig {
composite_mode,
establishes_raster_root,
surface_index: state.push_surface(surface),
root_scaling_factor: 1.0,
});
}
Some(mem::replace(&mut self.prim_list, PrimitiveList::empty()))
}
fn post_update(
&mut self,
prim_list: PrimitiveList,
state: &mut PictureUpdateState,
frame_context: &FrameBuildingContext,
data_stores: &mut DataStores,
) {
self.prim_list = prim_list;
state.pop_picture();
for cluster in &mut self.prim_list.clusters {
cluster.flags.remove(ClusterFlags::IS_VISIBLE);
if !cluster.flags.contains(ClusterFlags::IS_BACKFACE_VISIBLE) {
if let Picture3DContext::In { ancestor_index, .. } = self.context_3d {
match frame_context.spatial_tree
.get_relative_transform(cluster.spatial_node_index, ancestor_index)
.visible_face()
{
VisibleFace::Back => continue,
VisibleFace::Front => (),
}
}
}
let spatial_node = &frame_context
.spatial_tree
.spatial_nodes[cluster.spatial_node_index.0 as usize];
if !spatial_node.invertible {
continue;
}
if cluster.flags.contains(ClusterFlags::IS_BACKDROP_FILTER) {
let backdrop_to_world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
cluster.spatial_node_index,
LayoutRect::max_rect(),
frame_context.spatial_tree,
);
for prim_instance in &mut cluster.prim_instances {
match prim_instance.kind {
PrimitiveInstanceKind::Backdrop { data_handle, .. } => {
let prim_data = &mut data_stores.backdrop[data_handle];
let spatial_node_index = prim_data.kind.spatial_node_index;
let prim_to_world_mapper = SpaceMapper::new_with_target(
ROOT_SPATIAL_NODE_INDEX,
spatial_node_index,
LayoutRect::max_rect(),
frame_context.spatial_tree,
);
let prim_rect = prim_to_world_mapper.map(&prim_data.kind.border_rect).unwrap_or_else(LayoutRect::zero);
let prim_rect = backdrop_to_world_mapper.unmap(&prim_rect).unwrap_or_else(LayoutRect::zero);
prim_data.common.prim_rect = prim_rect;
prim_instance.local_clip_rect = prim_rect;
cluster.bounding_rect = cluster.bounding_rect.union(&prim_rect);
}
_ => {
panic!("BUG: unexpected deferred primitive kind for cluster updates");
}
}
}
}
let surface = state.current_surface_mut();
surface.map_local_to_surface.set_target_spatial_node(
cluster.spatial_node_index,
frame_context.spatial_tree,
);
cluster.flags.insert(ClusterFlags::IS_VISIBLE);
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 surface = state.current_surface_mut();
if self.options.inflate_if_required {
surface.rect = raster_config.composite_mode.inflate_picture_rect(surface.rect, surface.scale_factors);
let snap_surface_to_raster = SpaceSnapper::new_with_target(
surface.raster_spatial_node_index,
self.spatial_node_index,
surface.device_pixel_scale,
frame_context.spatial_tree,
);
surface.rect = snap_surface_to_raster.snap_rect(&surface.rect);
}
let mut surface_rect = surface.rect * Scale::new(1.0);
let surface_index = state.pop_surface();
debug_assert_eq!(surface_index, raster_config.surface_index);
if raster_config.establishes_raster_root
&& (surface_rect.size.width > MAX_SURFACE_SIZE
|| surface_rect.size.height > MAX_SURFACE_SIZE)
&& frame_context.debug_flags.contains(DebugFlags::DISABLE_RASTER_ROOT_SCALING)
{
raster_config.establishes_raster_root = false;
state.are_raster_roots_assigned = false;
}
self.estimated_local_rect = surface_rect;
self.precise_local_rect = surface_rect;
match raster_config.composite_mode {
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
for shadow in shadows {
let shadow_rect = self.estimated_local_rect.translate(shadow.offset);
surface_rect = surface_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.spatial_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,
frame_context: &FrameBuildingContext,
frame_state: &mut FrameBuildingState,
data_stores: &mut DataStores,
) -> bool {
let mut pic_state_for_children = self.take_state();
if let Some(ref mut splitter) = pic_state_for_children.plane_splitter {
self.resolve_split_planes(
splitter,
&mut frame_state.gpu_cache,
&frame_context.spatial_tree,
);
}
let raster_config = match self.raster_config {
Some(ref mut raster_config) => raster_config,
None => {
return true
}
};
match raster_config.composite_mode {
PictureCompositeMode::TileCache { .. } => {}
PictureCompositeMode::Filter(Filter::Blur(..)) => {}
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
self.extra_gpu_data_handles.resize(shadows.len(), GpuCacheHandle::new());
for (shadow, extra_handle) in shadows.iter().zip(self.extra_gpu_data_handles.iter_mut()) {
if let Some(mut request) = frame_state.gpu_cache.request(extra_handle) {
let shadow_rect = self.precise_local_rect.translate(shadow.offset);
request.push(shadow.color.premultiplied());
request.push(PremultipliedColorF::WHITE);
request.push([
self.precise_local_rect.size.width,
self.precise_local_rect.size.height,
0.0,
0.0,
]);
request.push(shadow_rect);
request.push([0.0, 0.0, 0.0, 0.0]);
}
}
}
PictureCompositeMode::MixBlend(..) if !frame_context.fb_config.gpu_supports_advanced_blend => {}
PictureCompositeMode::Filter(ref filter) => {
match *filter {
Filter::ColorMatrix(ref m) => {
if self.extra_gpu_data_handles.is_empty() {
self.extra_gpu_data_handles.push(GpuCacheHandle::new());
}
if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) {
for i in 0..5 {
request.push([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
}
}
}
Filter::Flood(ref color) => {
if self.extra_gpu_data_handles.is_empty() {
self.extra_gpu_data_handles.push(GpuCacheHandle::new());
}
if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handles[0]) {
request.push(color.to_array());
}
}
_ => {}
}
}
PictureCompositeMode::ComponentTransferFilter(handle) => {
let filter_data = &mut data_stores.filter_data[handle];
filter_data.update(frame_state);
}
PictureCompositeMode::MixBlend(..) |
PictureCompositeMode::Blit(_) |
PictureCompositeMode::SvgFilter(..) => {}
}
true
}
}
fn calculate_screen_uv(
local_pos: &PicturePoint,
transform: &PictureToRasterTransform,
rendered_rect: &DeviceRect,
device_pixel_scale: DevicePixelScale,
) -> DeviceHomogeneousVector {
let raster_pos = transform.transform_point2d_homogeneous(*local_pos);
DeviceHomogeneousVector::new(
(raster_pos.x * device_pixel_scale.0 - rendered_rect.origin.x * raster_pos.w) / rendered_rect.size.width,
(raster_pos.y * device_pixel_scale.0 - rendered_rect.origin.y * raster_pos.w) / rendered_rect.size.height,
0.0,
raster_pos.w,
)
}
fn calculate_uv_rect_kind(
pic_rect: &PictureRect,
transform: &PictureToRasterTransform,
rendered_rect: &DeviceIntRect,
device_pixel_scale: DevicePixelScale,
) -> UvRectKind {
let rendered_rect = rendered_rect.to_f32();
let top_left = calculate_screen_uv(
&pic_rect.origin,
transform,
&rendered_rect,
device_pixel_scale,
);
let top_right = calculate_screen_uv(
&pic_rect.top_right(),
transform,
&rendered_rect,
device_pixel_scale,
);
let bottom_left = calculate_screen_uv(
&pic_rect.bottom_left(),
transform,
&rendered_rect,
device_pixel_scale,
);
let bottom_right = calculate_screen_uv(
&pic_rect.bottom_right(),
transform,
&rendered_rect,
device_pixel_scale,
);
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,
spatial_tree: &SpatialTree,
) -> (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,
spatial_tree,
);
let raster_bounds = map_raster_to_world.unmap(&world_rect)
.unwrap_or_else(RasterRect::max_rect);
let map_pic_to_raster = SpaceMapper::new_with_target(
raster_spatial_node_index,
surface_spatial_node_index,
raster_bounds,
spatial_tree,
);
(map_raster_to_world, map_pic_to_raster)
}
fn get_transform_key(
spatial_node_index: SpatialNodeIndex,
cache_spatial_node_index: SpatialNodeIndex,
spatial_tree: &SpatialTree,
) -> TransformKey {
let transform = if cache_spatial_node_index >= spatial_node_index {
spatial_tree
.get_relative_transform(
cache_spatial_node_index,
spatial_node_index,
)
} else {
spatial_tree
.get_relative_transform(
spatial_node_index,
cache_spatial_node_index,
)
};
transform.into()
}
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
struct PrimitiveComparisonKey {
prev_index: PrimitiveDependencyIndex,
curr_index: PrimitiveDependencyIndex,
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ImageDependency {
pub key: ImageKey,
pub generation: ImageGeneration,
}
impl ImageDependency {
pub const INVALID: ImageDependency = ImageDependency {
key: ImageKey::DUMMY,
generation: ImageGeneration::INVALID,
};
}
struct PrimitiveComparer<'a> {
clip_comparer: CompareHelper<'a, ItemUid>,
transform_comparer: CompareHelper<'a, SpatialNodeKey>,
image_comparer: CompareHelper<'a, ImageDependency>,
opacity_comparer: CompareHelper<'a, OpacityBinding>,
color_comparer: CompareHelper<'a, ColorBinding>,
resource_cache: &'a ResourceCache,
spatial_node_comparer: &'a mut SpatialNodeComparer,
opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
}
impl<'a> PrimitiveComparer<'a> {
fn new(
prev: &'a TileDescriptor,
curr: &'a TileDescriptor,
resource_cache: &'a ResourceCache,
spatial_node_comparer: &'a mut SpatialNodeComparer,
opacity_bindings: &'a FastHashMap<PropertyBindingId, OpacityBindingInfo>,
color_bindings: &'a FastHashMap<PropertyBindingId, ColorBindingInfo>,
) -> Self {
let clip_comparer = CompareHelper::new(
&prev.clips,
&curr.clips,
);
let transform_comparer = CompareHelper::new(
&prev.transforms,
&curr.transforms,
);
let image_comparer = CompareHelper::new(
&prev.images,
&curr.images,
);
let opacity_comparer = CompareHelper::new(
&prev.opacity_bindings,
&curr.opacity_bindings,
);
let color_comparer = CompareHelper::new(
&prev.color_bindings,
&curr.color_bindings,
);
PrimitiveComparer {
clip_comparer,
transform_comparer,
image_comparer,
opacity_comparer,
color_comparer,
resource_cache,
spatial_node_comparer,
opacity_bindings,
color_bindings,
}
}
fn reset(&mut self) {
self.clip_comparer.reset();
self.transform_comparer.reset();
self.image_comparer.reset();
self.opacity_comparer.reset();
self.color_comparer.reset();
}
fn advance_prev(&mut self, prim: &PrimitiveDescriptor) {
self.clip_comparer.advance_prev(prim.clip_dep_count);
self.transform_comparer.advance_prev(prim.transform_dep_count);
self.image_comparer.advance_prev(prim.image_dep_count);
self.opacity_comparer.advance_prev(prim.opacity_binding_dep_count);
self.color_comparer.advance_prev(prim.color_binding_dep_count);
}
fn advance_curr(&mut self, prim: &PrimitiveDescriptor) {
self.clip_comparer.advance_curr(prim.clip_dep_count);
self.transform_comparer.advance_curr(prim.transform_dep_count);
self.image_comparer.advance_curr(prim.image_dep_count);
self.opacity_comparer.advance_curr(prim.opacity_binding_dep_count);
self.color_comparer.advance_curr(prim.color_binding_dep_count);
}
fn compare_prim(
&mut self,
prev: &PrimitiveDescriptor,
curr: &PrimitiveDescriptor,
opt_detail: Option<&mut PrimitiveCompareResultDetail>,
) -> PrimitiveCompareResult {
let resource_cache = self.resource_cache;
let spatial_node_comparer = &mut self.spatial_node_comparer;
let opacity_bindings = self.opacity_bindings;
let color_bindings = self.color_bindings;
if prev != curr {
if let Some(detail) = opt_detail {
*detail = PrimitiveCompareResultDetail::Descriptor{ old: *prev, new: *curr };
}
return PrimitiveCompareResult::Descriptor;
}
let mut clip_result = CompareHelperResult::Equal;
if !self.clip_comparer.is_same(
prev.clip_dep_count,
curr.clip_dep_count,
|prev, curr| {
prev == curr
},
if opt_detail.is_some() { Some(&mut clip_result) } else { None }
) {
if let Some(detail) = opt_detail { *detail = PrimitiveCompareResultDetail::Clip{ detail: clip_result }; }
return PrimitiveCompareResult::Clip;
}
let mut transform_result = CompareHelperResult::Equal;
if !self.transform_comparer.is_same(
prev.transform_dep_count,
curr.transform_dep_count,
|prev, curr| {
spatial_node_comparer.are_transforms_equivalent(prev, curr)
},
if opt_detail.is_some() { Some(&mut transform_result) } else { None },
) {
if let Some(detail) = opt_detail {
*detail = PrimitiveCompareResultDetail::Transform{ detail: transform_result };
}
return PrimitiveCompareResult::Transform;
}
let mut image_result = CompareHelperResult::Equal;
if !self.image_comparer.is_same(
prev.image_dep_count,
curr.image_dep_count,
|prev, curr| {
prev == curr &&
resource_cache.get_image_generation(curr.key) == curr.generation
},
if opt_detail.is_some() { Some(&mut image_result) } else { None },
) {
if let Some(detail) = opt_detail {
*detail = PrimitiveCompareResultDetail::Image{ detail: image_result };
}
return PrimitiveCompareResult::Image;
}
let mut bind_result = CompareHelperResult::Equal;
if !self.opacity_comparer.is_same(
prev.opacity_binding_dep_count,
curr.opacity_binding_dep_count,
|prev, curr| {
if prev != curr {
return false;
}
if let OpacityBinding::Binding(id) = curr {
if opacity_bindings
.get(id)
.map_or(true, |info| info.changed) {
return false;
}
}
true
},
if opt_detail.is_some() { Some(&mut bind_result) } else { None },
) {
if let Some(detail) = opt_detail {
*detail = PrimitiveCompareResultDetail::OpacityBinding{ detail: bind_result };
}
return PrimitiveCompareResult::OpacityBinding;
}
let mut bind_result = CompareHelperResult::Equal;
if !self.color_comparer.is_same(
prev.color_binding_dep_count,
curr.color_binding_dep_count,
|prev, curr| {
if prev != curr {
return false;
}
if let ColorBinding::Binding(id) = curr {
if color_bindings
.get(id)
.map_or(true, |info| info.changed) {
return false;
}
}
true
},
if opt_detail.is_some() { Some(&mut bind_result) } else { None },
) {
if let Some(detail) = opt_detail {
*detail = PrimitiveCompareResultDetail::ColorBinding{ detail: bind_result };
}
return PrimitiveCompareResult::ColorBinding;
}
PrimitiveCompareResult::Equal
}
}
#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum TileNodeKind {
Leaf {
#[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
prev_indices: Vec<PrimitiveDependencyIndex>,
#[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
curr_indices: Vec<PrimitiveDependencyIndex>,
#[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
dirty_tracker: u64,
#[cfg_attr(any(feature = "capture", feature = "replay"), serde(skip))]
frames_since_modified: usize,
},
Node {
children: Vec<TileNode>,
},
}
#[derive(Copy, Clone, PartialEq, Debug)]
enum TileModification {
Split,
Merge,
}
#[cfg_attr(any(feature="capture",feature="replay"), derive(Clone))]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct TileNode {
pub kind: TileNodeKind,
pub rect: PictureRect,
}
impl TileNode {
fn new_leaf(curr_indices: Vec<PrimitiveDependencyIndex>) -> Self {
TileNode {
kind: TileNodeKind::Leaf {
prev_indices: Vec::new(),
curr_indices,
dirty_tracker: 0,
frames_since_modified: 0,
},
rect: PictureRect::zero(),
}
}
fn draw_debug_rects(
&self,
pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
is_opaque: bool,
local_valid_rect: PictureRect,
scratch: &mut PrimitiveScratchBuffer,
global_device_pixel_scale: DevicePixelScale,
) {
match self.kind {
TileNodeKind::Leaf { dirty_tracker, .. } => {
let color = if (dirty_tracker & 1) != 0 {
debug_colors::RED
} else if is_opaque {
debug_colors::GREEN
} else {
debug_colors::YELLOW
};
if let Some(local_rect) = local_valid_rect.intersection(&self.rect) {
let world_rect = pic_to_world_mapper
.map(&local_rect)
.unwrap();
let device_rect = world_rect * global_device_pixel_scale;
let outer_color = color.scale_alpha(0.3);
let inner_color = outer_color.scale_alpha(0.5);
scratch.push_debug_rect(
device_rect.inflate(-3.0, -3.0),
outer_color,
inner_color
);
}
}
TileNodeKind::Node { ref children, .. } => {
for child in children.iter() {
child.draw_debug_rects(
pic_to_world_mapper,
is_opaque,
local_valid_rect,
scratch,
global_device_pixel_scale,
);
}
}
}
}
fn get_child_rects(
rect: &PictureRect,
result: &mut [PictureRect; 4],
) {
let p0 = rect.origin;
let half_size = PictureSize::new(rect.size.width * 0.5, rect.size.height * 0.5);
*result = [
PictureRect::new(
PicturePoint::new(p0.x, p0.y),
half_size,
),
PictureRect::new(
PicturePoint::new(p0.x + half_size.width, p0.y),
half_size,
),
PictureRect::new(
PicturePoint::new(p0.x, p0.y + half_size.height),
half_size,
),
PictureRect::new(
PicturePoint::new(p0.x + half_size.width, p0.y + half_size.height),
half_size,
),
];
}
fn clear(
&mut self,
rect: PictureRect,
) {
self.rect = rect;
match self.kind {
TileNodeKind::Leaf { ref mut prev_indices, ref mut curr_indices, ref mut dirty_tracker, ref mut frames_since_modified } => {
mem::swap(prev_indices, curr_indices);
curr_indices.clear();
*dirty_tracker = *dirty_tracker << 1;
*frames_since_modified += 1;
}
TileNodeKind::Node { ref mut children, .. } => {
let mut child_rects = [PictureRect::zero(); 4];
TileNode::get_child_rects(&rect, &mut child_rects);
assert_eq!(child_rects.len(), children.len());
for (child, rect) in children.iter_mut().zip(child_rects.iter()) {
child.clear(*rect);
}
}
}
}
fn add_prim(
&mut self,
index: PrimitiveDependencyIndex,
prim_rect: &PictureRect,
) {
match self.kind {
TileNodeKind::Leaf { ref mut curr_indices, .. } => {
curr_indices.push(index);
}
TileNodeKind::Node { ref mut children, .. } => {
for child in children.iter_mut() {
if child.rect.intersects(prim_rect) {
child.add_prim(index, prim_rect);
}
}
}
}
}
fn maybe_merge_or_split(
&mut self,
level: i32,
curr_prims: &[PrimitiveDescriptor],
max_split_levels: i32,
) {
let mut tile_mod = None;
fn get_dirty_frames(
dirty_tracker: u64,
frames_since_modified: usize,
) -> Option<u32> {
if frames_since_modified > 64 {
Some(dirty_tracker.count_ones())
} else {
None
}
}
match self.kind {
TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } => {
if level < max_split_levels {
if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) {
if dirty_frames > 32 {
tile_mod = Some(TileModification::Split);
}
}
}
}
TileNodeKind::Node { ref children, .. } => {
let mut static_count = 0;
let mut changing_count = 0;
for child in children {
if let TileNodeKind::Leaf { dirty_tracker, frames_since_modified, .. } = child.kind {
if let Some(dirty_frames) = get_dirty_frames(dirty_tracker, frames_since_modified) {
if dirty_frames == 0 {
static_count += 1;
} else if dirty_frames == 64 {
changing_count += 1;
}
}
}
if static_count == 4 || changing_count == 4 {
tile_mod = Some(TileModification::Merge);
}
}
}
}
match tile_mod {
Some(TileModification::Split) => {
let curr_indices = match self.kind {
TileNodeKind::Node { .. } => {
unreachable!("bug - only leaves can split");
}
TileNodeKind::Leaf { ref mut curr_indices, .. } => {
curr_indices.take()
}
};
let mut child_rects = [PictureRect::zero(); 4];
TileNode::get_child_rects(&self.rect, &mut child_rects);
let mut child_indices = [
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
];
for index in curr_indices {
let prim = &curr_prims[index.0 as usize];
for (child_rect, indices) in child_rects.iter().zip(child_indices.iter_mut()) {
let child_rect_key: RectangleKey = (*child_rect).into();
if prim.prim_clip_rect.intersects(&child_rect_key) {
indices.push(index);
}
}
}
let children = child_indices
.iter_mut()
.map(|i| TileNode::new_leaf(mem::replace(i, Vec::new())))
.collect();
self.kind = TileNodeKind::Node {
children,
};
}
Some(TileModification::Merge) => {
let merged_indices = match self.kind {
TileNodeKind::Node { ref mut children, .. } => {
let mut merged_indices = Vec::new();
for child in children.iter() {
let child_indices = match child.kind {
TileNodeKind::Leaf { ref curr_indices, .. } => {
curr_indices
}
TileNodeKind::Node { .. } => {
unreachable!("bug: child is not a leaf");
}
};
merged_indices.extend_from_slice(child_indices);
}
merged_indices.sort();
merged_indices.dedup();
merged_indices
}
TileNodeKind::Leaf { .. } => {
unreachable!("bug - trying to merge a leaf");
}
};
self.kind = TileNodeKind::Leaf {
prev_indices: Vec::new(),
curr_indices: merged_indices,
dirty_tracker: 0,
frames_since_modified: 0,
};
}
None => {
if let TileNodeKind::Node { ref mut children, .. } = self.kind {
for child in children.iter_mut() {
child.maybe_merge_or_split(
level+1,
curr_prims,
max_split_levels,
);
}
}
}
}
}
fn update_dirty_rects(
&mut self,
prev_prims: &[PrimitiveDescriptor],
curr_prims: &[PrimitiveDescriptor],
prim_comparer: &mut PrimitiveComparer,
dirty_rect: &mut PictureRect,
compare_cache: &mut FastHashMap<PrimitiveComparisonKey, PrimitiveCompareResult>,
invalidation_reason: &mut Option<InvalidationReason>,
frame_context: &FrameVisibilityContext,
) {
match self.kind {
TileNodeKind::Node { ref mut children, .. } => {
for child in children.iter_mut() {
child.update_dirty_rects(
prev_prims,
curr_prims,
prim_comparer,
dirty_rect,
compare_cache,
invalidation_reason,
frame_context,
);
}
}
TileNodeKind::Leaf { ref prev_indices, ref curr_indices, ref mut dirty_tracker, .. } => {
if prev_indices.len() == curr_indices.len() {
let mut prev_i0 = 0;
let mut prev_i1 = 0;
prim_comparer.reset();
for (prev_index, curr_index) in prev_indices.iter().zip(curr_indices.iter()) {
let i0 = prev_index.0 as usize;
let i1 = curr_index.0 as usize;
for i in prev_i0 .. i0 {
prim_comparer.advance_prev(&prev_prims[i]);
}
for i in prev_i1 .. i1 {
prim_comparer.advance_curr(&curr_prims[i]);
}
let key = PrimitiveComparisonKey {
prev_index: *prev_index,
curr_index: *curr_index,
};
#[cfg(any(feature = "capture", feature = "replay"))]
let mut compare_detail = PrimitiveCompareResultDetail::Equal;
#[cfg(any(feature = "capture", feature = "replay"))]
let prim_compare_result_detail =
if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) {
Some(&mut compare_detail)
} else {
None
};
#[cfg(not(any(feature = "capture", feature = "replay")))]
let compare_detail = PrimitiveCompareResultDetail::Equal;
#[cfg(not(any(feature = "capture", feature = "replay")))]
let prim_compare_result_detail = None;
let prim_compare_result = *compare_cache
.entry(key)
.or_insert_with(|| {
let prev = &prev_prims[i0];
let curr = &curr_prims[i1];
prim_comparer.compare_prim(prev, curr, prim_compare_result_detail)
});
if prim_compare_result != PrimitiveCompareResult::Equal {
if invalidation_reason.is_none() {
*invalidation_reason = Some(InvalidationReason::Content {
prim_compare_result,
prim_compare_result_detail: Some(compare_detail)
});
}
*dirty_rect = self.rect.union(dirty_rect);
*dirty_tracker = *dirty_tracker | 1;
break;
}
prev_i0 = i0;
prev_i1 = i1;
}
} else {
if invalidation_reason.is_none() {
#[cfg(any(feature = "capture", feature = "replay"))]
{
if frame_context.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) {
let old = prev_indices.iter().map( |i| prev_prims[i.0 as usize].prim_uid ).collect();
let new = curr_indices.iter().map( |i| curr_prims[i.0 as usize].prim_uid ).collect();
*invalidation_reason = Some(InvalidationReason::PrimCount {
old: Some(old),
new: Some(new) });
} else {
*invalidation_reason = Some(InvalidationReason::PrimCount {
old: None,
new: None });
}
}
#[cfg(not(any(feature = "capture", feature = "replay")))]
{
*invalidation_reason = Some(InvalidationReason::PrimCount {
old: None,
new: None });
}
}
*dirty_rect = self.rect.union(dirty_rect);
*dirty_tracker = *dirty_tracker | 1;
}
}
}
}
}
impl CompositeState {
pub fn destroy_native_tiles<'a, I: Iterator<Item = &'a mut Box<Tile>>>(
&mut self,
tiles_iter: I,
resource_cache: &mut ResourceCache,
) {
if let CompositorKind::Native { .. } = self.compositor_kind {
for tile in tiles_iter {
if let Some(TileSurface::Texture { descriptor: SurfaceTextureDescriptor::Native { ref mut id, .. }, .. }) = tile.surface {
if let Some(id) = id.take() {
resource_cache.destroy_compositor_tile(id);
}
}
}
}
}
}