Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
//! Internal representation of clips in WebRender.
6
//!
7
//! # Data structures
8
//!
9
//! There are a number of data structures involved in the clip module:
10
//!
11
//! - ClipStore - Main interface used by other modules.
12
//!
13
//! - ClipItem - A single clip item (e.g. a rounded rect, or a box shadow).
14
//! These are an exposed API type, stored inline in a ClipNode.
15
//!
16
//! - ClipNode - A ClipItem with an attached GPU handle. The GPU handle is populated
17
//! when a ClipNodeInstance is built from this node (which happens while
18
//! preparing primitives for render).
19
//!
20
//! ClipNodeInstance - A ClipNode with attached positioning information (a spatial
21
//! node index). This is stored as a contiguous array of nodes
22
//! within the ClipStore.
23
//!
24
//! ```ascii
25
//! +-----------------------+-----------------------+-----------------------+
26
//! | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance |
27
//! +-----------------------+-----------------------+-----------------------+
28
//! | ClipItem | ClipItem | ClipItem |
29
//! | Spatial Node Index | Spatial Node Index | Spatial Node Index |
30
//! | GPU cache handle | GPU cache handle | GPU cache handle |
31
//! | ... | ... | ... |
32
//! +-----------------------+-----------------------+-----------------------+
33
//! 0 1 2
34
//! +----------------+ | |
35
//! | ClipNodeRange |____| |
36
//! | index: 1 | |
37
//! | count: 2 |___________________________________________________|
38
//! +----------------+
39
//! ```
40
//!
41
//! - ClipNodeRange - A clip item range identifies a range of clip nodes instances.
42
//! It is stored as an (index, count).
43
//!
44
//! - ClipChainNode - A clip chain node contains a handle to an interned clip item,
45
//! positioning information (from where the clip was defined), and
46
//! an optional parent link to another ClipChainNode. ClipChainId
47
//! is an index into an array, or ClipChainId::NONE for no parent.
48
//!
49
//! ```ascii
50
//! +----------------+ ____+----------------+ ____+----------------+ /---> ClipChainId::NONE
51
//! | ClipChainNode | | | ClipChainNode | | | ClipChainNode | |
52
//! +----------------+ | +----------------+ | +----------------+ |
53
//! | ClipDataHandle | | | ClipDataHandle | | | ClipDataHandle | |
54
//! | Spatial index | | | Spatial index | | | Spatial index | |
55
//! | Parent Id |___| | Parent Id |___| | Parent Id |___|
56
//! | ... | | ... | | ... |
57
//! +----------------+ +----------------+ +----------------+
58
//! ```
59
//!
60
//! - ClipChainInstance - A ClipChain that has been built for a specific primitive + positioning node.
61
//!
62
//! When given a clip chain ID, and a local primitive rect and its spatial node, the clip module
63
//! creates a clip chain instance. This is a struct with various pieces of useful information
64
//! (such as a local clip rect). It also contains a (index, count)
65
//! range specifier into an index buffer of the ClipNodeInstance structures that are actually relevant
66
//! for this clip chain instance. The index buffer structure allows a single array to be used for
67
//! all of the clip-chain instances built in a single frame. Each entry in the index buffer
68
//! also stores some flags relevant to the clip node in this positioning context.
69
//!
70
//! ```ascii
71
//! +----------------------+
72
//! | ClipChainInstance |
73
//! +----------------------+
74
//! | ... |
75
//! | local_clip_rect |________________________________________________________________________
76
//! | clips_range |_______________ |
77
//! +----------------------+ | |
78
//! | |
79
//! +------------------+------------------+------------------+------------------+------------------+
80
//! | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance | ClipNodeInstance |
81
//! +------------------+------------------+------------------+------------------+------------------+
82
//! | flags | flags | flags | flags | flags |
83
//! | ... | ... | ... | ... | ... |
84
//! +------------------+------------------+------------------+------------------+------------------+
85
//! ```
86
//!
87
//! # Rendering clipped primitives
88
//!
89
//! See the [`segment` module documentation][segment.rs].
90
//!
91
//!
92
//! [segment.rs]: ../segment/index.html
93
//!
94
95
use api::{BorderRadius, ClipIntern, ClipMode, ComplexClipRegion, ImageMask};
96
use api::{BoxShadowClipMode, ImageKey, ImageRendering};
97
use api::units::*;
98
use crate::border::{ensure_no_corner_overlap, BorderRadiusAu};
99
use crate::box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
100
use crate::spatial_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialTree, SpatialNodeIndex};
101
use crate::ellipse::Ellipse;
102
use crate::gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
103
use crate::gpu_types::{BoxShadowStretchMode};
104
use crate::image::{self, Repetition};
105
use crate::intern;
106
use crate::prim_store::{ClipData, ImageMaskData, SpaceMapper, VisibleMaskImageTile};
107
use crate::prim_store::{PointKey, SizeKey, RectangleKey};
108
use crate::render_task_cache::to_cache_size;
109
use crate::resource_cache::{ImageRequest, ResourceCache};
110
use std::{iter, ops, u32};
111
use crate::util::{extract_inner_rect_safe, project_rect, ScaleOffset};
112
113
// Type definitions for interning clip nodes.
114
115
pub type ClipDataStore = intern::DataStore<ClipIntern>;
116
pub type ClipDataHandle = intern::Handle<ClipIntern>;
117
118
/// Helper to identify simple clips (normal rects) from other kinds of clips,
119
/// which can often be handled via fast code paths.
120
#[cfg_attr(feature = "capture", derive(Serialize))]
121
#[cfg_attr(feature = "replay", derive(Deserialize))]
122
#[derive(Debug, Copy, Clone, MallocSizeOf)]
123
pub enum ClipNodeKind {
124
/// A normal clip rectangle, with Clip mode.
125
Rectangle,
126
/// A rectangle with ClipOut, or any other kind of clip.
127
Complex,
128
}
129
130
// Result of comparing a clip node instance against a local rect.
131
#[derive(Debug)]
132
enum ClipResult {
133
// The clip does not affect the region at all.
134
Accept,
135
// The clip prevents the region from being drawn.
136
Reject,
137
// The clip affects part of the region. This may
138
// require a clip mask, depending on other factors.
139
Partial,
140
}
141
142
// A clip node is a single clip source, along with some
143
// positioning information and implementation details
144
// that control where the GPU data for this clip source
145
// can be found.
146
#[derive(Debug)]
147
#[cfg_attr(feature = "capture", derive(Serialize))]
148
#[cfg_attr(feature = "replay", derive(Deserialize))]
149
#[derive(MallocSizeOf)]
150
pub struct ClipNode {
151
pub item: ClipItem,
152
pub gpu_cache_handle: GpuCacheHandle,
153
}
154
155
// Convert from an interning key for a clip item
156
// to a clip node, which is cached in the document.
157
impl From<ClipItemKey> for ClipNode {
158
fn from(item: ClipItemKey) -> Self {
159
let kind = match item.kind {
160
ClipItemKeyKind::Rectangle(rect, mode) => {
161
ClipItemKind::Rectangle { rect: rect.into(), mode }
162
}
163
ClipItemKeyKind::RoundedRectangle(rect, radius, mode) => {
164
ClipItemKind::RoundedRectangle {
165
rect: rect.into(),
166
radius: radius.into(),
167
mode,
168
}
169
}
170
ClipItemKeyKind::ImageMask(rect, image, repeat) => {
171
ClipItemKind::Image {
172
image,
173
rect: rect.into(),
174
repeat,
175
}
176
}
177
ClipItemKeyKind::BoxShadow(shadow_rect_fract_offset, shadow_rect_size, shadow_radius, prim_shadow_rect, blur_radius, clip_mode) => {
178
ClipItemKind::new_box_shadow(
179
shadow_rect_fract_offset.into(),
180
shadow_rect_size.into(),
181
shadow_radius.into(),
182
prim_shadow_rect.into(),
183
blur_radius.to_f32_px(),
184
clip_mode,
185
)
186
}
187
};
188
189
ClipNode {
190
item: ClipItem {
191
kind,
192
spatial_node_index: item.spatial_node_index,
193
},
194
gpu_cache_handle: GpuCacheHandle::new(),
195
}
196
}
197
}
198
199
// Flags that are attached to instances of clip nodes.
200
bitflags! {
201
#[cfg_attr(feature = "capture", derive(Serialize))]
202
#[cfg_attr(feature = "replay", derive(Deserialize))]
203
#[derive(MallocSizeOf)]
204
pub struct ClipNodeFlags: u8 {
205
const SAME_SPATIAL_NODE = 0x1;
206
const SAME_COORD_SYSTEM = 0x2;
207
const USE_FAST_PATH = 0x4;
208
}
209
}
210
211
// Identifier for a clip chain. Clip chains are stored
212
// in a contiguous array in the clip store. They are
213
// identified by a simple index into that array.
214
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, Hash)]
215
#[cfg_attr(feature = "capture", derive(Serialize))]
216
pub struct ClipChainId(pub u32);
217
218
// The root of each clip chain is the NONE id. The
219
// value is specifically set to u32::MAX so that if
220
// any code accidentally tries to access the root
221
// node, a bounds error will occur.
222
impl ClipChainId {
223
pub const NONE: Self = ClipChainId(u32::MAX);
224
pub const INVALID: Self = ClipChainId(0xDEADBEEF);
225
}
226
227
// A clip chain node is an id for a range of clip sources,
228
// and a link to a parent clip chain node, or ClipChainId::NONE.
229
#[derive(Clone, Debug, MallocSizeOf)]
230
#[cfg_attr(feature = "capture", derive(Serialize))]
231
pub struct ClipChainNode {
232
pub handle: ClipDataHandle,
233
pub parent_clip_chain_id: ClipChainId,
234
}
235
236
// When a clip node is found to be valid for a
237
// clip chain instance, it's stored in an index
238
// buffer style structure. This struct contains
239
// an index to the node data itself, as well as
240
// some flags describing how this clip node instance
241
// is positioned.
242
#[derive(Debug, MallocSizeOf)]
243
#[cfg_attr(feature = "capture", derive(Serialize))]
244
#[cfg_attr(feature = "replay", derive(Deserialize))]
245
pub struct ClipNodeInstance {
246
pub handle: ClipDataHandle,
247
pub flags: ClipNodeFlags,
248
pub visible_tiles: Option<Vec<VisibleMaskImageTile>>,
249
}
250
251
// A range of clip node instances that were found by
252
// building a clip chain instance.
253
#[derive(Debug, Copy, Clone)]
254
#[cfg_attr(feature = "capture", derive(Serialize))]
255
#[cfg_attr(feature = "replay", derive(Deserialize))]
256
pub struct ClipNodeRange {
257
pub first: u32,
258
pub count: u32,
259
}
260
261
impl ClipNodeRange {
262
pub fn to_range(&self) -> ops::Range<usize> {
263
let start = self.first as usize;
264
let end = start + self.count as usize;
265
266
ops::Range {
267
start,
268
end,
269
}
270
}
271
}
272
273
/// A helper struct for converting between coordinate systems
274
/// of clip sources and primitives.
275
// todo(gw): optimize:
276
// separate arrays for matrices
277
// cache and only build as needed.
278
//TODO: merge with `CoordinateSpaceMapping`?
279
#[derive(Debug, MallocSizeOf)]
280
#[cfg_attr(feature = "capture", derive(Serialize))]
281
enum ClipSpaceConversion {
282
Local,
283
ScaleOffset(ScaleOffset),
284
Transform(LayoutToWorldTransform),
285
}
286
287
impl ClipSpaceConversion {
288
/// Construct a new clip space converter between two spatial nodes.
289
fn new(
290
prim_spatial_node_index: SpatialNodeIndex,
291
clip_spatial_node_index: SpatialNodeIndex,
292
spatial_tree: &SpatialTree,
293
) -> Self {
294
//Note: this code is different from `get_relative_transform` in a way that we only try
295
// getting the relative transform if it's Local or ScaleOffset,
296
// falling back to the world transform otherwise.
297
let clip_spatial_node = &spatial_tree
298
.spatial_nodes[clip_spatial_node_index.0 as usize];
299
let prim_spatial_node = &spatial_tree
300
.spatial_nodes[prim_spatial_node_index.0 as usize];
301
302
if prim_spatial_node_index == clip_spatial_node_index {
303
ClipSpaceConversion::Local
304
} else if prim_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
305
let scale_offset = prim_spatial_node.content_transform
306
.inverse()
307
.accumulate(&clip_spatial_node.content_transform);
308
ClipSpaceConversion::ScaleOffset(scale_offset)
309
} else {
310
ClipSpaceConversion::Transform(
311
spatial_tree
312
.get_world_transform(clip_spatial_node_index)
313
.into_transform()
314
)
315
}
316
}
317
318
fn to_flags(&self) -> ClipNodeFlags {
319
match *self {
320
ClipSpaceConversion::Local => {
321
ClipNodeFlags::SAME_SPATIAL_NODE | ClipNodeFlags::SAME_COORD_SYSTEM
322
}
323
ClipSpaceConversion::ScaleOffset(..) => {
324
ClipNodeFlags::SAME_COORD_SYSTEM
325
}
326
ClipSpaceConversion::Transform(..) => {
327
ClipNodeFlags::empty()
328
}
329
}
330
}
331
}
332
333
// Temporary information that is cached and reused
334
// during building of a clip chain instance.
335
#[derive(MallocSizeOf)]
336
#[cfg_attr(feature = "capture", derive(Serialize))]
337
struct ClipNodeInfo {
338
conversion: ClipSpaceConversion,
339
handle: ClipDataHandle,
340
}
341
342
impl ClipNodeInfo {
343
fn create_instance(
344
&self,
345
node: &ClipNode,
346
clipped_rect: &LayoutRect,
347
gpu_cache: &mut GpuCache,
348
resource_cache: &mut ResourceCache,
349
spatial_tree: &SpatialTree,
350
request_resources: bool,
351
) -> Option<ClipNodeInstance> {
352
// Calculate some flags that are required for the segment
353
// building logic.
354
let mut flags = self.conversion.to_flags();
355
356
// Some clip shaders support a fast path mode for simple clips.
357
// TODO(gw): We could also apply fast path when segments are created, since we only write
358
// the mask for a single corner at a time then, so can always consider radii uniform.
359
let is_raster_2d =
360
flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) ||
361
spatial_tree
362
.get_world_viewport_transform(node.item.spatial_node_index)
363
.is_2d_axis_aligned();
364
if is_raster_2d && node.item.kind.supports_fast_path_rendering() {
365
flags |= ClipNodeFlags::USE_FAST_PATH;
366
}
367
368
let mut visible_tiles = None;
369
370
if let ClipItemKind::Image { rect, image, repeat } = node.item.kind {
371
let request = ImageRequest {
372
key: image,
373
rendering: ImageRendering::Auto,
374
tile: None,
375
};
376
377
if let Some(props) = resource_cache.get_image_properties(image) {
378
if let Some(tile_size) = props.tiling {
379
let mut mask_tiles = Vec::new();
380
381
let visible_rect = if repeat {
382
*clipped_rect
383
} else {
384
clipped_rect.intersection(&rect).unwrap()
385
};
386
387
let repetitions = image::repetitions(
388
&rect,
389
&visible_rect,
390
rect.size,
391
);
392
393
for Repetition { origin, .. } in repetitions {
394
let layout_image_rect = LayoutRect {
395
origin,
396
size: rect.size,
397
};
398
let tiles = image::tiles(
399
&layout_image_rect,
400
&visible_rect,
401
&props.visible_rect,
402
tile_size as i32,
403
);
404
for tile in tiles {
405
if request_resources {
406
resource_cache.request_image(
407
request.with_tile(tile.offset),
408
gpu_cache,
409
);
410
}
411
mask_tiles.push(VisibleMaskImageTile {
412
tile_offset: tile.offset,
413
tile_rect: tile.rect,
414
});
415
}
416
}
417
visible_tiles = Some(mask_tiles);
418
} else if request_resources {
419
resource_cache.request_image(request, gpu_cache);
420
}
421
} else {
422
// If the supplied image key doesn't exist in the resource cache,
423
// skip the clip node since there is nothing to mask with.
424
warn!("Clip mask with missing image key {:?}", request.key);
425
return None;
426
}
427
}
428
429
Some(ClipNodeInstance {
430
handle: self.handle,
431
flags,
432
visible_tiles,
433
})
434
}
435
}
436
437
impl ClipNode {
438
pub fn update(
439
&mut self,
440
gpu_cache: &mut GpuCache,
441
device_pixel_scale: DevicePixelScale,
442
) {
443
match self.item.kind {
444
ClipItemKind::Image { rect, .. } => {
445
if let Some(request) = gpu_cache.request(&mut self.gpu_cache_handle) {
446
let data = ImageMaskData {
447
local_mask_size: rect.size,
448
};
449
data.write_gpu_blocks(request);
450
}
451
}
452
ClipItemKind::BoxShadow { ref mut source } => {
453
if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
454
request.push([
455
source.original_alloc_size.width,
456
source.original_alloc_size.height,
457
source.clip_mode as i32 as f32,
458
0.0,
459
]);
460
request.push([
461
source.stretch_mode_x as i32 as f32,
462
source.stretch_mode_y as i32 as f32,
463
0.0,
464
0.0,
465
]);
466
request.push(source.prim_shadow_rect);
467
}
468
470
// "the image that would be generated by applying to the shadow a
471
// Gaussian blur with a standard deviation equal to half the blur radius."
472
let blur_radius_dp = source.blur_radius * 0.5;
473
474
// Create scaling from requested size to cache size.
475
let content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
476
477
// Create the cache key for this box-shadow render task.
478
let cache_size = to_cache_size(source.shadow_rect_alloc_size * content_scale);
479
let bs_cache_key = BoxShadowCacheKey {
480
blur_radius_dp: (blur_radius_dp * content_scale.0).round() as i32,
481
clip_mode: source.clip_mode,
482
original_alloc_size: (source.original_alloc_size * content_scale).round().to_i32(),
483
br_top_left: (source.shadow_radius.top_left * content_scale).round().to_i32(),
484
br_top_right: (source.shadow_radius.top_right * content_scale).round().to_i32(),
485
br_bottom_right: (source.shadow_radius.bottom_right * content_scale).round().to_i32(),
486
br_bottom_left: (source.shadow_radius.bottom_left * content_scale).round().to_i32(),
487
};
488
489
source.cache_key = Some((cache_size, bs_cache_key));
490
491
if let Some(mut request) = gpu_cache.request(&mut source.clip_data_handle) {
492
let data = ClipData::rounded_rect(
493
source.minimal_shadow_rect.size,
494
&source.shadow_radius,
495
ClipMode::Clip,
496
);
497
498
data.write(&mut request);
499
}
500
}
501
ClipItemKind::Rectangle { rect, mode } => {
502
if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
503
let data = ClipData::uniform(rect.size, 0.0, mode);
504
data.write(&mut request);
505
}
506
}
507
ClipItemKind::RoundedRectangle { rect, ref radius, mode } => {
508
if let Some(mut request) = gpu_cache.request(&mut self.gpu_cache_handle) {
509
let data = ClipData::rounded_rect(rect.size, radius, mode);
510
data.write(&mut request);
511
}
512
}
513
}
514
}
515
}
516
517
/// The main clipping public interface that other modules access.
518
#[derive(MallocSizeOf)]
519
#[cfg_attr(feature = "capture", derive(Serialize))]
520
pub struct ClipStore {
521
pub clip_chain_nodes: Vec<ClipChainNode>,
522
pub clip_node_instances: Vec<ClipNodeInstance>,
523
524
active_clip_node_info: Vec<ClipNodeInfo>,
525
active_local_clip_rect: Option<LayoutRect>,
526
}
527
528
// A clip chain instance is what gets built for a given clip
529
// chain id + local primitive region + positioning node.
530
#[derive(Debug)]
531
#[cfg_attr(feature = "capture", derive(Serialize))]
532
pub struct ClipChainInstance {
533
pub clips_range: ClipNodeRange,
534
// Combined clip rect for clips that are in the
535
// same coordinate system as the primitive.
536
pub local_clip_rect: LayoutRect,
537
pub has_non_local_clips: bool,
538
// If true, this clip chain requires allocation
539
// of a clip mask.
540
pub needs_mask: bool,
541
// Combined clip rect in picture space (may
542
// be more conservative that local_clip_rect).
543
pub pic_clip_rect: PictureRect,
544
// Space, in which the `pic_clip_rect` is defined.
545
pub pic_spatial_node_index: SpatialNodeIndex,
546
}
547
548
impl ClipChainInstance {
549
pub fn empty() -> Self {
550
ClipChainInstance {
551
clips_range: ClipNodeRange {
552
first: 0,
553
count: 0,
554
},
555
local_clip_rect: LayoutRect::zero(),
556
has_non_local_clips: false,
557
needs_mask: false,
558
pic_clip_rect: PictureRect::zero(),
559
pic_spatial_node_index: ROOT_SPATIAL_NODE_INDEX,
560
}
561
}
562
}
563
564
/// Maintains a (flattened) list of clips for a given level in the surface level stack.
565
#[derive(Debug)]
566
pub struct ClipChainLevel {
567
/// These clips will be handled when compositing this surface into the parent,
568
/// and can thus be ignored on the primitives that are drawn as part of this surface.
569
shared_clips: Vec<ClipDataHandle>,
570
571
/// Index of the first element in ClipChainStack::clip that belongs to this level.
572
first_clip_index: usize,
573
/// Used to sanity check push/pop balance.
574
initial_clip_counts_len: usize,
575
}
576
577
/// Maintains a stack of clip chain ids that are currently active,
578
/// when a clip exists on a picture that has no surface, and is passed
579
/// on down to the child primitive(s).
580
///
581
///
582
/// In order to avoid many small vector allocations, all clip chain ids are
583
/// stored in a single vector instead of per-level.
584
/// Since we only work with the top-most level of the stack, we only need to
585
/// know the first index in the clips vector that belongs to each level. The
586
/// last index for the top-most level is always the end of the clips array.
587
///
588
/// Likewise, we push several clip chain ids to the clips array at each
589
/// push_clip, and the number of clip chain ids removed during pop_clip
590
/// must match. This is done by having a separate stack of clip counts
591
/// in the clip-stack rather than per-level to avoid vector allocations.
592
///
593
/// ```ascii
594
/// +----+----+---
595
/// levels: | | | ...
596
/// +----+----+---
597
/// |first \
598
/// | \
599
/// | \
600
/// +--+--+--+--+--+--+--
601
/// clips: | | | | | | | ...
602
/// +--+--+--+--+--+--+--
603
/// | / /
604
/// | / /
605
/// | / /
606
/// +--+--+--+--
607
/// clip_counts: | 1| 2| 2| ...
608
/// +--+--+--+--
609
/// ```
610
pub struct ClipChainStack {
611
/// A stack of clip chain lists. Each time a new surface is pushed,
612
/// a new level is added. Each time a new picture without surface is
613
/// pushed, it adds the picture clip chain to the clips vector in the
614
/// range belonging to the level (always the top-most level, so always
615
/// at the end of the clips array).
616
levels: Vec<ClipChainLevel>,
617
/// The actual stack of clip ids.
618
clips: Vec<ClipChainId>,
619
/// How many clip ids to pop from the vector each time we call pop_clip.
620
clip_counts: Vec<usize>,
621
}
622
623
impl ClipChainStack {
624
pub fn new() -> Self {
625
ClipChainStack {
626
levels: vec![
627
ClipChainLevel {
628
shared_clips: Vec::new(),
629
first_clip_index: 0,
630
initial_clip_counts_len: 0,
631
}
632
],
633
clips: Vec::new(),
634
clip_counts: Vec::new(),
635
}
636
}
637
638
/// Push a clip chain root onto the currently active list.
639
pub fn push_clip(
640
&mut self,
641
clip_chain_id: ClipChainId,
642
clip_store: &ClipStore,
643
) {
644
let mut clip_count = 0;
645
646
let mut current_clip_chain_id = clip_chain_id;
647
while current_clip_chain_id != ClipChainId::NONE {
648
let clip_chain_node = &clip_store.clip_chain_nodes[current_clip_chain_id.0 as usize];
649
let clip_uid = clip_chain_node.handle.uid();
650
651
// The clip is required, so long as it doesn't exist in any of the shared_clips
652
// array from this or any parent surfaces.
653
// TODO(gw): We could consider making this a HashSet if it ever shows up in
654
// profiles, but the typical array length is 2-3 elements.
655
let mut valid_clip = true;
656
for level in &self.levels {
657
if level.shared_clips.iter().any(|handle| {
658
handle.uid() == clip_uid
659
}) {
660
valid_clip = false;
661
break;
662
}
663
}
664
665
if valid_clip {
666
self.clips.push(current_clip_chain_id);
667
clip_count += 1;
668
}
669
670
current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
671
}
672
673
self.clip_counts.push(clip_count);
674
}
675
676
/// Pop a clip chain root from the currently active list.
677
pub fn pop_clip(&mut self) {
678
let count = self.clip_counts.pop().unwrap();
679
for _ in 0 .. count {
680
self.clips.pop().unwrap();
681
}
682
}
683
684
/// When a surface is created, it takes all clips and establishes a new
685
/// stack of clips to be propagated.
686
pub fn push_surface(
687
&mut self,
688
shared_clips: &[ClipDataHandle],
689
) {
690
let level = ClipChainLevel {
691
shared_clips: shared_clips.to_vec(),
692
first_clip_index: self.clips.len(),
693
initial_clip_counts_len: self.clip_counts.len(),
694
};
695
696
self.levels.push(level);
697
}
698
699
/// Pop a surface from the clip chain stack
700
pub fn pop_surface(&mut self) {
701
let level = self.levels.pop().unwrap();
702
assert!(self.clip_counts.len() == level.initial_clip_counts_len);
703
assert!(self.clips.len() == level.first_clip_index);
704
}
705
706
/// Get the list of currently active clip chains
707
pub fn current_clips_array(&self) -> &[ClipChainId] {
708
let first = self.levels.last().unwrap().first_clip_index;
709
&self.clips[first..]
710
}
711
}
712
713
impl ClipStore {
714
pub fn new() -> Self {
715
ClipStore {
716
clip_chain_nodes: Vec::new(),
717
clip_node_instances: Vec::new(),
718
719
active_clip_node_info: Vec::new(),
720
active_local_clip_rect: None,
721
}
722
}
723
724
pub fn get_clip_chain(&self, clip_chain_id: ClipChainId) -> &ClipChainNode {
725
&self.clip_chain_nodes[clip_chain_id.0 as usize]
726
}
727
728
pub fn add_clip_chain_node(
729
&mut self,
730
handle: ClipDataHandle,
731
parent_clip_chain_id: ClipChainId,
732
) -> ClipChainId {
733
let id = ClipChainId(self.clip_chain_nodes.len() as u32);
734
self.clip_chain_nodes.push(ClipChainNode {
735
handle,
736
parent_clip_chain_id,
737
});
738
id
739
}
740
741
pub fn get_instance_from_range(
742
&self,
743
node_range: &ClipNodeRange,
744
index: u32,
745
) -> &ClipNodeInstance {
746
&self.clip_node_instances[(node_range.first + index) as usize]
747
}
748
749
/// Setup the active clip chains for building a clip chain instance.
750
pub fn set_active_clips(
751
&mut self,
752
local_prim_clip_rect: LayoutRect,
753
spatial_node_index: SpatialNodeIndex,
754
clip_chains: &[ClipChainId],
755
spatial_tree: &SpatialTree,
756
clip_data_store: &ClipDataStore,
757
) {
758
self.active_clip_node_info.clear();
759
self.active_local_clip_rect = None;
760
761
let mut local_clip_rect = local_prim_clip_rect;
762
763
for clip_chain_id in clip_chains {
764
let clip_chain_node = &self.clip_chain_nodes[clip_chain_id.0 as usize];
765
766
if !add_clip_node_to_current_chain(
767
clip_chain_node,
768
spatial_node_index,
769
&mut local_clip_rect,
770
&mut self.active_clip_node_info,
771
clip_data_store,
772
spatial_tree,
773
) {
774
return;
775
}
776
}
777
778
self.active_local_clip_rect = Some(local_clip_rect);
779
}
780
781
/// Setup the active clip chains, based on an existing primitive clip chain instance.
782
pub fn set_active_clips_from_clip_chain(
783
&mut self,
784
prim_clip_chain: &ClipChainInstance,
785
prim_spatial_node_index: SpatialNodeIndex,
786
spatial_tree: &SpatialTree,
787
clip_data_store: &ClipDataStore,
788
) {
789
// TODO(gw): Although this does less work than set_active_clips(), it does
790
// still do some unnecessary work (such as the clip space conversion).
791
// We could consider optimizing this if it ever shows up in a profile.
792
793
self.active_clip_node_info.clear();
794
self.active_local_clip_rect = Some(prim_clip_chain.local_clip_rect);
795
796
let clip_instances = &self
797
.clip_node_instances[prim_clip_chain.clips_range.to_range()];
798
for clip_instance in clip_instances {
799
let clip_node = &clip_data_store[clip_instance.handle];
800
let conversion = ClipSpaceConversion::new(
801
prim_spatial_node_index,
802
clip_node.item.spatial_node_index,
803
spatial_tree,
804
);
805
self.active_clip_node_info.push(ClipNodeInfo {
806
handle: clip_instance.handle,
807
conversion,
808
});
809
}
810
}
811
812
/// The main interface external code uses. Given a local primitive, positioning
813
/// information, and a clip chain id, build an optimized clip chain instance.
814
pub fn build_clip_chain_instance(
815
&mut self,
816
local_prim_rect: LayoutRect,
817
prim_to_pic_mapper: &SpaceMapper<LayoutPixel, PicturePixel>,
818
pic_to_world_mapper: &SpaceMapper<PicturePixel, WorldPixel>,
819
spatial_tree: &SpatialTree,
820
gpu_cache: &mut GpuCache,
821
resource_cache: &mut ResourceCache,
822
device_pixel_scale: DevicePixelScale,
823
world_rect: &WorldRect,
824
clip_data_store: &mut ClipDataStore,
825
request_resources: bool,
826
is_chased: bool,
827
) -> Option<ClipChainInstance> {
828
let local_clip_rect = match self.active_local_clip_rect {
829
Some(rect) => rect,
830
None => return None,
831
};
832
if is_chased {
833
println!("\tbuilding clip chain instance with local rect {:?}", local_prim_rect);
834
}
835
836
let local_bounding_rect = local_prim_rect.intersection(&local_clip_rect)?;
837
let pic_clip_rect = prim_to_pic_mapper.map(&local_bounding_rect)?;
838
let world_clip_rect = pic_to_world_mapper.map(&pic_clip_rect)?;
839
840
// Now, we've collected all the clip nodes that *potentially* affect this
841
// primitive region, and reduced the size of the prim region as much as possible.
842
843
// Run through the clip nodes, and see which ones affect this prim region.
844
845
let first_clip_node_index = self.clip_node_instances.len() as u32;
846
let mut has_non_local_clips = false;
847
let mut needs_mask = false;
848
849
// For each potential clip node
850
for node_info in self.active_clip_node_info.drain(..) {
851
let node = &mut clip_data_store[node_info.handle];
852
853
// See how this clip affects the prim region.
854
let clip_result = match node_info.conversion {
855
ClipSpaceConversion::Local => {
856
node.item.kind.get_clip_result(&local_bounding_rect)
857
}
858
ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
859
has_non_local_clips = true;
860
node.item.kind.get_clip_result(&scale_offset.unmap_rect(&local_bounding_rect))
861
}
862
ClipSpaceConversion::Transform(ref transform) => {
863
has_non_local_clips = true;
864
node.item.kind.get_clip_result_complex(
865
transform,
866
&world_clip_rect,
867
world_rect,
868
)
869
}
870
};
871
872
if is_chased {
873
println!("\t\tclip {:?}", node.item);
874
println!("\t\tflags {:?}, resulted in {:?}", node_info.conversion.to_flags(), clip_result);
875
}
876
877
match clip_result {
878
ClipResult::Accept => {
879
// Doesn't affect the primitive at all, so skip adding to list
880
}
881
ClipResult::Reject => {
882
// Completely clips the supplied prim rect
883
return None;
884
}
885
ClipResult::Partial => {
886
// Needs a mask -> add to clip node indices
887
888
// TODO(gw): Ensure this only runs once on each node per frame?
889
node.update(
890
gpu_cache,
891
device_pixel_scale,
892
);
893
894
// Create the clip node instance for this clip node
895
if let Some(instance) = node_info.create_instance(
896
node,
897
&local_bounding_rect,
898
gpu_cache,
899
resource_cache,
900
spatial_tree,
901
request_resources,
902
) {
903
// As a special case, a partial accept of a clip rect that is
904
// in the same coordinate system as the primitive doesn't need
905
// a clip mask. Instead, it can be handled by the primitive
906
// vertex shader as part of the local clip rect. This is an
907
// important optimization for reducing the number of clip
908
// masks that are allocated on common pages.
909
needs_mask |= match node.item.kind {
910
ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } |
911
ClipItemKind::RoundedRectangle { .. } |
912
ClipItemKind::Image { .. } |
913
ClipItemKind::BoxShadow { .. } => {
914
true
915
}
916
917
ClipItemKind::Rectangle { mode: ClipMode::Clip, .. } => {
918
!instance.flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM)
919
}
920
};
921
922
// Store this in the index buffer for this clip chain instance.
923
self.clip_node_instances.push(instance);
924
}
925
}
926
}
927
}
928
929
// Get the range identifying the clip nodes in the index buffer.
930
let clips_range = ClipNodeRange {
931
first: first_clip_node_index,
932
count: self.clip_node_instances.len() as u32 - first_clip_node_index,
933
};
934
935
// Return a valid clip chain instance
936
Some(ClipChainInstance {
937
clips_range,
938
has_non_local_clips,
939
local_clip_rect,
940
pic_clip_rect,
941
pic_spatial_node_index: prim_to_pic_mapper.ref_spatial_node_index,
942
needs_mask,
943
})
944
}
945
946
pub fn clear_old_instances(&mut self) {
947
self.clip_node_instances.clear();
948
}
949
}
950
951
pub struct ComplexTranslateIter<I> {
952
source: I,
953
offset: LayoutVector2D,
954
}
955
956
impl<I: Iterator<Item = ComplexClipRegion>> Iterator for ComplexTranslateIter<I> {
957
type Item = ComplexClipRegion;
958
fn next(&mut self) -> Option<Self::Item> {
959
self.source
960
.next()
961
.map(|mut complex| {
962
complex.rect = complex.rect.translate(self.offset);
963
complex
964
})
965
}
966
}
967
968
#[derive(Clone, Debug)]
969
pub struct ClipRegion<I> {
970
pub main: LayoutRect,
971
pub image_mask: Option<ImageMask>,
972
pub complex_clips: I,
973
}
974
975
impl<J> ClipRegion<ComplexTranslateIter<J>> {
976
pub fn create_for_clip_node(
977
rect: LayoutRect,
978
complex_clips: J,
979
mut image_mask: Option<ImageMask>,
980
reference_frame_relative_offset: &LayoutVector2D,
981
) -> Self
982
where
983
J: Iterator<Item = ComplexClipRegion>
984
{
985
if let Some(ref mut image_mask) = image_mask {
986
image_mask.rect = image_mask.rect.translate(*reference_frame_relative_offset);
987
}
988
989
ClipRegion {
990
main: rect.translate(*reference_frame_relative_offset),
991
image_mask,
992
complex_clips: ComplexTranslateIter {
993
source: complex_clips,
994
offset: *reference_frame_relative_offset,
995
},
996
}
997
}
998
}
999
1000
impl ClipRegion<Option<ComplexClipRegion>> {
1001
pub fn create_for_clip_node_with_local_clip(
1002
local_clip: &LayoutRect,
1003
reference_frame_relative_offset: &LayoutVector2D
1004
) -> Self {
1005
ClipRegion {
1006
main: local_clip.translate(*reference_frame_relative_offset),
1007
image_mask: None,
1008
complex_clips: None,
1009
}
1010
}
1011
}
1012
1013
// The ClipItemKey is a hashable representation of the contents
1014
// of a clip item. It is used during interning to de-duplicate
1015
// clip nodes between frames and display lists. This allows quick
1016
// comparison of clip node equality by handle, and also allows
1017
// the uploaded GPU cache handle to be retained between display lists.
1018
// TODO(gw): Maybe we should consider constructing these directly
1019
// in the DL builder?
1020
#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
1021
#[cfg_attr(feature = "capture", derive(Serialize))]
1022
#[cfg_attr(feature = "replay", derive(Deserialize))]
1023
pub enum ClipItemKeyKind {
1024
Rectangle(RectangleKey, ClipMode),
1025
RoundedRectangle(RectangleKey, BorderRadiusAu, ClipMode),
1026
ImageMask(RectangleKey, ImageKey, bool),
1027
BoxShadow(PointKey, SizeKey, BorderRadiusAu, RectangleKey, Au, BoxShadowClipMode),
1028
}
1029
1030
impl ClipItemKeyKind {
1031
pub fn rectangle(rect: LayoutRect, mode: ClipMode) -> Self {
1032
ClipItemKeyKind::Rectangle(rect.into(), mode)
1033
}
1034
1035
pub fn rounded_rect(rect: LayoutRect, mut radii: BorderRadius, mode: ClipMode) -> Self {
1036
if radii.is_zero() {
1037
ClipItemKeyKind::rectangle(rect, mode)
1038
} else {
1039
ensure_no_corner_overlap(&mut radii, rect.size);
1040
ClipItemKeyKind::RoundedRectangle(
1041
rect.into(),
1042
radii.into(),
1043
mode,
1044
)
1045
}
1046
}
1047
1048
pub fn image_mask(image_mask: &ImageMask, mask_rect: LayoutRect) -> Self {
1049
ClipItemKeyKind::ImageMask(
1050
mask_rect.into(),
1051
image_mask.image,
1052
image_mask.repeat,
1053
)
1054
}
1055
1056
pub fn box_shadow(
1057
shadow_rect: LayoutRect,
1058
shadow_radius: BorderRadius,
1059
prim_shadow_rect: LayoutRect,
1060
blur_radius: f32,
1061
clip_mode: BoxShadowClipMode,
1062
) -> Self {
1063
// Get the fractional offsets required to match the
1064
// source rect with a minimal rect.
1065
let fract_offset = LayoutPoint::new(
1066
shadow_rect.origin.x.fract().abs(),
1067
shadow_rect.origin.y.fract().abs(),
1068
);
1069
1070
ClipItemKeyKind::BoxShadow(
1071
fract_offset.into(),
1072
shadow_rect.size.into(),
1073
shadow_radius.into(),
1074
prim_shadow_rect.into(),
1075
Au::from_f32_px(blur_radius),
1076
clip_mode,
1077
)
1078
}
1079
1080
pub fn node_kind(&self) -> ClipNodeKind {
1081
match *self {
1082
ClipItemKeyKind::Rectangle(_, ClipMode::Clip) => ClipNodeKind::Rectangle,
1083
1084
ClipItemKeyKind::Rectangle(_, ClipMode::ClipOut) |
1085
ClipItemKeyKind::RoundedRectangle(..) |
1086
ClipItemKeyKind::ImageMask(..) |
1087
ClipItemKeyKind::BoxShadow(..) => ClipNodeKind::Complex,
1088
}
1089
}
1090
}
1091
1092
#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
1093
#[cfg_attr(feature = "capture", derive(Serialize))]
1094
#[cfg_attr(feature = "replay", derive(Deserialize))]
1095
pub struct ClipItemKey {
1096
pub kind: ClipItemKeyKind,
1097
pub spatial_node_index: SpatialNodeIndex,
1098
}
1099
1100
/// The data available about an interned clip node during scene building
1101
#[derive(Debug, MallocSizeOf)]
1102
#[cfg_attr(feature = "capture", derive(Serialize))]
1103
#[cfg_attr(feature = "replay", derive(Deserialize))]
1104
pub struct ClipInternData {
1105
/// Whether this is a simple rectangle clip
1106
pub clip_node_kind: ClipNodeKind,
1107
/// The positioning node for this clip item
1108
pub spatial_node_index: SpatialNodeIndex,
1109
}
1110
1111
impl intern::InternDebug for ClipItemKey {}
1112
1113
impl intern::Internable for ClipIntern {
1114
type Key = ClipItemKey;
1115
type StoreData = ClipNode;
1116
type InternData = ClipInternData;
1117
}
1118
1119
#[derive(Debug, MallocSizeOf)]
1120
#[cfg_attr(feature = "capture", derive(Serialize))]
1121
#[cfg_attr(feature = "replay", derive(Deserialize))]
1122
pub enum ClipItemKind {
1123
Rectangle {
1124
rect: LayoutRect,
1125
mode: ClipMode,
1126
},
1127
RoundedRectangle {
1128
rect: LayoutRect,
1129
radius: BorderRadius,
1130
mode: ClipMode,
1131
},
1132
Image {
1133
image: ImageKey,
1134
rect: LayoutRect,
1135
repeat: bool,
1136
},
1137
BoxShadow {
1138
source: BoxShadowClipSource,
1139
},
1140
}
1141
1142
#[derive(Debug, MallocSizeOf)]
1143
#[cfg_attr(feature = "capture", derive(Serialize))]
1144
#[cfg_attr(feature = "replay", derive(Deserialize))]
1145
pub struct ClipItem {
1146
pub kind: ClipItemKind,
1147
pub spatial_node_index: SpatialNodeIndex,
1148
}
1149
1150
fn compute_box_shadow_parameters(
1151
shadow_rect_fract_offset: LayoutPoint,
1152
shadow_rect_size: LayoutSize,
1153
mut shadow_radius: BorderRadius,
1154
prim_shadow_rect: LayoutRect,
1155
blur_radius: f32,
1156
clip_mode: BoxShadowClipMode,
1157
) -> BoxShadowClipSource {
1158
// Make sure corners don't overlap.
1159
ensure_no_corner_overlap(&mut shadow_radius, shadow_rect_size);
1160
1161
let fract_size = LayoutSize::new(
1162
shadow_rect_size.width.fract().abs(),
1163
shadow_rect_size.height.fract().abs(),
1164
);
1165
1166
// Create a minimal size primitive mask to blur. In this
1167
// case, we ensure the size of each corner is the same,
1168
// to simplify the shader logic that stretches the blurred
1169
// result across the primitive.
1170
let max_corner_width = shadow_radius.top_left.width
1171
.max(shadow_radius.bottom_left.width)
1172
.max(shadow_radius.top_right.width)
1173
.max(shadow_radius.bottom_right.width);
1174
let max_corner_height = shadow_radius.top_left.height
1175
.max(shadow_radius.bottom_left.height)
1176
.max(shadow_radius.top_right.height)
1177
.max(shadow_radius.bottom_right.height);
1178
1179
// Get maximum distance that can be affected by given blur radius.
1180
let blur_region = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
1181
1182
// If the largest corner is smaller than the blur radius, we need to ensure
1183
// that it's big enough that the corners don't affect the middle segments.
1184
let used_corner_width = max_corner_width.max(blur_region);
1185
let used_corner_height = max_corner_height.max(blur_region);
1186
1187
// Minimal nine-patch size, corner + internal + corner.
1188
let min_shadow_rect_size = LayoutSize::new(
1189
2.0 * used_corner_width + blur_region,
1190
2.0 * used_corner_height + blur_region,
1191
);
1192
1193
// The minimal rect to blur.
1194
let mut minimal_shadow_rect = LayoutRect::new(
1195
LayoutPoint::new(
1196
blur_region + shadow_rect_fract_offset.x,
1197
blur_region + shadow_rect_fract_offset.y,
1198
),
1199
LayoutSize::new(
1200
min_shadow_rect_size.width + fract_size.width,
1201
min_shadow_rect_size.height + fract_size.height,
1202
),
1203
);
1204
1205
// If the width or height ends up being bigger than the original
1206
// primitive shadow rect, just blur the entire rect along that
1207
// axis and draw that as a simple blit. This is necessary for
1208
// correctness, since the blur of one corner may affect the blur
1209
// in another corner.
1210
let mut stretch_mode_x = BoxShadowStretchMode::Stretch;
1211
if shadow_rect_size.width < minimal_shadow_rect.size.width {
1212
minimal_shadow_rect.size.width = shadow_rect_size.width;
1213
stretch_mode_x = BoxShadowStretchMode::Simple;
1214
}
1215
1216
let mut stretch_mode_y = BoxShadowStretchMode::Stretch;
1217
if shadow_rect_size.height < minimal_shadow_rect.size.height {
1218
minimal_shadow_rect.size.height = shadow_rect_size.height;
1219
stretch_mode_y = BoxShadowStretchMode::Simple;
1220
}
1221
1222
// Expand the shadow rect by enough room for the blur to take effect.
1223
let shadow_rect_alloc_size = LayoutSize::new(
1224
2.0 * blur_region + minimal_shadow_rect.size.width.ceil(),
1225
2.0 * blur_region + minimal_shadow_rect.size.height.ceil(),
1226
);
1227
1228
BoxShadowClipSource {
1229
original_alloc_size: shadow_rect_alloc_size,
1230
shadow_rect_alloc_size,
1231
shadow_radius,
1232
prim_shadow_rect,
1233
blur_radius,
1234
clip_mode,
1235
stretch_mode_x,
1236
stretch_mode_y,
1237
cache_handle: None,
1238
cache_key: None,
1239
clip_data_handle: GpuCacheHandle::new(),
1240
minimal_shadow_rect,
1241
}
1242
}
1243
1244
impl ClipItemKind {
1245
pub fn new_box_shadow(
1246
shadow_rect_fract_offset: LayoutPoint,
1247
shadow_rect_size: LayoutSize,
1248
mut shadow_radius: BorderRadius,
1249
prim_shadow_rect: LayoutRect,
1250
blur_radius: f32,
1251
clip_mode: BoxShadowClipMode,
1252
) -> Self {
1253
let mut source = compute_box_shadow_parameters(
1254
shadow_rect_fract_offset,
1255
shadow_rect_size,
1256
shadow_radius,
1257
prim_shadow_rect,
1258
blur_radius,
1259
clip_mode,
1260
);
1261
1262
fn needed_downscaling(source: &BoxShadowClipSource) -> Option<f32> {
1263
// This size is fairly arbitrary, but it's the same as the size that
1264
// we use to avoid caching big blurred stacking contexts.
1265
//
1266
// If you change it, ensure that the reftests
1267
// box-shadow-large-blur-radius-* still hit the downscaling path,
1268
// and that they render correctly.
1269
const MAX_SIZE: f32 = 2048.;
1270
1271
let max_dimension =
1272
source.shadow_rect_alloc_size.width.max(source.shadow_rect_alloc_size.height);
1273
1274
if max_dimension > MAX_SIZE {
1275
Some(MAX_SIZE / max_dimension)
1276
} else {
1277
None
1278
}
1279
}
1280
1281
if let Some(downscale) = needed_downscaling(&source) {
1282
shadow_radius.bottom_left.height *= downscale;
1283
shadow_radius.bottom_left.width *= downscale;
1284
shadow_radius.bottom_right.height *= downscale;
1285
shadow_radius.bottom_right.width *= downscale;
1286
shadow_radius.top_left.height *= downscale;
1287
shadow_radius.top_left.width *= downscale;
1288
shadow_radius.top_right.height *= downscale;
1289
shadow_radius.top_right.width *= downscale;
1290
1291
let original_alloc_size = source.shadow_rect_alloc_size;
1292
1293
source = compute_box_shadow_parameters(
1294
shadow_rect_fract_offset * downscale,
1295
shadow_rect_size * downscale,
1296
shadow_radius,
1297
prim_shadow_rect,
1298
blur_radius * downscale,
1299
clip_mode,
1300
);
1301
source.original_alloc_size = original_alloc_size;
1302
}
1303
ClipItemKind::BoxShadow { source }
1304
}
1305
1306
/// Returns true if this clip mask can run through the fast path
1307
/// for the given clip item type.
1308
///
1309
/// Note: this logic has to match `ClipBatcher::add` behavior.
1310
fn supports_fast_path_rendering(&self) -> bool {
1311
match *self {
1312
ClipItemKind::Rectangle { .. } |
1313
ClipItemKind::Image { .. } |
1314
ClipItemKind::BoxShadow { .. } => {
1315
false
1316
}
1317
ClipItemKind::RoundedRectangle { ref radius, .. } => {
1318
// The rounded clip rect fast path shader can only work
1319
// if the radii are uniform.
1320
radius.is_uniform().is_some()
1321
}
1322
}
1323
}
1324
1325
// Get an optional clip rect that a clip source can provide to
1326
// reduce the size of a primitive region. This is typically
1327
// used to eliminate redundant clips, and reduce the size of
1328
// any clip mask that eventually gets drawn.
1329
pub fn get_local_clip_rect(&self) -> Option<LayoutRect> {
1330
match *self {
1331
ClipItemKind::Rectangle { rect, mode: ClipMode::Clip } => Some(rect),
1332
ClipItemKind::Rectangle { mode: ClipMode::ClipOut, .. } => None,
1333
ClipItemKind::RoundedRectangle { rect, mode: ClipMode::Clip, .. } => Some(rect),
1334
ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, .. } => None,
1335
ClipItemKind::Image { repeat, rect, .. } => {
1336
if repeat {
1337
None
1338
} else {
1339
Some(rect)
1340
}
1341
}
1342
ClipItemKind::BoxShadow { .. } => None,
1343
}
1344
}
1345
1346
fn get_clip_result_complex(
1347
&self,
1348
transform: &LayoutToWorldTransform,
1349
prim_world_rect: &WorldRect,
1350
world_rect: &WorldRect,
1351
) -> ClipResult {
1352
let visible_rect = match prim_world_rect.intersection(world_rect) {
1353
Some(rect) => rect,
1354
None => return ClipResult::Reject,
1355
};
1356
1357
let (clip_rect, inner_rect, mode) = match *self {
1358
ClipItemKind::Rectangle { rect, mode } => {
1359
(rect, Some(rect), mode)
1360
}
1361
ClipItemKind::RoundedRectangle { rect, ref radius, mode } => {
1362
let inner_clip_rect = extract_inner_rect_safe(&rect, radius);
1363
(rect, inner_clip_rect, mode)
1364
}
1365
ClipItemKind::Image { rect, repeat: false, .. } => {
1366
(rect, None, ClipMode::Clip)
1367
}
1368
ClipItemKind::Image { repeat: true, .. } |
1369
ClipItemKind::BoxShadow { .. } => {
1370
return ClipResult::Partial;
1371
}
1372
};
1373
1374
if let Some(ref inner_clip_rect) = inner_rect {
1375
if let Some(()) = projected_rect_contains(inner_clip_rect, transform, &visible_rect) {
1376
return match mode {
1377
ClipMode::Clip => ClipResult::Accept,
1378
ClipMode::ClipOut => ClipResult::Reject,
1379
};
1380
}
1381
}
1382
1383
match mode {
1384
ClipMode::Clip => {
1385
let outer_clip_rect = match project_rect(
1386
transform,
1387
&clip_rect,
1388
world_rect,
1389
) {
1390
Some(outer_clip_rect) => outer_clip_rect,
1391
None => return ClipResult::Partial,
1392
};
1393
1394
match outer_clip_rect.intersection(prim_world_rect) {
1395
Some(..) => {
1396
ClipResult::Partial
1397
}
1398
None => {
1399
ClipResult::Reject
1400
}
1401
}
1402
}
1403
ClipMode::ClipOut => ClipResult::Partial,
1404
}
1405
}
1406
1407
// Check how a given clip source affects a local primitive region.
1408
fn get_clip_result(
1409
&self,
1410
prim_rect: &LayoutRect,
1411
) -> ClipResult {
1412
match *self {
1413
ClipItemKind::Rectangle { rect, mode: ClipMode::Clip } => {
1414
if rect.contains_rect(prim_rect) {
1415
return ClipResult::Accept;
1416
}
1417
1418
match rect.intersection(prim_rect) {
1419
Some(..) => {
1420
ClipResult::Partial
1421
}
1422
None => {
1423
ClipResult::Reject
1424
}
1425
}
1426
}
1427
ClipItemKind::Rectangle { rect, mode: ClipMode::ClipOut } => {
1428
if rect.contains_rect(prim_rect) {
1429
return ClipResult::Reject;
1430
}
1431
1432
match rect.intersection(prim_rect) {
1433
Some(_) => {
1434
ClipResult::Partial
1435
}
1436
None => {
1437
ClipResult::Accept
1438
}
1439
}
1440
}
1441
ClipItemKind::RoundedRectangle { rect, ref radius, mode: ClipMode::Clip } => {
1442
// TODO(gw): Consider caching this in the ClipNode
1443
// if it ever shows in profiles.
1444
// TODO(gw): extract_inner_rect_safe is overly
1445
// conservative for this code!
1446
let inner_clip_rect = extract_inner_rect_safe(&rect, radius);
1447
if let Some(inner_clip_rect) = inner_clip_rect {
1448
if inner_clip_rect.contains_rect(prim_rect) {
1449
return ClipResult::Accept;
1450
}
1451
}
1452
1453
match rect.intersection(prim_rect) {
1454
Some(..) => {
1455
ClipResult::Partial
1456
}
1457
None => {
1458
ClipResult::Reject
1459
}
1460
}
1461
}
1462
ClipItemKind::RoundedRectangle { rect, ref radius, mode: ClipMode::ClipOut } => {
1463
// TODO(gw): Consider caching this in the ClipNode
1464
// if it ever shows in profiles.
1465
// TODO(gw): extract_inner_rect_safe is overly
1466
// conservative for this code!
1467
let inner_clip_rect = extract_inner_rect_safe(&rect, radius);
1468
if let Some(inner_clip_rect) = inner_clip_rect {
1469
if inner_clip_rect.contains_rect(prim_rect) {
1470
return ClipResult::Reject;
1471
}
1472
}
1473
1474
match rect.intersection(prim_rect) {
1475
Some(_) => {
1476
ClipResult::Partial
1477
}
1478
None => {
1479
ClipResult::Accept
1480
}
1481
}
1482
}
1483
ClipItemKind::Image { rect, repeat, .. } => {
1484
if repeat {
1485
ClipResult::Partial
1486
} else {
1487
match rect.intersection(prim_rect) {
1488
Some(..) => {
1489
ClipResult::Partial
1490
}
1491
None => {
1492
ClipResult::Reject
1493
}
1494
}
1495
}
1496
}
1497
ClipItemKind::BoxShadow { .. } => {
1498
ClipResult::Partial
1499
}
1500
}
1501
}
1502
}
1503
1504
/// Represents a local rect and a device space
1505
/// rectangles that are either outside or inside bounds.
1506
#[derive(Clone, Debug, PartialEq)]
1507
pub struct Geometry {
1508
pub local_rect: LayoutRect,
1509
pub device_rect: DeviceIntRect,
1510
}
1511
1512
impl From<LayoutRect> for Geometry {
1513
fn from(local_rect: LayoutRect) -> Self {
1514
Geometry {
1515
local_rect,
1516
device_rect: DeviceIntRect::zero(),
1517
}
1518
}
1519
}
1520
1521
pub fn rounded_rectangle_contains_point(
1522
point: &LayoutPoint,
1523
rect: &LayoutRect,
1524
radii: &BorderRadius
1525
) -> bool {
1526
if !rect.contains(*point) {
1527
return false;
1528
}
1529
1530
let top_left_center = rect.origin + radii.top_left.to_vector();
1531
if top_left_center.x > point.x && top_left_center.y > point.y &&
1532
!Ellipse::new(radii.top_left).contains(*point - top_left_center.to_vector()) {
1533
return false;
1534
}
1535
1536
let bottom_right_center = rect.bottom_right() - radii.bottom_right.to_vector();
1537
if bottom_right_center.x < point.x && bottom_right_center.y < point.y &&
1538
!Ellipse::new(radii.bottom_right).contains(*point - bottom_right_center.to_vector()) {
1539
return false;
1540
}
1541
1542
let top_right_center = rect.top_right() +
1543
LayoutVector2D::new(-radii.top_right.width, radii.top_right.height);
1544
if top_right_center.x < point.x && top_right_center.y > point.y &&
1545
!Ellipse::new(radii.top_right).contains(*point - top_right_center.to_vector()) {
1546
return false;
1547
}
1548
1549
let bottom_left_center = rect.bottom_left() +
1550
LayoutVector2D::new(radii.bottom_left.width, -radii.bottom_left.height);
1551
if bottom_left_center.x > point.x && bottom_left_center.y < point.y &&
1552
!Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) {
1553
return false;
1554
}
1555
1556
true
1557
}
1558
1559
pub fn projected_rect_contains(
1560
source_rect: &LayoutRect,
1561
transform: &LayoutToWorldTransform,
1562
target_rect: &WorldRect,
1563
) -> Option<()> {
1564
let points = [
1565
transform.transform_point2d(source_rect.origin)?,
1566
transform.transform_point2d(source_rect.top_right())?,
1567
transform.transform_point2d(source_rect.bottom_right())?,
1568
transform.transform_point2d(source_rect.bottom_left())?,
1569
];
1570
let target_points = [
1571
target_rect.origin,
1572
target_rect.top_right(),
1573
target_rect.bottom_right(),
1574
target_rect.bottom_left(),
1575
];
1576
1577
// iterate the edges of the transformed polygon
1578
for (a, b) in points
1579
.iter()
1580
.cloned()
1581
.zip(points[1..].iter().cloned().chain(iter::once(points[0])))
1582
{
1583
// check if every destination point is on the right of the edge
1584
for &c in target_points.iter() {
1585
if (b - a).cross(c - a) < 0.0 {
1586
return None
1587
}
1588
}
1589
}
1590
1591
Some(())
1592
}
1593
1594
// Add a clip node into the list of clips to be processed
1595
// for the current clip chain. Returns false if the clip
1596
// results in the entire primitive being culled out.
1597
fn add_clip_node_to_current_chain(
1598
node: &ClipChainNode,
1599
spatial_node_index: SpatialNodeIndex,
1600
local_clip_rect: &mut LayoutRect,
1601
clip_node_info: &mut Vec<ClipNodeInfo>,
1602
clip_data_store: &ClipDataStore,
1603
spatial_tree: &SpatialTree,
1604
) -> bool {
1605
let clip_node = &clip_data_store[node.handle];
1606
1607
// Determine the most efficient way to convert between coordinate
1608
// systems of the primitive and clip node.
1609
let conversion = ClipSpaceConversion::new(
1610
spatial_node_index,
1611
clip_node.item.spatial_node_index,
1612
spatial_tree,
1613
);
1614
1615
// If we can convert spaces, try to reduce the size of the region
1616
// requested, and cache the conversion information for the next step.
1617
if let Some(clip_rect) = clip_node.item.kind.get_local_clip_rect() {
1618
match conversion {
1619
ClipSpaceConversion::Local => {
1620
*local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
1621
Some(rect) => rect,
1622
None => return false,
1623
};
1624
}
1625
ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
1626
let clip_rect = scale_offset.map_rect(&clip_rect);
1627
*local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
1628
Some(rect) => rect,
1629
None => return false,
1630
};
1631
}
1632
ClipSpaceConversion::Transform(..) => {
1633
// TODO(gw): In the future, we can reduce the size
1634
// of the pic_clip_rect here. To do this,
1635
// we can use project_rect or the
1636
// inverse_rect_footprint method, depending
1637
// on the relationship of the clip, pic
1638
// and primitive spatial nodes.
1639
// I have left this for now until we
1640
// find some good test cases where this
1641
// would be a worthwhile perf win.
1642
}
1643
}
1644
}
1645
1646
clip_node_info.push(ClipNodeInfo {
1647
conversion,
1648
handle: node.handle,
1649
});
1650
1651
true
1652
}