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
//! Primitive segmentation
6
//!
7
//! # Overview
8
//!
9
//! Segmenting is the process of breaking rectangular primitives into smaller rectangular
10
//! primitives in order to extract parts that could benefit from a fast paths.
11
//!
12
//! Typically this is used to allow fully opaque segments to be rendered in the opaque
13
//! pass. For example when an opaque rectangle has a non-axis-aligned transform applied,
14
//! we usually have to apply some anti-aliasing around the edges which requires alpha
15
//! blending. By segmenting the edges out of the center of the primitive, we can keep a
16
//! large amount of pixels in the opaque pass.
17
//! Segmenting also lets us avoids rasterizing parts of clip masks that we know to have
18
//! no effect or to be fully masking. For example by segmenting the corners of a rounded
19
//! rectangle clip, we can optimize both rendering the mask and the primitive by only
20
//! rasterize the corners in the mask and not applying any clipping to the segments of
21
//! the primitive that don't overlap the borders.
22
//!
23
//! It is a flexible system in the sense that different sources of segmentation (for
24
//! example two rounded rectangle clips) can affect the segmentation, and the possibility
25
//! to segment some effects such as specific clip kinds does not necessarily mean the
26
//! primitive will actually be segmented.
27
//!
28
//! ## Segments and clipping
29
//!
30
//! Segments of a primitive can be either not clipped, fully clipped, or partially clipped.
31
//! In the first two case we don't need a clip mask. For each partially masked segments, a
32
//! mask is rasterized using a render task. All of the interesting steps happen during frame
33
//! building.
34
//!
35
//! - The first step is to determine the segmentation and write the associated GPU data.
36
//! See `PrimitiveInstance::build_segments_if_needed` and `write_brush_segment_description`
37
//! in `prim_store/mod.rs` which uses the segment builder of this module.
38
//! - The second step is to generate the mask render tasks.
39
//! See `BrushSegment::update_clip_task` and `RenderTask::new_mask`. For each segment that
40
//! needs a mask, the contribution of all clips that affect the segment is added to the
41
//! mask's render task.
42
//! - Segments are assigned to batches (See `batch.rs`). Segments of a given primitive can
43
//! be assigned to different batches.
44
//!
45
//! See also the [`clip` module documentation][clip.rs] for details about how clipping
46
//! information is represented.
47
//!
48
//!
49
//! [clip.rs]: ../clip/index.html
50
//!
51
52
use api::{BorderRadius, ClipMode};
53
use api::units::*;
54
use crate::prim_store::EdgeAaSegmentMask;
55
use std::{cmp, usize};
56
use crate::util::{extract_inner_rect_safe, RectHelpers};
57
use smallvec::SmallVec;
58
59
bitflags! {
60
pub struct ItemFlags: u8 {
61
const X_ACTIVE = 0x1;
62
const Y_ACTIVE = 0x2;
63
const HAS_MASK = 0x4;
64
}
65
}
66
67
// The segment builder outputs a list of these segments.
68
#[derive(Debug, PartialEq)]
69
pub struct Segment {
70
pub rect: LayoutRect,
71
pub has_mask: bool,
72
pub edge_flags: EdgeAaSegmentMask,
73
pub region_x: usize,
74
pub region_y: usize,
75
}
76
77
// The segment builder creates a list of x/y axis events
78
// that are used to build a segment list. Right now, we
79
// don't bother providing a list of *which* clip regions
80
// are active for a given segment. Instead, if there is
81
// any clip mask present in a segment, we will just end
82
// up drawing each of the masks to that segment clip.
83
// This is a fairly rare case, but we can detect this
84
// in the future and only apply clip masks that are
85
// relevant to each segment region.
86
// TODO(gw): Provide clip region info with each segment.
87
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)]
88
enum EventKind {
89
// Beginning of a clip (rounded) rect.
90
BeginClip,
91
// End of a clip (rounded) rect.
92
EndClip,
93
// Begin the next region in the primitive.
94
BeginRegion,
95
}
96
97
// Events must be ordered such that when the coordinates
98
// of two events are the same, the end events are processed
99
// before the begin events. This ensures that we're able
100
// to detect which regions are active for a given segment.
101
impl Ord for EventKind {
102
fn cmp(&self, other: &EventKind) -> cmp::Ordering {
103
match (*self, *other) {
104
(EventKind::BeginRegion, EventKind::BeginRegion) => {
105
panic!("bug: regions must be non-overlapping")
106
}
107
(EventKind::EndClip, EventKind::BeginRegion) |
108
(EventKind::BeginRegion, EventKind::BeginClip) => {
109
cmp::Ordering::Less
110
}
111
(EventKind::BeginClip, EventKind::BeginRegion) |
112
(EventKind::BeginRegion, EventKind::EndClip) => {
113
cmp::Ordering::Greater
114
}
115
(EventKind::BeginClip, EventKind::BeginClip) |
116
(EventKind::EndClip, EventKind::EndClip) => {
117
cmp::Ordering::Equal
118
}
119
(EventKind::BeginClip, EventKind::EndClip) => {
120
cmp::Ordering::Greater
121
}
122
(EventKind::EndClip, EventKind::BeginClip) => {
123
cmp::Ordering::Less
124
}
125
}
126
}
127
}
128
129
// A x/y event where we will create a vertex in the
130
// segment builder.
131
#[derive(Debug, Eq, PartialEq, PartialOrd)]
132
struct Event {
133
value: Au,
134
item_index: ItemIndex,
135
kind: EventKind,
136
}
137
138
impl Ord for Event {
139
fn cmp(&self, other: &Event) -> cmp::Ordering {
140
self.value
141
.cmp(&other.value)
142
.then(self.kind.cmp(&other.kind))
143
}
144
}
145
146
impl Event {
147
fn begin(value: f32, index: usize) -> Event {
148
Event {
149
value: Au::from_f32_px(value),
150
item_index: ItemIndex(index),
151
kind: EventKind::BeginClip,
152
}
153
}
154
155
fn end(value: f32, index: usize) -> Event {
156
Event {
157
value: Au::from_f32_px(value),
158
item_index: ItemIndex(index),
159
kind: EventKind::EndClip,
160
}
161
}
162
163
fn region(value: f32) -> Event {
164
Event {
165
value: Au::from_f32_px(value),
166
kind: EventKind::BeginRegion,
167
item_index: ItemIndex(usize::MAX),
168
}
169
}
170
171
fn update(
172
&self,
173
flag: ItemFlags,
174
items: &mut [Item],
175
region: &mut usize,
176
) {
177
let is_active = match self.kind {
178
EventKind::BeginClip => true,
179
EventKind::EndClip => false,
180
EventKind::BeginRegion => {
181
*region += 1;
182
return;
183
}
184
};
185
186
items[self.item_index.0].flags.set(flag, is_active);
187
}
188
}
189
190
// An item that provides some kind of clip region (either
191
// a clip in/out rect, or a mask region).
192
#[derive(Debug)]
193
struct Item {
194
rect: LayoutRect,
195
mode: Option<ClipMode>,
196
flags: ItemFlags,
197
}
198
199
impl Item {
200
fn new(
201
rect: LayoutRect,
202
mode: Option<ClipMode>,
203
has_mask: bool,
204
) -> Item {
205
let flags = if has_mask {
206
ItemFlags::HAS_MASK
207
} else {
208
ItemFlags::empty()
209
};
210
211
Item {
212
rect,
213
mode,
214
flags,
215
}
216
}
217
}
218
219
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)]
220
struct ItemIndex(usize);
221
222
// The main public interface to the segment module.
223
pub struct SegmentBuilder {
224
items: Vec<Item>,
225
inner_rect: Option<LayoutRect>,
226
bounding_rect: Option<LayoutRect>,
227
has_interesting_clips: bool,
228
229
#[cfg(debug_assertions)]
230
initialized: bool,
231
}
232
233
impl SegmentBuilder {
234
// Create a new segment builder, supplying the primitive
235
// local rect and associated local clip rect.
236
pub fn new() -> SegmentBuilder {
237
SegmentBuilder {
238
items: Vec::with_capacity(4),
239
bounding_rect: None,
240
inner_rect: None,
241
has_interesting_clips: false,
242
#[cfg(debug_assertions)]
243
initialized: false,
244
}
245
}
246
247
pub fn initialize(
248
&mut self,
249
local_rect: LayoutRect,
250
inner_rect: Option<LayoutRect>,
251
local_clip_rect: LayoutRect,
252
) {
253
self.items.clear();
254
self.inner_rect = inner_rect;
255
self.bounding_rect = Some(local_rect);
256
257
self.push_clip_rect(local_rect, None, ClipMode::Clip);
258
self.push_clip_rect(local_clip_rect, None, ClipMode::Clip);
259
260
// This must be set after the push_clip_rect calls above, since we
261
// want to skip segment building if those are the only clips.
262
self.has_interesting_clips = false;
263
264
#[cfg(debug_assertions)]
265
{
266
self.initialized = true;
267
}
268
}
269
270
// Push a region defined by an inner and outer rect where there
271
// is a mask required. This ensures that segments which intersect
272
// with these areas will get a clip mask task allocated. This
273
// is currently used to mark where a box-shadow region can affect
274
// the pixels of a clip-mask. It might be useful for other types
275
// such as dashed and dotted borders in the future.
276
pub fn push_mask_region(
277
&mut self,
278
outer_rect: LayoutRect,
279
inner_rect: LayoutRect,
280
inner_clip_mode: Option<ClipMode>,
281
) {
282
self.has_interesting_clips = true;
283
284
if !inner_rect.is_well_formed_and_nonempty() {
285
self.items.push(Item::new(
286
outer_rect,
287
None,
288
true
289
));
290
return;
291
}
292
293
debug_assert!(outer_rect.contains_rect(&inner_rect));
294
295
let p0 = outer_rect.origin;
296
let p1 = inner_rect.origin;
297
let p2 = inner_rect.bottom_right();
298
let p3 = outer_rect.bottom_right();
299
300
let segments = &[
301
LayoutRect::new(
302
LayoutPoint::new(p0.x, p0.y),
303
LayoutSize::new(p1.x - p0.x, p1.y - p0.y),
304
),
305
LayoutRect::new(
306
LayoutPoint::new(p2.x, p0.y),
307
LayoutSize::new(p3.x - p2.x, p1.y - p0.y),
308
),
309
LayoutRect::new(
310
LayoutPoint::new(p2.x, p2.y),
311
LayoutSize::new(p3.x - p2.x, p3.y - p2.y),
312
),
313
LayoutRect::new(
314
LayoutPoint::new(p0.x, p2.y),
315
LayoutSize::new(p1.x - p0.x, p3.y - p2.y),
316
),
317
LayoutRect::new(
318
LayoutPoint::new(p1.x, p0.y),
319
LayoutSize::new(p2.x - p1.x, p1.y - p0.y),
320
),
321
LayoutRect::new(
322
LayoutPoint::new(p2.x, p1.y),
323
LayoutSize::new(p3.x - p2.x, p2.y - p1.y),
324
),
325
LayoutRect::new(
326
LayoutPoint::new(p1.x, p2.y),
327
LayoutSize::new(p2.x - p1.x, p3.y - p2.y),
328
),
329
LayoutRect::new(
330
LayoutPoint::new(p0.x, p1.y),
331
LayoutSize::new(p1.x - p0.x, p2.y - p1.y),
332
),
333
];
334
335
for segment in segments {
336
self.items.push(Item::new(
337
*segment,
338
None,
339
true
340
));
341
}
342
343
if inner_clip_mode.is_some() {
344
self.items.push(Item::new(
345
inner_rect,
346
inner_clip_mode,
347
false,
348
));
349
}
350
}
351
352
// Push some kind of clipping region into the segment builder.
353
// If radius is None, it's a simple rect.
354
pub fn push_clip_rect(
355
&mut self,
356
rect: LayoutRect,
357
radius: Option<BorderRadius>,
358
mode: ClipMode,
359
) {
360
self.has_interesting_clips = true;
361
362
// Keep track of a minimal bounding rect for the set of
363
// segments that will be generated.
364
if mode == ClipMode::Clip {
365
self.bounding_rect = self.bounding_rect.and_then(|bounding_rect| {
366
bounding_rect.intersection(&rect)
367
});
368
}
369
let mode = Some(mode);
370
371
match radius {
372
Some(radius) => {
373
// For a rounded rect, try to create a nine-patch where there
374
// is a clip item for each corner, inner and edge region.
375
match extract_inner_rect_safe(&rect, &radius) {
376
Some(inner) => {
377
let p0 = rect.origin;
378
let p1 = inner.origin;
379
let p2 = inner.bottom_right();
380
let p3 = rect.bottom_right();
381
382
let corner_segments = &[
383
LayoutRect::new(
384
LayoutPoint::new(p0.x, p0.y),
385
LayoutSize::new(p1.x - p0.x, p1.y - p0.y),
386
),
387
LayoutRect::new(
388
LayoutPoint::new(p2.x, p0.y),
389
LayoutSize::new(p3.x - p2.x, p1.y - p0.y),
390
),
391
LayoutRect::new(
392
LayoutPoint::new(p2.x, p2.y),
393
LayoutSize::new(p3.x - p2.x, p3.y - p2.y),
394
),
395
LayoutRect::new(
396
LayoutPoint::new(p0.x, p2.y),
397
LayoutSize::new(p1.x - p0.x, p3.y - p2.y),
398
),
399
];
400
401
for segment in corner_segments {
402
self.items.push(Item::new(
403
*segment,
404
mode,
405
true
406
));
407
}
408
409
let other_segments = &[
410
LayoutRect::new(
411
LayoutPoint::new(p1.x, p0.y),
412
LayoutSize::new(p2.x - p1.x, p1.y - p0.y),
413
),
414
LayoutRect::new(
415
LayoutPoint::new(p2.x, p1.y),
416
LayoutSize::new(p3.x - p2.x, p2.y - p1.y),
417
),
418
LayoutRect::new(
419
LayoutPoint::new(p1.x, p2.y),
420
LayoutSize::new(p2.x - p1.x, p3.y - p2.y),
421
),
422
LayoutRect::new(
423
LayoutPoint::new(p0.x, p1.y),
424
LayoutSize::new(p1.x - p0.x, p2.y - p1.y),
425
),
426
LayoutRect::new(
427
LayoutPoint::new(p1.x, p1.y),
428
LayoutSize::new(p2.x - p1.x, p2.y - p1.y),
429
),
430
];
431
432
for segment in other_segments {
433
self.items.push(Item::new(
434
*segment,
435
mode,
436
false,
437
));
438
}
439
}
440
None => {
441
// If we get here, we could not extract an inner rectangle
442
// for this clip region. This can occur in cases such as
443
// a rounded rect where the top-left and bottom-left radii
444
// result in overlapping rects. In that case, just create
445
// a single clip region for the entire rounded rect.
446
self.items.push(Item::new(
447
rect,
448
mode,
449
true,
450
))
451
}
452
}
453
}
454
None => {
455
// For a simple rect, just create one clipping item.
456
self.items.push(Item::new(
457
rect,
458
mode,
459
false,
460
))
461
}
462
}
463
}
464
465
// Consume this segment builder and produce a list of segments.
466
pub fn build<F>(&mut self, mut f: F) where F: FnMut(&Segment) {
467
#[cfg(debug_assertions)]
468
debug_assert!(self.initialized);
469
470
#[cfg(debug_assertions)]
471
{
472
self.initialized = false;
473
}
474
475
let bounding_rect = match self.bounding_rect {
476
Some(bounding_rect) => bounding_rect,
477
None => return,
478
};
479
480
if !self.has_interesting_clips {
481
// There were no additional clips added, so don't bother building segments.
482
// Just emit a single segment for the bounding rect of the primitive.
483
f(&Segment {
484
edge_flags: EdgeAaSegmentMask::all(),
485
region_x: 0,
486
region_y: 0,
487
has_mask: false,
488
rect: bounding_rect,
489
});
490
return
491
}
492
493
// First, filter out any items that don't intersect
494
// with the visible bounding rect.
495
self.items.retain(|item| item.rect.intersects(&bounding_rect));
496
497
// Create events for each item
498
let mut x_events : SmallVec<[Event; 4]> = SmallVec::new();
499
let mut y_events : SmallVec<[Event; 4]> = SmallVec::new();
500
501
for (item_index, item) in self.items.iter().enumerate() {
502
let p0 = item.rect.origin;
503
let p1 = item.rect.bottom_right();
504
505
x_events.push(Event::begin(p0.x, item_index));
506
x_events.push(Event::end(p1.x, item_index));
507
y_events.push(Event::begin(p0.y, item_index));
508
y_events.push(Event::end(p1.y, item_index));
509
}
510
511
// Add the region events, if provided.
512
if let Some(inner_rect) = self.inner_rect {
513
x_events.push(Event::region(inner_rect.origin.x));
514
x_events.push(Event::region(inner_rect.origin.x + inner_rect.size.width));
515
516
y_events.push(Event::region(inner_rect.origin.y));
517
y_events.push(Event::region(inner_rect.origin.y + inner_rect.size.height));
518
}
519
520
// Get the minimal bounding rect in app units. We will
521
// work in fixed point in order to avoid float precision
522
// error while handling events.
523
let p0 = LayoutPointAu::new(
524
Au::from_f32_px(bounding_rect.origin.x),
525
Au::from_f32_px(bounding_rect.origin.y),
526
);
527
528
let p1 = LayoutPointAu::new(
529
Au::from_f32_px(bounding_rect.origin.x + bounding_rect.size.width),
530
Au::from_f32_px(bounding_rect.origin.y + bounding_rect.size.height),
531
);
532
533
// Sort the events in ascending order.
534
x_events.sort();
535
y_events.sort();
536
537
// Generate segments from the event lists, by sweeping the y-axis
538
// and then the x-axis for each event. This can generate a significant
539
// number of segments, but most importantly, it ensures that there are
540
// no t-junctions in the generated segments. It's probably possible
541
// to come up with more efficient segmentation algorithms, at least
542
// for simple / common cases.
543
544
// Each coordinate is clamped to the bounds of the minimal
545
// bounding rect. This ensures that we don't generate segments
546
// outside that bounding rect, but does allow correctly handling
547
// clips where the clip region starts outside the minimal
548
// rect but still intersects with it.
549
550
let mut prev_y = clamp(p0.y, y_events[0].value, p1.y);
551
let mut region_y = 0;
552
let mut segments : SmallVec<[_; 4]> = SmallVec::new();
553
let mut x_count = 0;
554
let mut y_count = 0;
555
556
for ey in &y_events {
557
let cur_y = clamp(p0.y, ey.value, p1.y);
558
559
if cur_y != prev_y {
560
let mut prev_x = clamp(p0.x, x_events[0].value, p1.x);
561
let mut region_x = 0;
562
563
for ex in &x_events {
564
let cur_x = clamp(p0.x, ex.value, p1.x);
565
566
if cur_x != prev_x {
567
segments.push(emit_segment_if_needed(
568
prev_x,
569
prev_y,
570
cur_x,
571
cur_y,
572
region_x,
573
region_y,
574
&self.items,
575
));
576
577
prev_x = cur_x;
578
if y_count == 0 {
579
x_count += 1;
580
}
581
}
582
583
ex.update(
584
ItemFlags::X_ACTIVE,
585
&mut self.items,
586
&mut region_x,
587
);
588
}
589
590
prev_y = cur_y;
591
y_count += 1;
592
}
593
594
ey.update(
595
ItemFlags::Y_ACTIVE,
596
&mut self.items,
597
&mut region_y,
598
);
599
}
600
601
// Run user supplied closure for each valid segment.
602
debug_assert_eq!(segments.len(), x_count * y_count);
603
for y in 0 .. y_count {
604
for x in 0 .. x_count {
605
let mut edge_flags = EdgeAaSegmentMask::empty();
606
607
if x == 0 || segments[y * x_count + x - 1].is_none() {
608
edge_flags |= EdgeAaSegmentMask::LEFT;
609
}
610
if x == x_count-1 || segments[y * x_count + x + 1].is_none() {
611
edge_flags |= EdgeAaSegmentMask::RIGHT;
612
}
613
if y == 0 || segments[(y-1) * x_count + x].is_none() {
614
edge_flags |= EdgeAaSegmentMask::TOP;
615
}
616
if y == y_count-1 || segments[(y+1) * x_count + x].is_none() {
617
edge_flags |= EdgeAaSegmentMask::BOTTOM;
618
}
619
620
if let Some(ref mut segment) = segments[y * x_count + x] {
621
segment.edge_flags = edge_flags;
622
f(segment);
623
}
624
}
625
}
626
}
627
}
628
629
fn clamp(low: Au, value: Au, high: Au) -> Au {
630
value.max(low).min(high)
631
}
632
633
fn emit_segment_if_needed(
634
x0: Au,
635
y0: Au,
636
x1: Au,
637
y1: Au,
638
region_x: usize,
639
region_y: usize,
640
items: &[Item],
641
) -> Option<Segment> {
642
debug_assert!(x1 > x0);
643
debug_assert!(y1 > y0);
644
645
// TODO(gw): Don't scan the whole list of items for
646
// each segment rect. Store active list
647
// in a hash set or similar if this ever
648
// shows up in a profile.
649
let mut has_clip_mask = false;
650
651
for item in items {
652
if item.flags.contains(ItemFlags::X_ACTIVE | ItemFlags::Y_ACTIVE) {
653
has_clip_mask |= item.flags.contains(ItemFlags::HAS_MASK);
654
655
if item.mode == Some(ClipMode::ClipOut) && !item.flags.contains(ItemFlags::HAS_MASK) {
656
return None;
657
}
658
}
659
}
660
661
let segment_rect = LayoutRect::new(
662
LayoutPoint::new(
663
x0.to_f32_px(),
664
y0.to_f32_px(),
665
),
666
LayoutSize::new(
667
(x1 - x0).to_f32_px(),
668
(y1 - y0).to_f32_px(),
669
),
670
);
671
672
Some(Segment {
673
rect: segment_rect,
674
has_mask: has_clip_mask,
675
edge_flags: EdgeAaSegmentMask::empty(),
676
region_x,
677
region_y,
678
})
679
}
680
681
#[cfg(test)]
682
mod test {
683
use api::{BorderRadius, ClipMode};
684
use api::units::{LayoutPoint, LayoutRect, LayoutSize};
685
use crate::prim_store::EdgeAaSegmentMask;
686
use super::{Segment, SegmentBuilder};
687
use std::cmp;
688
689
fn rect(x0: f32, y0: f32, x1: f32, y1: f32) -> LayoutRect {
690
LayoutRect::new(
691
LayoutPoint::new(x0, y0),
692
LayoutSize::new(x1-x0, y1-y0),
693
)
694
}
695
696
fn seg(
697
x0: f32,
698
y0: f32,
699
x1: f32,
700
y1: f32,
701
has_mask: bool,
702
edge_flags: Option<EdgeAaSegmentMask>,
703
) -> Segment {
704
seg_region(x0, y0, x1, y1, 0, 0, has_mask, edge_flags)
705
}
706
707
fn seg_region(
708
x0: f32,
709
y0: f32,
710
x1: f32,
711
y1: f32,
712
region_x: usize,
713
region_y: usize,
714
has_mask: bool,
715
edge_flags: Option<EdgeAaSegmentMask>,
716
) -> Segment {
717
Segment {
718
rect: LayoutRect::new(
719
LayoutPoint::new(x0, y0),
720
LayoutSize::new(x1-x0, y1-y0),
721
),
722
has_mask,
723
edge_flags: edge_flags.unwrap_or(EdgeAaSegmentMask::empty()),
724
region_x,
725
region_y,
726
}
727
}
728
729
fn segment_sorter(s0: &Segment, s1: &Segment) -> cmp::Ordering {
730
let r0 = &s0.rect;
731
let r1 = &s1.rect;
732
733
(
734
(r0.origin.x, r0.origin.y, r0.size.width, r0.size.height)
735
).partial_cmp(&
736
(r1.origin.x, r1.origin.y, r1.size.width, r1.size.height)
737
).unwrap()
738
}
739
740
fn seg_test(
741
local_rect: LayoutRect,
742
inner_rect: Option<LayoutRect>,
743
local_clip_rect: LayoutRect,
744
clips: &[(LayoutRect, Option<BorderRadius>, ClipMode)],
745
expected_segments: &mut [Segment]
746
) {
747
let mut sb = SegmentBuilder::new();
748
sb.initialize(
749
local_rect,
750
inner_rect,
751
local_clip_rect,
752
);
753
sb.push_clip_rect(local_rect, None, ClipMode::Clip);
754
sb.push_clip_rect(local_clip_rect, None, ClipMode::Clip);
755
let mut segments = Vec::new();
756
for &(rect, radius, mode) in clips {
757
sb.push_clip_rect(rect, radius, mode);
758
}
759
sb.build(|segment| {
760
segments.push(Segment {
761
..*segment
762
});
763
});
764
segments.sort_by(segment_sorter);
765
expected_segments.sort_by(segment_sorter);
766
assert_eq!(
767
segments.len(),
768
expected_segments.len(),
769
"segments\n{:?}\nexpected\n{:?}\n",
770
segments,
771
expected_segments
772
);
773
for (segment, expected) in segments.iter().zip(expected_segments.iter()) {
774
assert_eq!(segment, expected);
775
}
776
}
777
778
#[test]
779
fn segment_empty() {
780
seg_test(
781
rect(0.0, 0.0, 0.0, 0.0),
782
None,
783
rect(0.0, 0.0, 0.0, 0.0),
784
&[],
785
&mut [],
786
);
787
}
788
789
#[test]
790
fn segment_single() {
791
seg_test(
792
rect(10.0, 20.0, 30.0, 40.0),
793
None,
794
rect(10.0, 20.0, 30.0, 40.0),
795
&[],
796
&mut [
797
seg(10.0, 20.0, 30.0, 40.0, false,
798
Some(EdgeAaSegmentMask::LEFT |
799
EdgeAaSegmentMask::TOP |
800
EdgeAaSegmentMask::RIGHT |
801
EdgeAaSegmentMask::BOTTOM
802
)
803
),
804
],
805
);
806
}
807
808
#[test]
809
fn segment_single_clip() {
810
seg_test(
811
rect(10.0, 20.0, 30.0, 40.0),
812
None,
813
rect(10.0, 20.0, 25.0, 35.0),
814
&[],
815
&mut [
816
seg(10.0, 20.0, 25.0, 35.0, false,
817
Some(EdgeAaSegmentMask::LEFT |
818
EdgeAaSegmentMask::TOP |
819
EdgeAaSegmentMask::RIGHT |
820
EdgeAaSegmentMask::BOTTOM
821
)
822
),
823
],
824
);
825
}
826
827
#[test]
828
fn segment_inner_clip() {
829
seg_test(
830
rect(10.0, 20.0, 30.0, 40.0),
831
None,
832
rect(15.0, 25.0, 25.0, 35.0),
833
&[],
834
&mut [
835
seg(15.0, 25.0, 25.0, 35.0, false,
836
Some(EdgeAaSegmentMask::LEFT |
837
EdgeAaSegmentMask::TOP |
838
EdgeAaSegmentMask::RIGHT |
839
EdgeAaSegmentMask::BOTTOM
840
)
841
),
842
],
843
);
844
}
845
846
#[test]
847
fn segment_outer_clip() {
848
seg_test(
849
rect(15.0, 25.0, 25.0, 35.0),
850
None,
851
rect(10.0, 20.0, 30.0, 40.0),
852
&[],
853
&mut [
854
seg(15.0, 25.0, 25.0, 35.0, false,
855
Some(EdgeAaSegmentMask::LEFT |
856
EdgeAaSegmentMask::TOP |
857
EdgeAaSegmentMask::RIGHT |
858
EdgeAaSegmentMask::BOTTOM
859
)
860
),
861
],
862
);
863
}
864
865
#[test]
866
fn segment_clip_int() {
867
seg_test(
868
rect(10.0, 20.0, 30.0, 40.0),
869
None,
870
rect(20.0, 10.0, 40.0, 30.0),
871
&[],
872
&mut [
873
seg(20.0, 20.0, 30.0, 30.0, false,
874
Some(EdgeAaSegmentMask::LEFT |
875
EdgeAaSegmentMask::TOP |
876
EdgeAaSegmentMask::RIGHT |
877
EdgeAaSegmentMask::BOTTOM
878
)
879
),
880
],
881
);
882
}
883
884
#[test]
885
fn segment_clip_disjoint() {
886
seg_test(
887
rect(10.0, 20.0, 30.0, 40.0),
888
None,
889
rect(30.0, 20.0, 50.0, 40.0),
890
&[],
891
&mut [],
892
);
893
}
894
895
#[test]
896
fn segment_clips() {
897
seg_test(
898
rect(0.0, 0.0, 100.0, 100.0),
899
None,
900
rect(-1000.0, -1000.0, 1000.0, 1000.0),
901
&[
902
(rect(20.0, 20.0, 40.0, 40.0), None, ClipMode::Clip),
903
(rect(40.0, 20.0, 60.0, 40.0), None, ClipMode::Clip),
904
],
905
&mut [
906
],
907
);
908
}
909
910
#[test]
911
fn segment_rounded_clip() {
912
seg_test(
913
rect(0.0, 0.0, 100.0, 100.0),
914
None,
915
rect(-1000.0, -1000.0, 1000.0, 1000.0),
916
&[
917
(rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip),
918
],
919
&mut [
920
// corners
921
seg(20.0, 20.0, 30.0, 30.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)),
922
seg(20.0, 50.0, 30.0, 60.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)),
923
seg(50.0, 20.0, 60.0, 30.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::TOP)),
924
seg(50.0, 50.0, 60.0, 60.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)),
925
926
// inner
927
seg(30.0, 30.0, 50.0, 50.0, false, None),
928
929
// edges
930
seg(30.0, 20.0, 50.0, 30.0, false, Some(EdgeAaSegmentMask::TOP)),
931
seg(30.0, 50.0, 50.0, 60.0, false, Some(EdgeAaSegmentMask::BOTTOM)),
932
seg(20.0, 30.0, 30.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT)),
933
seg(50.0, 30.0, 60.0, 50.0, false, Some(EdgeAaSegmentMask::RIGHT)),
934
],
935
);
936
}
937
938
#[test]
939
fn segment_clip_out() {
940
seg_test(
941
rect(0.0, 0.0, 100.0, 100.0),
942
None,
943
rect(-1000.0, -1000.0, 2000.0, 2000.0),
944
&[
945
(rect(20.0, 20.0, 60.0, 60.0), None, ClipMode::ClipOut),
946
],
947
&mut [
948
seg(0.0, 0.0, 20.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)),
949
seg(20.0, 0.0, 60.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM)),
950
seg(60.0, 0.0, 100.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)),
951
952
seg(0.0, 20.0, 20.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT)),
953
seg(60.0, 20.0, 100.0, 60.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT)),
954
955
seg(0.0, 60.0, 20.0, 100.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)),
956
seg(20.0, 60.0, 60.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)),
957
seg(60.0, 60.0, 100.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT)),
958
],
959
);
960
}
961
962
#[test]
963
fn segment_rounded_clip_out() {
964
seg_test(
965
rect(0.0, 0.0, 100.0, 100.0),
966
None,
967
rect(-1000.0, -1000.0, 2000.0, 2000.0),
968
&[
969
(rect(20.0, 20.0, 60.0, 60.0), Some(BorderRadius::uniform(10.0)), ClipMode::ClipOut),
970
],
971
&mut [
972
// top row
973
seg(0.0, 0.0, 20.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)),
974
seg(20.0, 0.0, 30.0, 20.0, false, Some(EdgeAaSegmentMask::TOP)),
975
seg(30.0, 0.0, 50.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM)),
976
seg(50.0, 0.0, 60.0, 20.0, false, Some(EdgeAaSegmentMask::TOP)),
977
seg(60.0, 0.0, 100.0, 20.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)),
978
979
// left
980
seg(0.0, 20.0, 20.0, 30.0, false, Some(EdgeAaSegmentMask::LEFT)),
981
seg(0.0, 30.0, 20.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT)),
982
seg(0.0, 50.0, 20.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT)),
983
984
// right
985
seg(60.0, 20.0, 100.0, 30.0, false, Some(EdgeAaSegmentMask::RIGHT)),
986
seg(60.0, 30.0, 100.0, 50.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT)),
987
seg(60.0, 50.0, 100.0, 60.0, false, Some(EdgeAaSegmentMask::RIGHT)),
988
989
// bottom row
990
seg(0.0, 60.0, 20.0, 100.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)),
991
seg(20.0, 60.0, 30.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)),
992
seg(30.0, 60.0, 50.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)),
993
seg(50.0, 60.0, 60.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)),
994
seg(60.0, 60.0, 100.0, 100.0, false, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)),
995
996
// inner corners
997
seg(20.0, 20.0, 30.0, 30.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)),
998
seg(20.0, 50.0, 30.0, 60.0, true, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)),
999
seg(50.0, 20.0, 60.0, 30.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)),
1000
seg(50.0, 50.0, 60.0, 60.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)),
1001
],
1002
);
1003
}
1004
1005
#[test]
1006
fn segment_clip_in_clip_out() {
1007
seg_test(
1008
rect(0.0, 0.0, 100.0, 100.0),
1009
None,
1010
rect(-1000.0, -1000.0, 2000.0, 2000.0),
1011
&[
1012
(rect(20.0, 20.0, 60.0, 60.0), None, ClipMode::Clip),
1013
(rect(50.0, 50.0, 80.0, 80.0), None, ClipMode::ClipOut),
1014
],
1015
&mut [
1016
seg(20.0, 20.0, 50.0, 50.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)),
1017
seg(50.0, 20.0, 60.0, 50.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)),
1018
seg(20.0, 50.0, 50.0, 60.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT)),
1019
],
1020
);
1021
}
1022
1023
#[test]
1024
fn segment_rounded_clip_overlap() {
1025
seg_test(
1026
rect(0.0, 0.0, 100.0, 100.0),
1027
None,
1028
rect(0.0, 0.0, 100.0, 100.0),
1029
&[
1030
(rect(0.0, 0.0, 10.0, 10.0), None, ClipMode::ClipOut),
1031
(rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip),
1032
],
1033
&mut [
1034
// corners
1035
seg(0.0, 90.0, 10.0, 100.0, true, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)),
1036
seg(90.0, 0.0, 100.0, 10.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::TOP)),
1037
seg(90.0, 90.0, 100.0, 100.0, true, Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)),
1038
1039
// inner
1040
seg(10.0, 10.0, 90.0, 90.0, false, None),
1041
1042
// edges
1043
seg(10.0, 0.0, 90.0, 10.0, false, Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT)),
1044
seg(10.0, 90.0, 90.0, 100.0, false, Some(EdgeAaSegmentMask::BOTTOM)),
1045
seg(0.0, 10.0, 10.0, 90.0, false, Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)),
1046
seg(90.0, 10.0, 100.0, 90.0, false, Some(EdgeAaSegmentMask::RIGHT)),
1047
],
1048
);
1049
}
1050
1051
#[test]
1052
fn segment_rounded_clip_overlap_reverse() {
1053
seg_test(
1054
rect(0.0, 0.0, 100.0, 100.0),
1055
None,
1056
rect(0.0, 0.0, 100.0, 100.0),
1057
&[
1058
(rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::Clip),
1059
(rect(0.0, 0.0, 100.0, 100.0), Some(BorderRadius::uniform(10.0)), ClipMode::Clip),
1060
],
1061
&mut [
1062
seg(10.0, 10.0, 90.0, 90.0, false,
1063
Some(EdgeAaSegmentMask::LEFT |
1064
EdgeAaSegmentMask::TOP |
1065
EdgeAaSegmentMask::RIGHT |
1066
EdgeAaSegmentMask::BOTTOM
1067
)
1068
),
1069
],
1070
);
1071
}
1072
1073
#[test]
1074
fn segment_clip_in_clip_out_overlap() {
1075
seg_test(
1076
rect(0.0, 0.0, 100.0, 100.0),
1077
None,
1078
rect(0.0, 0.0, 100.0, 100.0),
1079
&[
1080
(rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::Clip),
1081
(rect(10.0, 10.0, 90.0, 90.0), None, ClipMode::ClipOut),
1082
],
1083
&mut [
1084
],
1085
);
1086
}
1087
1088
#[test]
1089
fn segment_event_order() {
1090
seg_test(
1091
rect(0.0, 0.0, 100.0, 100.0),
1092
None,
1093
rect(0.0, 0.0, 100.0, 100.0),
1094
&[
1095
(rect(0.0, 0.0, 100.0, 90.0), None, ClipMode::ClipOut),
1096
],
1097
&mut [
1098
seg(0.0, 90.0, 100.0, 100.0, false, Some(
1099
EdgeAaSegmentMask::LEFT |
1100
EdgeAaSegmentMask::RIGHT |
1101
EdgeAaSegmentMask::BOTTOM |
1102
EdgeAaSegmentMask::TOP
1103
)),
1104
],
1105
);
1106
}
1107
1108
#[test]
1109
fn segment_region_simple() {
1110
seg_test(
1111
rect(0.0, 0.0, 100.0, 100.0),
1112
Some(rect(20.0, 40.0, 60.0, 80.0)),
1113
rect(0.0, 0.0, 100.0, 100.0),
1114
&[
1115
],
1116
&mut [
1117
seg_region(
1118
0.0, 0.0,
1119
20.0, 40.0,
1120
0, 0,
1121
false,
1122
Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)
1123
),
1124
1125
seg_region(
1126
20.0, 0.0,
1127
60.0, 40.0,
1128
1, 0,
1129
false,
1130
Some(EdgeAaSegmentMask::TOP)
1131
),
1132
1133
seg_region(
1134
60.0, 0.0,
1135
100.0, 40.0,
1136
2, 0,
1137
false,
1138
Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT)
1139
),
1140
1141
seg_region(
1142
0.0, 40.0,
1143
20.0, 80.0,
1144
0, 1,
1145
false,
1146
Some(EdgeAaSegmentMask::LEFT)
1147
),
1148
1149
seg_region(
1150
20.0, 40.0,
1151
60.0, 80.0,
1152
1, 1,
1153
false,
1154
None,
1155
),
1156
1157
seg_region(
1158
60.0, 40.0,
1159
100.0, 80.0,
1160
2, 1,
1161
false,
1162
Some(EdgeAaSegmentMask::RIGHT)
1163
),
1164
1165
seg_region(
1166
0.0, 80.0,
1167
20.0, 100.0,
1168
0, 2,
1169
false,
1170
Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM)
1171
),
1172
1173
seg_region(
1174
20.0, 80.0,
1175
60.0, 100.0,
1176
1, 2,
1177
false,
1178
Some(EdgeAaSegmentMask::BOTTOM),
1179
),
1180
1181
seg_region(
1182
60.0, 80.0,
1183
100.0, 100.0,
1184
2, 2,
1185
false,
1186
Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM)
1187
),
1188
1189
],
1190
);
1191
}
1192
1193
#[test]
1194
fn segment_region_clip() {
1195
seg_test(
1196
rect(0.0, 0.0, 100.0, 100.0),
1197
Some(rect(20.0, 40.0, 60.0, 80.0)),
1198
rect(0.0, 0.0, 100.0, 100.0),
1199
&[
1200
(rect(0.0, 0.0, 100.0, 90.0), None, ClipMode::ClipOut),
1201
],
1202
&mut [
1203
seg_region(
1204
0.0, 90.0,
1205
20.0, 100.0,
1206
0, 2,
1207
false,
1208
Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)
1209
),
1210
1211
seg_region(
1212
20.0, 90.0,
1213
60.0, 100.0,
1214
1, 2,
1215
false,
1216
Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP),
1217
),
1218
1219
seg_region(
1220
60.0, 90.0,
1221
100.0, 100.0,
1222
2, 2,
1223
false,
1224
Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP)
1225
),
1226
1227
],
1228
);
1229
}
1230
1231
#[test]
1232
fn segment_region_clip2() {
1233
seg_test(
1234
rect(0.0, 0.0, 100.0, 100.0),
1235
Some(rect(20.0, 20.0, 80.0, 80.0)),
1236
rect(0.0, 0.0, 100.0, 100.0),
1237
&[
1238
(rect(20.0, 20.0, 100.0, 100.0), None, ClipMode::ClipOut),
1239
],
1240
&mut [
1241
seg_region(
1242
0.0, 0.0,
1243
20.0, 20.0,
1244
0, 0,
1245
false,
1246
Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::TOP)
1247
),
1248
1249
seg_region(
1250
20.0, 0.0,
1251
80.0, 20.0,
1252
1, 0,
1253
false,
1254
Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM),
1255
),
1256
1257
seg_region(
1258
80.0, 0.0,
1259
100.0, 20.0,
1260
2, 0,
1261
false,
1262
Some(EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM)
1263
),
1264
1265
seg_region(
1266
0.0, 20.0,
1267
20.0, 80.0,
1268
0, 1,
1269
false,
1270
Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT)
1271
),
1272
1273
seg_region(
1274
0.0, 80.0,
1275
20.0, 100.0,
1276
0, 2,
1277
false,
1278
Some(EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT)
1279
),
1280
],
1281
);
1282
}
1283
1284
#[test]
1285
fn segment_region_clip3() {
1286
seg_test(
1287
rect(0.0, 0.0, 100.0, 100.0),
1288
Some(rect(20.0, 20.0, 80.0, 80.0)),
1289
rect(0.0, 0.0, 100.0, 100.0),
1290
&[
1291
(rect(10.0, 10.0, 30.0, 30.0), None, ClipMode::Clip),
1292
],
1293
&mut [
1294
seg_region(
1295
10.0, 10.0,
1296
20.0, 20.0,
1297
0, 0,
1298
false,
1299
Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT),
1300
),
1301
1302
seg_region(
1303
20.0, 10.0,
1304
30.0, 20.0,
1305
1, 0,
1306
false,
1307
Some(EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT),
1308
),
1309
1310
seg_region(
1311
10.0, 20.0,
1312
20.0, 30.0,
1313
0, 1,
1314
false,
1315
Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT),
1316
),
1317
1318
seg_region(
1319
20.0, 20.0,
1320
30.0, 30.0,
1321
1, 1,
1322
false,
1323
Some(EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT),
1324
),
1325
],
1326
);
1327
}
1328
}