quad.rs (61047B)
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 use api::{units::*, ClipMode, ColorF}; 6 use euclid::point2; 7 8 use crate::ItemUid; 9 use crate::batch::{BatchKey, BatchKind, BatchTextures}; 10 use crate::clip::{ClipChainInstance, ClipIntern, ClipItemKind, ClipNodeRange, ClipSpaceConversion, ClipStore}; 11 use crate::command_buffer::{CommandBufferIndex, PrimitiveCommand, QuadFlags}; 12 use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState}; 13 use crate::gpu_types::{PrimitiveInstanceData, QuadHeader, QuadInstance, QuadPrimitive, QuadSegment, TransformPaletteId, ZBufferId}; 14 use crate::intern::DataStore; 15 use crate::internal_types::TextureSource; 16 use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState, PatternKind, PatternShaderInput}; 17 use crate::prim_store::{PrimitiveInstanceIndex, PrimitiveScratchBuffer}; 18 use crate::render_task::{MaskSubPass, RenderTask, RenderTaskAddress, RenderTaskKind, SubPass}; 19 use crate::render_task_cache::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskParent}; 20 use crate::render_task_graph::{RenderTaskGraph, RenderTaskGraphBuilder, RenderTaskId}; 21 use crate::renderer::{BlendMode, GpuBufferAddress, GpuBufferBuilder, GpuBufferBuilderF, GpuBufferDataI}; 22 use crate::resource_cache::ResourceCache; 23 use crate::segment::EdgeAaSegmentMask; 24 use crate::space::SpaceMapper; 25 use crate::spatial_tree::{CoordinateSpaceMapping, SpatialNodeIndex, SpatialTree}; 26 use crate::surface::SurfaceBuilder; 27 use crate::util::{extract_inner_rect_k, MaxRect, ScaleOffset}; 28 use crate::visibility::compute_conservative_visible_rect; 29 30 /// This type reflects the unfortunate situation with quad coordinates where we 31 /// sometimes use layout and sometimes device coordinates. 32 pub type LayoutOrDeviceRect = api::euclid::default::Box2D<f32>; 33 34 const MIN_AA_SEGMENTS_SIZE: f32 = 4.0; 35 const MIN_QUAD_SPLIT_SIZE: f32 = 256.0; 36 const MAX_TILES_PER_QUAD: usize = 4; 37 38 39 #[derive(Clone, Debug, Hash, PartialEq, Eq)] 40 #[cfg_attr(feature = "capture", derive(Serialize))] 41 #[cfg_attr(feature = "replay", derive(Deserialize))] 42 pub struct QuadCacheKey { 43 pub prim: u64, 44 pub clips: [u64; 3], 45 pub spatial_node: u64, 46 } 47 48 /// Describes how clipping affects the rendering of a quad primitive. 49 /// 50 /// As a general rule, parts of the quad that require masking are prerendered in an 51 /// intermediate target and the mask is applied using multiplicative blending to 52 /// the intermediate result before compositing it into the destination target. 53 /// 54 /// Each segment can opt in or out of masking independently. 55 #[derive(Debug, Copy, Clone)] 56 pub enum QuadRenderStrategy { 57 /// The quad is not affected by any mask and is drawn directly in the destination 58 /// target. 59 Direct, 60 /// The quad is drawn entirely in an intermediate target and a mask is applied 61 /// before compositing in the destination target. 62 Indirect, 63 /// A rounded rectangle clip is applied to the quad primitive via a nine-patch. 64 /// The segments of the nine-patch that require a mask are rendered and masked in 65 /// an intermediate target, while other segments are drawn directly in the destination 66 /// target. 67 NinePatch { 68 radius: LayoutVector2D, 69 clip_rect: LayoutRect, 70 }, 71 /// Split the primitive into coarse tiles so that each tile independently 72 /// has the opportunity to be drawn directly in the destination target or 73 /// via an intermediate target if it is affected by a mask. 74 Tiled { 75 x_tiles: u16, 76 y_tiles: u16, 77 } 78 } 79 80 pub fn prepare_quad( 81 pattern_builder: &dyn PatternBuilder, 82 local_rect: &LayoutRect, 83 prim_instance_index: PrimitiveInstanceIndex, 84 cache_key: &Option<QuadCacheKey>, 85 prim_spatial_node_index: SpatialNodeIndex, 86 clip_chain: &ClipChainInstance, 87 device_pixel_scale: DevicePixelScale, 88 89 frame_context: &FrameBuildingContext, 90 pic_context: &PictureContext, 91 targets: &[CommandBufferIndex], 92 interned_clips: &DataStore<ClipIntern>, 93 94 frame_state: &mut FrameBuildingState, 95 pic_state: &mut PictureState, 96 scratch: &mut PrimitiveScratchBuffer, 97 ) { 98 let pattern_ctx = PatternBuilderContext { 99 scene_properties: frame_context.scene_properties, 100 spatial_tree: frame_context.spatial_tree, 101 fb_config: frame_context.fb_config, 102 }; 103 104 let shared_pattern = if pattern_builder.use_shared_pattern() { 105 Some(pattern_builder.build( 106 None, 107 &pattern_ctx, 108 &mut PatternBuilderState { 109 frame_gpu_data: frame_state.frame_gpu_data, 110 rg_builder: frame_state.rg_builder, 111 clip_store: frame_state.clip_store, 112 }, 113 )) 114 } else { 115 None 116 }; 117 118 // TODO: It would be worth hoisting this out of prepare_quad and 119 // prepare_repeatable_quad. 120 let map_prim_to_raster = pattern_ctx.spatial_tree.get_relative_transform( 121 prim_spatial_node_index, 122 pic_context.raster_spatial_node_index, 123 ); 124 125 let can_use_nine_patch = map_prim_to_raster.is_2d_scale_translation() 126 && pattern_builder.can_use_nine_patch(); 127 128 let strategy = match cache_key { 129 Some(_) => QuadRenderStrategy::Indirect, 130 None => get_prim_render_strategy( 131 prim_spatial_node_index, 132 clip_chain, 133 frame_state.clip_store, 134 interned_clips, 135 can_use_nine_patch, 136 pattern_ctx.spatial_tree, 137 ), 138 }; 139 140 prepare_quad_impl( 141 strategy, 142 pattern_builder, 143 shared_pattern.as_ref(), 144 local_rect, 145 prim_instance_index, 146 cache_key, 147 prim_spatial_node_index, 148 clip_chain, 149 device_pixel_scale, 150 151 &map_prim_to_raster, 152 &pattern_ctx, 153 pic_context, 154 targets, 155 interned_clips, 156 157 frame_state, 158 pic_state, 159 scratch, 160 ) 161 } 162 163 pub fn prepare_repeatable_quad( 164 pattern_builder: &dyn PatternBuilder, 165 local_rect: &LayoutRect, 166 stretch_size: LayoutSize, 167 tile_spacing: LayoutSize, 168 prim_instance_index: PrimitiveInstanceIndex, 169 cache_key: &Option<QuadCacheKey>, 170 prim_spatial_node_index: SpatialNodeIndex, 171 clip_chain: &ClipChainInstance, 172 device_pixel_scale: DevicePixelScale, 173 174 frame_context: &FrameBuildingContext, 175 pic_context: &PictureContext, 176 targets: &[CommandBufferIndex], 177 interned_clips: &DataStore<ClipIntern>, 178 179 frame_state: &mut FrameBuildingState, 180 pic_state: &mut PictureState, 181 scratch: &mut PrimitiveScratchBuffer, 182 ) { 183 let pattern_ctx = PatternBuilderContext { 184 scene_properties: frame_context.scene_properties, 185 spatial_tree: frame_context.spatial_tree, 186 fb_config: frame_context.fb_config, 187 }; 188 189 let shared_pattern = if pattern_builder.use_shared_pattern() { 190 Some(pattern_builder.build( 191 None, 192 &pattern_ctx, 193 &mut PatternBuilderState { 194 frame_gpu_data: frame_state.frame_gpu_data, 195 rg_builder: frame_state.rg_builder, 196 clip_store: frame_state.clip_store, 197 }, 198 )) 199 } else { 200 None 201 }; 202 203 let map_prim_to_raster = pattern_ctx.spatial_tree.get_relative_transform( 204 prim_spatial_node_index, 205 pic_context.raster_spatial_node_index, 206 ); 207 208 let can_use_nine_patch = map_prim_to_raster.is_2d_scale_translation() 209 && pattern_builder.can_use_nine_patch(); 210 211 // This could move back into preapre_quad_impl if it took the tile's 212 // coverage rect into account rather than the whole primitive's, but 213 // for now it does the latter so we might as well not do the work 214 // multiple times. 215 let strategy = match cache_key { 216 Some(_) => QuadRenderStrategy::Indirect, 217 None => get_prim_render_strategy( 218 prim_spatial_node_index, 219 clip_chain, 220 frame_state.clip_store, 221 interned_clips, 222 can_use_nine_patch, 223 pattern_ctx.spatial_tree, 224 ), 225 }; 226 227 let needs_repetition = stretch_size.width < local_rect.width() 228 || stretch_size.height < local_rect.height(); 229 230 if !needs_repetition { 231 // Most common path. 232 prepare_quad_impl( 233 strategy, 234 pattern_builder, 235 shared_pattern.as_ref(), 236 local_rect, 237 prim_instance_index, 238 &cache_key, 239 prim_spatial_node_index, 240 clip_chain, 241 device_pixel_scale, 242 &map_prim_to_raster, 243 &pattern_ctx, 244 pic_context, 245 targets, 246 interned_clips, 247 frame_state, 248 pic_state, 249 scratch, 250 ); 251 252 return; 253 } 254 255 // TODO: In some cases it would be a lot more efficient to bake the repeated 256 // pattern into a texture and use a repeating image shader instead of duplicating 257 // the primitive, especially with a high number of repetitions. 258 259 let visible_rect = compute_conservative_visible_rect( 260 clip_chain, 261 frame_state.current_dirty_region().combined, 262 frame_state.current_dirty_region().visibility_spatial_node, 263 prim_spatial_node_index, 264 frame_context.spatial_tree, 265 ); 266 267 let stride = stretch_size + tile_spacing; 268 let repetitions = crate::image_tiling::repetitions(&local_rect, &visible_rect, stride); 269 for tile in repetitions { 270 let tile_rect = LayoutRect::from_origin_and_size(tile.origin, stretch_size); 271 prepare_quad_impl( 272 strategy, 273 pattern_builder, 274 shared_pattern.as_ref(), 275 &tile_rect, 276 prim_instance_index, 277 &cache_key, 278 prim_spatial_node_index, 279 clip_chain, 280 device_pixel_scale, 281 &map_prim_to_raster, 282 &pattern_ctx, 283 pic_context, 284 targets, 285 interned_clips, 286 frame_state, 287 pic_state, 288 scratch, 289 ); 290 } 291 } 292 293 fn prepare_quad_impl( 294 strategy: QuadRenderStrategy, 295 pattern_builder: &dyn PatternBuilder, 296 shared_pattern: Option<&Pattern>, 297 local_rect: &LayoutRect, 298 prim_instance_index: PrimitiveInstanceIndex, 299 cache_key: &Option<QuadCacheKey>, 300 prim_spatial_node_index: SpatialNodeIndex, 301 clip_chain: &ClipChainInstance, 302 device_pixel_scale: DevicePixelScale, 303 304 map_prim_to_raster: &CoordinateSpaceMapping<LayoutPixel, LayoutPixel>, 305 ctx: &PatternBuilderContext, 306 pic_context: &PictureContext, 307 targets: &[CommandBufferIndex], 308 interned_clips: &DataStore<ClipIntern>, 309 310 frame_state: &mut FrameBuildingState, 311 pic_state: &mut PictureState, 312 scratch: &mut PrimitiveScratchBuffer, 313 ) { 314 let mut state = PatternBuilderState { 315 frame_gpu_data: frame_state.frame_gpu_data, 316 rg_builder: frame_state.rg_builder, 317 clip_store: frame_state.clip_store, 318 }; 319 320 let prim_is_2d_scale_translation = map_prim_to_raster.is_2d_scale_translation(); 321 let prim_is_2d_axis_aligned = map_prim_to_raster.is_2d_axis_aligned(); 322 323 let mut quad_flags = QuadFlags::empty(); 324 325 // Only use AA edge instances if the primitive is large enough to require it 326 let prim_size = local_rect.size(); 327 if prim_size.width > MIN_AA_SEGMENTS_SIZE && prim_size.height > MIN_AA_SEGMENTS_SIZE { 328 quad_flags |= QuadFlags::USE_AA_SEGMENTS; 329 } 330 331 let needs_scissor = !prim_is_2d_scale_translation; 332 if !needs_scissor { 333 quad_flags |= QuadFlags::APPLY_RENDER_TASK_CLIP; 334 } 335 336 // TODO(gw): For now, we don't select per-edge AA at all if the primitive 337 // has a 2d transform, which matches existing behavior. However, 338 // as a follow up, we can now easily check if we have a 2d-aligned 339 // primitive on a subpixel boundary, and enable AA along those edge(s). 340 let aa_flags = if prim_is_2d_axis_aligned { 341 EdgeAaSegmentMask::empty() 342 } else { 343 EdgeAaSegmentMask::all() 344 }; 345 346 let transform_id = frame_state.transforms.get_id( 347 prim_spatial_node_index, 348 pic_context.raster_spatial_node_index, 349 ctx.spatial_tree, 350 ); 351 352 if let QuadRenderStrategy::Direct = strategy { 353 let pattern = shared_pattern.cloned().unwrap_or_else(|| { 354 pattern_builder.build( 355 None, 356 &ctx, 357 &mut state, 358 ) 359 }); 360 361 if pattern.is_opaque { 362 quad_flags |= QuadFlags::IS_OPAQUE; 363 } 364 365 let main_prim_address = write_prim_blocks( 366 &mut frame_state.frame_gpu_data.f32, 367 local_rect.to_untyped(), 368 clip_chain.local_clip_rect.to_untyped(), 369 pattern.base_color, 370 pattern.texture_input.task_id, 371 &[], 372 ScaleOffset::identity(), 373 ); 374 375 // Render the primitive as a single instance. Coordinates are provided to the 376 // shader in layout space. 377 frame_state.push_prim( 378 &PrimitiveCommand::quad( 379 pattern.kind, 380 pattern.shader_input, 381 pattern.texture_input.task_id, 382 prim_instance_index, 383 main_prim_address, 384 transform_id, 385 quad_flags, 386 aa_flags, 387 ), 388 prim_spatial_node_index, 389 targets, 390 ); 391 392 // If the pattern samples from a texture, add it as a dependency 393 // of the surface we're drawing directly on to. 394 if pattern.texture_input.task_id != RenderTaskId::INVALID { 395 frame_state 396 .surface_builder 397 .add_child_render_task(pattern.texture_input.task_id, frame_state.rg_builder); 398 } 399 400 return; 401 } 402 403 let surface = &mut frame_state.surfaces[pic_context.surface_index.0]; 404 405 let Some(clipped_surface_rect) = surface.get_surface_rect( 406 &clip_chain.pic_coverage_rect, ctx.spatial_tree 407 ) else { 408 return; 409 }; 410 411 match strategy { 412 QuadRenderStrategy::Direct => {} 413 QuadRenderStrategy::Indirect => { 414 let pattern = shared_pattern.cloned().unwrap_or_else(|| { 415 pattern_builder.build( 416 None, 417 &ctx, 418 &mut state, 419 ) 420 }); 421 422 if pattern.is_opaque { 423 quad_flags |= QuadFlags::IS_OPAQUE; 424 } 425 426 let main_prim_address = write_prim_blocks( 427 &mut frame_state.frame_gpu_data.f32, 428 local_rect.to_untyped(), 429 clip_chain.local_clip_rect.to_untyped(), 430 pattern.base_color, 431 pattern.texture_input.task_id, 432 &[], 433 ScaleOffset::identity(), 434 ); 435 436 let cache_key = cache_key.as_ref().map(|key| { 437 RenderTaskCacheKey { 438 size: clipped_surface_rect.size(), 439 kind: RenderTaskCacheKeyKind::Quad(key.clone()), 440 } 441 }); 442 443 // Render the primtive as a single instance in a render task, apply a mask 444 // and composite it in the current picture. 445 // The coordinates are provided to the shaders: 446 // - in layout space for the render task, 447 // - in device space for the instance that draw into the destination picture. 448 let task_id = add_render_task_with_mask( 449 &pattern, 450 clipped_surface_rect.size(), 451 clipped_surface_rect.min.to_f32(), 452 clip_chain.clips_range, 453 prim_spatial_node_index, 454 pic_context.raster_spatial_node_index, 455 main_prim_address, 456 transform_id, 457 aa_flags, 458 quad_flags, 459 device_pixel_scale, 460 needs_scissor, 461 cache_key.as_ref(), 462 frame_state.resource_cache, 463 frame_state.rg_builder, 464 &mut frame_state.frame_gpu_data.f32, 465 &mut frame_state.surface_builder, 466 ); 467 468 let rect = clipped_surface_rect.to_f32().to_untyped(); 469 add_composite_prim( 470 pattern_builder.get_base_color(&ctx), 471 prim_instance_index, 472 rect, 473 frame_state, 474 targets, 475 &[QuadSegment { rect, task_id }], 476 ); 477 } 478 QuadRenderStrategy::Tiled { x_tiles, y_tiles } => { 479 // Render the primtive as a grid of tiles decomposed in device space. 480 // Tiles that need it are drawn in a render task and then composited into the 481 // destination picture. 482 // The coordinates are provided to the shaders: 483 // - in layout space for the render task, 484 // - in device space for the instances that draw into the destination picture. 485 let clip_coverage_rect = surface 486 .map_to_device_rect(&clip_chain.pic_coverage_rect, ctx.spatial_tree); 487 let clipped_surface_rect = clipped_surface_rect.to_f32(); 488 489 surface.map_local_to_picture.set_target_spatial_node( 490 prim_spatial_node_index, 491 ctx.spatial_tree, 492 ); 493 494 let Some(pic_rect) = surface.map_local_to_picture.map(local_rect) else { return }; 495 496 let unclipped_surface_rect = surface.map_to_device_rect( 497 &pic_rect, ctx.spatial_tree 498 ).round_out(); 499 500 // Set up the tile classifier for the params of this quad 501 scratch.quad_tile_classifier.reset( 502 x_tiles as usize, 503 y_tiles as usize, 504 *local_rect, 505 ); 506 507 // Walk each clip, extract the local mask regions and add them to the tile classifier. 508 for i in 0 .. clip_chain.clips_range.count { 509 let clip_instance = state.clip_store.get_instance_from_range(&clip_chain.clips_range, i); 510 let clip_node = &interned_clips[clip_instance.handle]; 511 512 // Construct a prim <-> clip space converter 513 let conversion = ClipSpaceConversion::new( 514 prim_spatial_node_index, 515 clip_node.item.spatial_node_index, 516 pic_context.visibility_spatial_node_index, 517 ctx.spatial_tree, 518 ); 519 520 // For now, we only handle axis-aligned mappings 521 let transform = match conversion { 522 ClipSpaceConversion::Local => ScaleOffset::identity(), 523 ClipSpaceConversion::ScaleOffset(scale_offset) => scale_offset, 524 ClipSpaceConversion::Transform(..) => { 525 // If the clip transform is not axis-aligned, just assume the entire primitive 526 // local rect is affected by the clip, for now. It's no worse than what 527 // we were doing previously for all tiles. 528 scratch.quad_tile_classifier.add_mask_region(*local_rect); 529 continue; 530 } 531 }; 532 533 // Add regions to the classifier depending on the clip kind 534 match clip_node.item.kind { 535 ClipItemKind::Rectangle { mode, ref rect } => { 536 let rect = transform.map_rect(rect); 537 scratch.quad_tile_classifier.add_clip_rect(rect, mode); 538 } 539 ClipItemKind::RoundedRectangle { mode: ClipMode::Clip, ref rect, ref radius } => { 540 // For rounded-rects with Clip mode, we need a mask for each corner, 541 // and to add the clip rect itself (to cull tiles outside that rect) 542 543 // Map the local rect and radii 544 let rect = transform.map_rect(rect); 545 let r_tl = transform.map_size(&radius.top_left); 546 let r_tr = transform.map_size(&radius.top_right); 547 let r_br = transform.map_size(&radius.bottom_right); 548 let r_bl = transform.map_size(&radius.bottom_left); 549 550 // Construct the mask regions for each corner 551 let c_tl = LayoutRect::from_origin_and_size( 552 LayoutPoint::new(rect.min.x, rect.min.y), 553 r_tl, 554 ); 555 let c_tr = LayoutRect::from_origin_and_size( 556 LayoutPoint::new( 557 rect.max.x - r_tr.width, 558 rect.min.y, 559 ), 560 r_tr, 561 ); 562 let c_br = LayoutRect::from_origin_and_size( 563 LayoutPoint::new( 564 rect.max.x - r_br.width, 565 rect.max.y - r_br.height, 566 ), 567 r_br, 568 ); 569 let c_bl = LayoutRect::from_origin_and_size( 570 LayoutPoint::new( 571 rect.min.x, 572 rect.max.y - r_bl.height, 573 ), 574 r_bl, 575 ); 576 577 scratch.quad_tile_classifier.add_clip_rect(rect, ClipMode::Clip); 578 scratch.quad_tile_classifier.add_mask_region(c_tl); 579 scratch.quad_tile_classifier.add_mask_region(c_tr); 580 scratch.quad_tile_classifier.add_mask_region(c_br); 581 scratch.quad_tile_classifier.add_mask_region(c_bl); 582 } 583 ClipItemKind::RoundedRectangle { mode: ClipMode::ClipOut, ref rect, ref radius } => { 584 // Try to find an inner rect within the clip-out rounded rect that we can 585 // use to cull inner tiles. If we can't, the entire rect needs to be masked 586 match extract_inner_rect_k(rect, radius, 0.5) { 587 Some(ref rect) => { 588 let rect = transform.map_rect(rect); 589 scratch.quad_tile_classifier.add_clip_rect(rect, ClipMode::ClipOut); 590 } 591 None => { 592 scratch.quad_tile_classifier.add_mask_region(*local_rect); 593 } 594 } 595 } 596 ClipItemKind::BoxShadow { .. } => { 597 panic!("bug: old box-shadow clips unexpected in this path"); 598 } 599 ClipItemKind::Image { .. } => { 600 panic!("bug: image clips unexpected in this path"); 601 } 602 } 603 } 604 605 // Classify each tile within the quad to be Pattern / Mask / Clipped 606 let tile_info = scratch.quad_tile_classifier.classify(); 607 scratch.quad_direct_segments.clear(); 608 scratch.quad_indirect_segments.clear(); 609 610 let mut x_coords = vec![unclipped_surface_rect.min.x]; 611 let mut y_coords = vec![unclipped_surface_rect.min.y]; 612 613 let dx = (unclipped_surface_rect.max.x - unclipped_surface_rect.min.x) as f32 / x_tiles as f32; 614 let dy = (unclipped_surface_rect.max.y - unclipped_surface_rect.min.y) as f32 / y_tiles as f32; 615 616 for x in 1 .. (x_tiles as i32) { 617 x_coords.push((unclipped_surface_rect.min.x as f32 + x as f32 * dx).round()); 618 } 619 for y in 1 .. (y_tiles as i32) { 620 y_coords.push((unclipped_surface_rect.min.y as f32 + y as f32 * dy).round()); 621 } 622 623 x_coords.push(unclipped_surface_rect.max.x); 624 y_coords.push(unclipped_surface_rect.max.y); 625 626 for y in 0 .. y_coords.len()-1 { 627 let y0 = y_coords[y]; 628 let y1 = y_coords[y+1]; 629 630 if y1 <= y0 { 631 continue; 632 } 633 634 for x in 0 .. x_coords.len()-1 { 635 let x0 = x_coords[x]; 636 let x1 = x_coords[x+1]; 637 638 if x1 <= x0 { 639 continue; 640 } 641 642 // Check whether this tile requires a mask 643 let tile_info = &tile_info[y * x_tiles as usize + x]; 644 let is_direct = match tile_info.kind { 645 QuadTileKind::Clipped => { 646 // This tile was entirely clipped, so we can skip drawing it 647 continue; 648 } 649 QuadTileKind::Pattern { has_mask } => { 650 prim_is_2d_scale_translation && !has_mask && shared_pattern.is_some() 651 } 652 }; 653 654 let int_rect = DeviceRect { 655 min: point2(x0, y0), 656 max: point2(x1, y1), 657 }; 658 659 let int_rect = match clipped_surface_rect.intersection(&int_rect) { 660 Some(rect) => rect, 661 None => continue, 662 }; 663 664 let rect = int_rect.to_f32(); 665 666 // At extreme scales the rect can round to zero size due to 667 // f32 precision, causing a panic in new_dynamic, so just 668 // skip segments that would produce zero size tasks. 669 // https://bugzilla.mozilla.org/show_bug.cgi?id=1941838#c13 670 let int_rect_size = int_rect.round().to_i32().size(); 671 if int_rect_size.is_empty() { 672 continue; 673 } 674 675 if is_direct { 676 scratch.quad_direct_segments.push(QuadSegment { rect: rect.cast_unit(), task_id: RenderTaskId::INVALID }); 677 } else { 678 let pattern = match shared_pattern.cloned() { 679 Some(ref shared_pattern) => shared_pattern.clone(), 680 None => { 681 pattern_builder.build( 682 Some(rect), 683 &ctx, 684 &mut state, 685 ) 686 } 687 }; 688 689 if pattern.is_opaque { 690 quad_flags |= QuadFlags::IS_OPAQUE; 691 } 692 693 let main_prim_address = write_prim_blocks( 694 &mut state.frame_gpu_data.f32, 695 local_rect.to_untyped(), 696 clip_chain.local_clip_rect.to_untyped(), 697 pattern.base_color, 698 pattern.texture_input.task_id, 699 &[], 700 ScaleOffset::identity(), 701 ); 702 703 let task_id = add_render_task_with_mask( 704 &pattern, 705 int_rect_size, 706 rect.min, 707 clip_chain.clips_range, 708 prim_spatial_node_index, 709 pic_context.raster_spatial_node_index, 710 main_prim_address, 711 transform_id, 712 aa_flags, 713 quad_flags, 714 device_pixel_scale, 715 needs_scissor, 716 None, 717 frame_state.resource_cache, 718 state.rg_builder, 719 &mut state.frame_gpu_data.f32, 720 &mut frame_state.surface_builder, 721 ); 722 723 scratch.quad_indirect_segments.push(QuadSegment { rect: rect.cast_unit(), task_id }); 724 } 725 } 726 } 727 728 if !scratch.quad_direct_segments.is_empty() { 729 let local_to_device = map_prim_to_raster.as_2d_scale_offset() 730 .expect("bug: nine-patch segments should be axis-aligned only") 731 .then_scale(device_pixel_scale.0); 732 733 let device_prim_rect: DeviceRect = local_to_device.map_rect(&local_rect); 734 735 let pattern = match shared_pattern { 736 Some(shared_pattern) => shared_pattern.clone(), 737 None => { 738 pattern_builder.build( 739 Some(device_prim_rect), 740 &ctx, 741 &mut state, 742 ) 743 } 744 }; 745 746 add_pattern_prim( 747 &pattern, 748 local_to_device.inverse(), 749 prim_instance_index, 750 device_prim_rect.to_untyped(), 751 clip_coverage_rect.to_untyped(), 752 pattern.is_opaque, 753 frame_state, 754 targets, 755 &scratch.quad_direct_segments, 756 ); 757 } 758 759 if !scratch.quad_indirect_segments.is_empty() { 760 add_composite_prim( 761 pattern_builder.get_base_color(&ctx), 762 prim_instance_index, 763 clip_coverage_rect.to_untyped(), 764 frame_state, 765 targets, 766 &scratch.quad_indirect_segments, 767 ); 768 } 769 } 770 QuadRenderStrategy::NinePatch { clip_rect, radius } => { 771 // Render the primtive as a nine-patch decomposed in device space. 772 // Nine-patch segments that need it are drawn in a render task and then composited into the 773 // destination picture. 774 // The coordinates are provided to the shaders: 775 // - in layout space for the render task, 776 // - in device space for the instances that draw into the destination picture. 777 let clip_coverage_rect = surface 778 .map_to_device_rect(&clip_chain.pic_coverage_rect, ctx.spatial_tree); 779 780 let local_to_device = map_prim_to_raster.as_2d_scale_offset() 781 .expect("bug: nine-patch segments should be axis-aligned only") 782 .then_scale(device_pixel_scale.0); 783 784 let device_prim_rect: DeviceRect = local_to_device.map_rect(&local_rect); 785 786 let local_corner_0 = LayoutRect::new( 787 clip_rect.min, 788 clip_rect.min + radius, 789 ); 790 791 let local_corner_1 = LayoutRect::new( 792 clip_rect.max - radius, 793 clip_rect.max, 794 ); 795 796 let pic_corner_0 = pic_state.map_local_to_pic.map(&local_corner_0).unwrap(); 797 let pic_corner_1 = pic_state.map_local_to_pic.map(&local_corner_1).unwrap(); 798 799 let surface_rect_0 = surface.map_to_device_rect( 800 &pic_corner_0, 801 ctx.spatial_tree, 802 ).round_out().to_i32(); 803 804 let surface_rect_1 = surface.map_to_device_rect( 805 &pic_corner_1, 806 ctx.spatial_tree, 807 ).round_out().to_i32(); 808 809 let p0 = surface_rect_0.min; 810 let p1 = surface_rect_0.max; 811 let p2 = surface_rect_1.min; 812 let p3 = surface_rect_1.max; 813 814 let mut x_coords = [p0.x, p1.x, p2.x, p3.x]; 815 let mut y_coords = [p0.y, p1.y, p2.y, p3.y]; 816 817 x_coords.sort_by(|a, b| a.partial_cmp(b).unwrap()); 818 y_coords.sort_by(|a, b| a.partial_cmp(b).unwrap()); 819 820 scratch.quad_direct_segments.clear(); 821 scratch.quad_indirect_segments.clear(); 822 823 // TODO: re-land clip-out mode. 824 let mode = ClipMode::Clip; 825 826 fn should_create_task(mode: ClipMode, x: usize, y: usize) -> bool { 827 match mode { 828 // Only create render tasks for the corners. 829 ClipMode::Clip => x != 1 && y != 1, 830 // Create render tasks for all segments (the 831 // center will be skipped). 832 ClipMode::ClipOut => true, 833 } 834 } 835 836 for y in 0 .. y_coords.len()-1 { 837 let y0 = y_coords[y]; 838 let y1 = y_coords[y+1]; 839 840 if y1 <= y0 { 841 continue; 842 } 843 844 for x in 0 .. x_coords.len()-1 { 845 if mode == ClipMode::ClipOut && x == 1 && y == 1 { 846 continue; 847 } 848 849 let x0 = x_coords[x]; 850 let x1 = x_coords[x+1]; 851 852 if x1 <= x0 { 853 continue; 854 } 855 856 let rect = DeviceIntRect::new(point2(x0, y0), point2(x1, y1)); 857 858 let device_rect = match rect.intersection(&clipped_surface_rect) { 859 Some(rect) => rect, 860 None => { 861 continue; 862 } 863 }; 864 865 if should_create_task(mode, x, y) { 866 let pattern = shared_pattern 867 .expect("bug: nine-patch expects shared pattern, for now"); 868 869 if pattern.is_opaque { 870 quad_flags |= QuadFlags::IS_OPAQUE; 871 } 872 873 let main_prim_address = write_prim_blocks( 874 &mut state.frame_gpu_data.f32, 875 local_rect.to_untyped(), 876 clip_chain.local_clip_rect.to_untyped(), 877 pattern.base_color, 878 pattern.texture_input.task_id, 879 &[], 880 ScaleOffset::identity(), 881 ); 882 883 let task_id = add_render_task_with_mask( 884 pattern, 885 device_rect.size(), 886 device_rect.min.to_f32(), 887 clip_chain.clips_range, 888 prim_spatial_node_index, 889 pic_context.raster_spatial_node_index, 890 main_prim_address, 891 transform_id, 892 aa_flags, 893 quad_flags, 894 device_pixel_scale, 895 false, 896 None, 897 frame_state.resource_cache, 898 state.rg_builder, 899 &mut state.frame_gpu_data.f32, 900 &mut frame_state.surface_builder, 901 ); 902 scratch.quad_indirect_segments.push(QuadSegment { 903 rect: device_rect.to_f32().cast_unit(), 904 task_id, 905 }); 906 } else { 907 scratch.quad_direct_segments.push(QuadSegment { 908 rect: device_rect.to_f32().cast_unit(), 909 task_id: RenderTaskId::INVALID, 910 }); 911 }; 912 } 913 } 914 915 if !scratch.quad_direct_segments.is_empty() { 916 let pattern = pattern_builder.build( 917 None, 918 &ctx, 919 &mut state, 920 ); 921 922 add_pattern_prim( 923 &pattern, 924 local_to_device.inverse(), 925 prim_instance_index, 926 device_prim_rect.cast_unit(), 927 clip_coverage_rect.cast_unit(), 928 pattern.is_opaque, 929 frame_state, 930 targets, 931 &scratch.quad_direct_segments, 932 ); 933 } 934 935 if !scratch.quad_indirect_segments.is_empty() { 936 add_composite_prim( 937 pattern_builder.get_base_color(&ctx), 938 prim_instance_index, 939 clip_coverage_rect.cast_unit(), 940 frame_state, 941 targets, 942 &scratch.quad_indirect_segments, 943 ); 944 } 945 } 946 } 947 } 948 949 fn get_prim_render_strategy( 950 prim_spatial_node_index: SpatialNodeIndex, 951 clip_chain: &ClipChainInstance, 952 clip_store: &ClipStore, 953 interned_clips: &DataStore<ClipIntern>, 954 can_use_nine_patch: bool, 955 spatial_tree: &SpatialTree, 956 ) -> QuadRenderStrategy { 957 if !clip_chain.needs_mask { 958 return QuadRenderStrategy::Direct 959 } 960 961 fn tile_count_for_size(size: f32) -> u16 { 962 (size / MIN_QUAD_SPLIT_SIZE).min(MAX_TILES_PER_QUAD as f32).max(1.0).ceil() as u16 963 } 964 965 let prim_coverage_size = clip_chain.pic_coverage_rect.size(); 966 let x_tiles = tile_count_for_size(prim_coverage_size.width); 967 let y_tiles = tile_count_for_size(prim_coverage_size.height); 968 let try_split_prim = x_tiles > 1 || y_tiles > 1; 969 970 if !try_split_prim { 971 return QuadRenderStrategy::Indirect; 972 } 973 974 if can_use_nine_patch && clip_chain.clips_range.count == 1 { 975 let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, 0); 976 let clip_node = &interned_clips[clip_instance.handle]; 977 978 if let ClipItemKind::RoundedRectangle { ref radius, mode: ClipMode::Clip, rect, .. } = clip_node.item.kind { 979 let max_corner_width = radius.top_left.width 980 .max(radius.bottom_left.width) 981 .max(radius.top_right.width) 982 .max(radius.bottom_right.width); 983 let max_corner_height = radius.top_left.height 984 .max(radius.bottom_left.height) 985 .max(radius.top_right.height) 986 .max(radius.bottom_right.height); 987 988 if max_corner_width <= 0.5 * rect.size().width && 989 max_corner_height <= 0.5 * rect.size().height { 990 991 let clip_prim_coords_match = spatial_tree.is_matching_coord_system( 992 prim_spatial_node_index, 993 clip_node.item.spatial_node_index, 994 ); 995 996 if clip_prim_coords_match { 997 let map_clip_to_prim = SpaceMapper::new_with_target( 998 prim_spatial_node_index, 999 clip_node.item.spatial_node_index, 1000 LayoutRect::max_rect(), 1001 spatial_tree, 1002 ); 1003 1004 if let Some(rect) = map_clip_to_prim.map(&rect) { 1005 return QuadRenderStrategy::NinePatch { 1006 radius: LayoutVector2D::new(max_corner_width, max_corner_height), 1007 clip_rect: rect, 1008 }; 1009 } 1010 } 1011 } 1012 } 1013 } 1014 1015 QuadRenderStrategy::Tiled { 1016 x_tiles, 1017 y_tiles, 1018 } 1019 } 1020 1021 pub fn cache_key( 1022 prim_uid: ItemUid, 1023 prim_spatial_node_index: SpatialNodeIndex, 1024 spatial_tree: &SpatialTree, 1025 clip_chain: &ClipChainInstance, 1026 clip_store: &ClipStore, 1027 interned_clips: &DataStore<ClipIntern>, 1028 ) -> Option<QuadCacheKey> { 1029 const CACHE_MAX_CLIPS: usize = 3; 1030 1031 if (clip_chain.clips_range.count as usize) >= CACHE_MAX_CLIPS { 1032 return None; 1033 } 1034 1035 let mut clip_uids = [!0; CACHE_MAX_CLIPS]; 1036 1037 for i in 0 .. clip_chain.clips_range.count { 1038 let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, i); 1039 clip_uids[i as usize] = clip_instance.handle.uid().get_uid(); 1040 let clip_node = &interned_clips[clip_instance.handle]; 1041 if clip_node.item.spatial_node_index != prim_spatial_node_index { 1042 return None; 1043 } 1044 } 1045 1046 let spatial_uid = spatial_tree 1047 .get_spatial_node(prim_spatial_node_index) 1048 .uid; 1049 1050 Some(QuadCacheKey { 1051 prim: prim_uid.get_uid(), 1052 clips: clip_uids, 1053 spatial_node: spatial_uid, 1054 }) 1055 } 1056 1057 fn add_render_task_with_mask( 1058 pattern: &Pattern, 1059 task_size: DeviceIntSize, 1060 content_origin: DevicePoint, 1061 clips_range: ClipNodeRange, 1062 prim_spatial_node_index: SpatialNodeIndex, 1063 raster_spatial_node_index: SpatialNodeIndex, 1064 prim_address_f: GpuBufferAddress, 1065 transform_id: TransformPaletteId, 1066 aa_flags: EdgeAaSegmentMask, 1067 quad_flags: QuadFlags, 1068 device_pixel_scale: DevicePixelScale, 1069 needs_scissor_rect: bool, 1070 cache_key: Option<&RenderTaskCacheKey>, 1071 resource_cache: &mut ResourceCache, 1072 rg_builder: &mut RenderTaskGraphBuilder, 1073 gpu_buffer: &mut GpuBufferBuilderF, 1074 surface_builder: &mut SurfaceBuilder, 1075 ) -> RenderTaskId { 1076 let is_opaque = pattern.is_opaque && clips_range.count == 0; 1077 resource_cache.request_render_task( 1078 cache_key.cloned(), 1079 is_opaque, 1080 RenderTaskParent::Surface, 1081 gpu_buffer, 1082 rg_builder, 1083 surface_builder, 1084 &mut|rg_builder, _| { 1085 let task_id = rg_builder.add().init(RenderTask::new_dynamic( 1086 task_size, 1087 RenderTaskKind::new_prim( 1088 pattern.kind, 1089 pattern.shader_input, 1090 raster_spatial_node_index, 1091 device_pixel_scale, 1092 content_origin, 1093 prim_address_f, 1094 transform_id, 1095 aa_flags, 1096 quad_flags, 1097 needs_scissor_rect, 1098 pattern.texture_input.task_id, 1099 ), 1100 )); 1101 1102 // If the pattern samples from a texture, add it as a dependency 1103 // of the indirect render task that relies on it. 1104 if pattern.texture_input.task_id != RenderTaskId::INVALID { 1105 rg_builder.add_dependency(task_id, pattern.texture_input.task_id); 1106 } 1107 1108 if clips_range.count > 0 { 1109 let masks = MaskSubPass { 1110 clip_node_range: clips_range, 1111 prim_spatial_node_index, 1112 prim_address_f, 1113 }; 1114 1115 let task = rg_builder.get_task_mut(task_id); 1116 task.add_sub_pass(SubPass::Masks { masks }); 1117 } 1118 1119 task_id 1120 } 1121 ) 1122 } 1123 1124 fn add_pattern_prim( 1125 pattern: &Pattern, 1126 pattern_transform: ScaleOffset, 1127 prim_instance_index: PrimitiveInstanceIndex, 1128 rect: LayoutOrDeviceRect, 1129 clip_rect: LayoutOrDeviceRect, 1130 is_opaque: bool, 1131 frame_state: &mut FrameBuildingState, 1132 targets: &[CommandBufferIndex], 1133 segments: &[QuadSegment], 1134 ) { 1135 let prim_address = write_prim_blocks( 1136 &mut frame_state.frame_gpu_data.f32, 1137 rect, 1138 clip_rect, 1139 pattern.base_color, 1140 pattern.texture_input.task_id, 1141 segments, 1142 pattern_transform, 1143 ); 1144 1145 frame_state.set_segments(segments, targets); 1146 1147 let mut quad_flags = QuadFlags::IGNORE_DEVICE_PIXEL_SCALE 1148 | QuadFlags::APPLY_RENDER_TASK_CLIP; 1149 1150 if is_opaque { 1151 quad_flags |= QuadFlags::IS_OPAQUE; 1152 } 1153 1154 frame_state.push_cmd( 1155 &PrimitiveCommand::quad( 1156 pattern.kind, 1157 pattern.shader_input, 1158 pattern.texture_input.task_id, 1159 prim_instance_index, 1160 prim_address, 1161 TransformPaletteId::IDENTITY, 1162 quad_flags, 1163 // TODO(gw): No AA on composite, unless we use it to apply 2d clips 1164 EdgeAaSegmentMask::empty(), 1165 ), 1166 targets, 1167 ); 1168 } 1169 1170 fn add_composite_prim( 1171 base_color: ColorF, 1172 prim_instance_index: PrimitiveInstanceIndex, 1173 rect: LayoutOrDeviceRect, 1174 frame_state: &mut FrameBuildingState, 1175 targets: &[CommandBufferIndex], 1176 segments: &[QuadSegment], 1177 ) { 1178 assert!(!segments.is_empty()); 1179 1180 let composite_prim_address = write_prim_blocks( 1181 &mut frame_state.frame_gpu_data.f32, 1182 rect, 1183 rect, 1184 // TODO: The base color for composite prim should be opaque white 1185 // (or white with some transparency to support an opacity directly 1186 // in the quad primitive). However, passing opaque white 1187 // here causes glitches with Adreno GPUs on Windows specifically 1188 // (See bug 1897444). 1189 base_color, 1190 RenderTaskId::INVALID, 1191 segments, 1192 ScaleOffset::identity(), 1193 ); 1194 1195 frame_state.set_segments(segments, targets); 1196 1197 let quad_flags = QuadFlags::IGNORE_DEVICE_PIXEL_SCALE 1198 | QuadFlags::APPLY_RENDER_TASK_CLIP; 1199 1200 frame_state.push_cmd( 1201 &PrimitiveCommand::quad( 1202 PatternKind::ColorOrTexture, 1203 PatternShaderInput::default(), 1204 RenderTaskId::INVALID, 1205 prim_instance_index, 1206 composite_prim_address, 1207 TransformPaletteId::IDENTITY, 1208 quad_flags, 1209 // TODO(gw): No AA on composite, unless we use it to apply 2d clips 1210 EdgeAaSegmentMask::empty(), 1211 ), 1212 targets, 1213 ); 1214 } 1215 1216 pub fn write_prim_blocks( 1217 builder: &mut GpuBufferBuilderF, 1218 prim_rect: LayoutOrDeviceRect, 1219 clip_rect: LayoutOrDeviceRect, 1220 pattern_base_color: ColorF, 1221 pattern_texture_input: RenderTaskId, 1222 segments: &[QuadSegment], 1223 pattern_scale_offset: ScaleOffset, 1224 ) -> GpuBufferAddress { 1225 let mut writer = builder.write_blocks(5 + segments.len() * 2); 1226 1227 writer.push(&QuadPrimitive { 1228 bounds: prim_rect, 1229 clip: clip_rect, 1230 input_task: pattern_texture_input, 1231 pattern_scale_offset, 1232 color: pattern_base_color.premultiplied(), 1233 }); 1234 1235 for segment in segments { 1236 writer.push(segment); 1237 } 1238 1239 writer.finish() 1240 } 1241 1242 pub fn add_to_batch<F>( 1243 kind: PatternKind, 1244 pattern_input: PatternShaderInput, 1245 dst_task_address: RenderTaskAddress, 1246 transform_id: TransformPaletteId, 1247 prim_address_f: GpuBufferAddress, 1248 quad_flags: QuadFlags, 1249 edge_flags: EdgeAaSegmentMask, 1250 segment_index: u8, 1251 src_task_id: RenderTaskId, 1252 z_id: ZBufferId, 1253 render_tasks: &RenderTaskGraph, 1254 gpu_buffer_builder: &mut GpuBufferBuilder, 1255 mut f: F, 1256 ) where F: FnMut(BatchKey, PrimitiveInstanceData) { 1257 1258 // See the corresponfing #defines in ps_quad.glsl 1259 #[repr(u8)] 1260 enum PartIndex { 1261 Center = 0, 1262 Left = 1, 1263 Top = 2, 1264 Right = 3, 1265 Bottom = 4, 1266 All = 5, 1267 } 1268 1269 // See QuadHeader in ps_quad.glsl 1270 let mut writer = gpu_buffer_builder.i32.write_blocks(QuadHeader::NUM_BLOCKS); 1271 writer.push(&QuadHeader { 1272 transform_id, 1273 z_id, 1274 pattern_input, 1275 }); 1276 let prim_address_i = writer.finish(); 1277 1278 let texture = match src_task_id { 1279 RenderTaskId::INVALID => TextureSource::Invalid, 1280 _ => { 1281 let texture = render_tasks 1282 .resolve_texture(src_task_id) 1283 .expect("bug: valid task id must be resolvable"); 1284 1285 texture 1286 } 1287 }; 1288 1289 let textures = BatchTextures::prim_textured( 1290 texture, 1291 TextureSource::Invalid, 1292 ); 1293 1294 let default_blend_mode = if quad_flags.contains(QuadFlags::IS_OPAQUE) { 1295 BlendMode::None 1296 } else { 1297 BlendMode::PremultipliedAlpha 1298 }; 1299 1300 let edge_flags_bits = edge_flags.bits(); 1301 1302 let prim_batch_key = BatchKey { 1303 blend_mode: default_blend_mode, 1304 kind: BatchKind::Quad(kind), 1305 textures, 1306 }; 1307 1308 let aa_batch_key = BatchKey { 1309 blend_mode: BlendMode::PremultipliedAlpha, 1310 kind: BatchKind::Quad(kind), 1311 textures, 1312 }; 1313 1314 let mut instance = QuadInstance { 1315 dst_task_address, 1316 prim_address_i: prim_address_i.as_int(), 1317 prim_address_f: prim_address_f.as_int(), 1318 edge_flags: edge_flags_bits, 1319 quad_flags: quad_flags.bits(), 1320 part_index: PartIndex::All as u8, 1321 segment_index, 1322 }; 1323 1324 if edge_flags.is_empty() { 1325 // No antialisaing. 1326 f(prim_batch_key, instance.into()); 1327 } else if quad_flags.contains(QuadFlags::USE_AA_SEGMENTS) { 1328 // Add instances for the antialisaing. This gives the center part 1329 // an opportunity to stay in the opaque pass. 1330 if edge_flags.contains(EdgeAaSegmentMask::LEFT) { 1331 let instance = QuadInstance { 1332 part_index: PartIndex::Left as u8, 1333 ..instance 1334 }; 1335 f(aa_batch_key, instance.into()); 1336 } 1337 if edge_flags.contains(EdgeAaSegmentMask::RIGHT) { 1338 let instance = QuadInstance { 1339 part_index: PartIndex::Top as u8, 1340 ..instance 1341 }; 1342 f(aa_batch_key, instance.into()); 1343 } 1344 if edge_flags.contains(EdgeAaSegmentMask::TOP) { 1345 let instance = QuadInstance { 1346 part_index: PartIndex::Right as u8, 1347 ..instance 1348 }; 1349 f(aa_batch_key, instance.into()); 1350 } 1351 if edge_flags.contains(EdgeAaSegmentMask::BOTTOM) { 1352 let instance = QuadInstance { 1353 part_index: PartIndex::Bottom as u8, 1354 ..instance 1355 }; 1356 f(aa_batch_key, instance.into()); 1357 } 1358 1359 instance = QuadInstance { 1360 part_index: PartIndex::Center as u8, 1361 ..instance 1362 }; 1363 1364 f(prim_batch_key, instance.into()); 1365 } else { 1366 // Render the anti-aliased quad with a single primitive. 1367 f(aa_batch_key, instance.into()); 1368 } 1369 } 1370 1371 /// Classification result for a tile within a quad 1372 #[allow(dead_code)] 1373 #[cfg_attr(feature = "capture", derive(Serialize))] 1374 #[derive(Debug, Copy, Clone, PartialEq)] 1375 pub enum QuadTileKind { 1376 // Clipped out - can be skipped 1377 Clipped, 1378 // Requires the pattern only, can draw directly 1379 Pattern { 1380 has_mask: bool, 1381 }, 1382 } 1383 1384 #[cfg_attr(feature = "capture", derive(Serialize))] 1385 #[derive(Copy, Clone, Debug)] 1386 pub struct QuadTileInfo { 1387 pub rect: LayoutRect, 1388 pub kind: QuadTileKind, 1389 } 1390 1391 impl Default for QuadTileInfo { 1392 fn default() -> Self { 1393 QuadTileInfo { 1394 rect: LayoutRect::zero(), 1395 kind: QuadTileKind::Pattern { has_mask: false }, 1396 } 1397 } 1398 } 1399 1400 /// A helper struct for classifying a set of tiles within a quad depending on 1401 /// what strategy they can be used to draw them. 1402 #[cfg_attr(feature = "capture", derive(Serialize))] 1403 pub struct QuadTileClassifier { 1404 buffer: [QuadTileInfo; MAX_TILES_PER_QUAD * MAX_TILES_PER_QUAD], 1405 mask_regions: Vec<LayoutRect>, 1406 clip_in_regions: Vec<LayoutRect>, 1407 clip_out_regions: Vec<LayoutRect>, 1408 rect: LayoutRect, 1409 x_tiles: usize, 1410 y_tiles: usize, 1411 } 1412 1413 impl QuadTileClassifier { 1414 pub fn new() -> Self { 1415 QuadTileClassifier { 1416 buffer: [QuadTileInfo::default(); MAX_TILES_PER_QUAD * MAX_TILES_PER_QUAD], 1417 mask_regions: Vec::new(), 1418 clip_in_regions: Vec::new(), 1419 clip_out_regions: Vec::new(), 1420 rect: LayoutRect::zero(), 1421 x_tiles: 0, 1422 y_tiles: 0, 1423 } 1424 } 1425 1426 pub fn reset( 1427 &mut self, 1428 x_tiles: usize, 1429 y_tiles: usize, 1430 rect: LayoutRect, 1431 ) { 1432 assert_eq!(self.x_tiles, 0); 1433 assert_eq!(self.y_tiles, 0); 1434 1435 self.x_tiles = x_tiles; 1436 self.y_tiles = y_tiles; 1437 self.rect = rect; 1438 self.mask_regions.clear(); 1439 self.clip_in_regions.clear(); 1440 self.clip_out_regions.clear(); 1441 1442 // TODO(gw): Might be some f32 accuracy issues with how we construct these, 1443 // should be more robust here... 1444 1445 let tw = (rect.max.x - rect.min.x) / x_tiles as f32; 1446 let th = (rect.max.y - rect.min.y) / y_tiles as f32; 1447 1448 for y in 0 .. y_tiles { 1449 for x in 0 .. x_tiles { 1450 let info = &mut self.buffer[y * x_tiles + x]; 1451 1452 let p0 = LayoutPoint::new( 1453 rect.min.x + x as f32 * tw, 1454 rect.min.y + y as f32 * th, 1455 ); 1456 let p1 = LayoutPoint::new( 1457 p0.x + tw, 1458 p0.y + th, 1459 ); 1460 1461 info.rect = LayoutRect::new(p0, p1); 1462 info.kind = QuadTileKind::Pattern { has_mask: false }; 1463 } 1464 } 1465 } 1466 1467 /// Add an area that needs a clip mask / indirect area 1468 pub fn add_mask_region( 1469 &mut self, 1470 mask_region: LayoutRect, 1471 ) { 1472 self.mask_regions.push(mask_region); 1473 } 1474 1475 // TODO(gw): Make use of this to skip tiles that are completely clipped out in a follow up! 1476 pub fn add_clip_rect( 1477 &mut self, 1478 clip_rect: LayoutRect, 1479 clip_mode: ClipMode, 1480 ) { 1481 match clip_mode { 1482 ClipMode::Clip => { 1483 self.clip_in_regions.push(clip_rect); 1484 } 1485 ClipMode::ClipOut => { 1486 self.clip_out_regions.push(clip_rect); 1487 1488 self.add_mask_region(self.rect); 1489 } 1490 } 1491 } 1492 1493 /// Classify all the tiles in to categories, based on the provided masks and clip regions 1494 pub fn classify( 1495 &mut self, 1496 ) -> &[QuadTileInfo] { 1497 assert_ne!(self.x_tiles, 0); 1498 assert_ne!(self.y_tiles, 0); 1499 1500 let tile_count = self.x_tiles * self.y_tiles; 1501 let tiles = &mut self.buffer[0 .. tile_count]; 1502 1503 for info in tiles.iter_mut() { 1504 // If a clip region contains the entire tile, it's clipped 1505 for clip_region in &self.clip_in_regions { 1506 match info.kind { 1507 QuadTileKind::Clipped => {}, 1508 QuadTileKind::Pattern { .. } => { 1509 if !clip_region.intersects(&info.rect) { 1510 info.kind = QuadTileKind::Clipped; 1511 } 1512 } 1513 } 1514 1515 } 1516 1517 // If a tile doesn't intersect with a clip-out region, it's clipped 1518 for clip_region in &self.clip_out_regions { 1519 match info.kind { 1520 QuadTileKind::Clipped => {}, 1521 QuadTileKind::Pattern { .. } => { 1522 if clip_region.contains_box(&info.rect) { 1523 info.kind = QuadTileKind::Clipped; 1524 } 1525 } 1526 } 1527 } 1528 1529 // If a tile intersects with a mask region, and isn't clipped, it needs a mask 1530 for mask_region in &self.mask_regions { 1531 match info.kind { 1532 QuadTileKind::Clipped | QuadTileKind::Pattern { has_mask: true, .. } => {}, 1533 QuadTileKind::Pattern { ref mut has_mask, .. } => { 1534 if mask_region.intersects(&info.rect) { 1535 *has_mask = true; 1536 } 1537 } 1538 } 1539 } 1540 } 1541 1542 self.x_tiles = 0; 1543 self.y_tiles = 0; 1544 1545 tiles 1546 } 1547 } 1548 1549 #[cfg(test)] 1550 fn qc_new(xc: usize, yc: usize, x0: f32, y0: f32, w: f32, h: f32) -> QuadTileClassifier { 1551 let mut qc = QuadTileClassifier::new(); 1552 1553 qc.reset( 1554 xc, 1555 yc, 1556 LayoutRect::new(LayoutPoint::new(x0, y0), LayoutPoint::new(x0 + w, y0 + h), 1557 )); 1558 1559 qc 1560 } 1561 1562 #[cfg(test)] 1563 fn qc_verify(mut qc: QuadTileClassifier, expected: &[QuadTileKind]) { 1564 let tiles = qc.classify(); 1565 1566 assert_eq!(tiles.len(), expected.len()); 1567 1568 for (tile, ex) in tiles.iter().zip(expected.iter()) { 1569 assert_eq!(tile.kind, *ex, "Failed for tile {:?}", tile.rect.to_rect()); 1570 } 1571 } 1572 1573 #[cfg(test)] 1574 const P: QuadTileKind = QuadTileKind::Pattern { has_mask: false }; 1575 1576 #[cfg(test)] 1577 const C: QuadTileKind = QuadTileKind::Clipped; 1578 1579 #[cfg(test)] 1580 const M: QuadTileKind = QuadTileKind::Pattern { has_mask: true }; 1581 1582 #[test] 1583 fn quad_classify_1() { 1584 let qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0); 1585 qc_verify(qc, &[ 1586 P, P, P, 1587 P, P, P, 1588 P, P, P, 1589 ]); 1590 } 1591 1592 #[test] 1593 fn quad_classify_2() { 1594 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0); 1595 1596 let rect = LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutPoint::new(100.0, 100.0)); 1597 qc.add_clip_rect(rect, ClipMode::Clip); 1598 1599 qc_verify(qc, &[ 1600 P, P, P, 1601 P, P, P, 1602 P, P, P, 1603 ]); 1604 } 1605 1606 #[test] 1607 fn quad_classify_3() { 1608 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0); 1609 1610 let rect = LayoutRect::new(LayoutPoint::new(40.0, 40.0), LayoutPoint::new(60.0, 60.0)); 1611 qc.add_clip_rect(rect, ClipMode::Clip); 1612 1613 qc_verify(qc, &[ 1614 C, C, C, 1615 C, P, C, 1616 C, C, C, 1617 ]); 1618 } 1619 1620 #[test] 1621 fn quad_classify_4() { 1622 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0); 1623 1624 let rect = LayoutRect::new(LayoutPoint::new(30.0, 30.0), LayoutPoint::new(70.0, 70.0)); 1625 qc.add_clip_rect(rect, ClipMode::Clip); 1626 1627 qc_verify(qc, &[ 1628 P, P, P, 1629 P, P, P, 1630 P, P, P, 1631 ]); 1632 } 1633 1634 #[test] 1635 fn quad_classify_5() { 1636 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0); 1637 1638 let rect = LayoutRect::new(LayoutPoint::new(30.0, 30.0), LayoutPoint::new(70.0, 70.0)); 1639 qc.add_clip_rect(rect, ClipMode::ClipOut); 1640 1641 qc_verify(qc, &[ 1642 M, M, M, 1643 M, C, M, 1644 M, M, M, 1645 ]); 1646 } 1647 1648 #[test] 1649 fn quad_classify_6() { 1650 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0); 1651 1652 let rect = LayoutRect::new(LayoutPoint::new(40.0, 40.0), LayoutPoint::new(60.0, 60.0)); 1653 qc.add_clip_rect(rect, ClipMode::ClipOut); 1654 1655 qc_verify(qc, &[ 1656 M, M, M, 1657 M, M, M, 1658 M, M, M, 1659 ]); 1660 } 1661 1662 #[test] 1663 fn quad_classify_7() { 1664 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0); 1665 1666 let rect = LayoutRect::new(LayoutPoint::new(20.0, 10.0), LayoutPoint::new(90.0, 80.0)); 1667 qc.add_mask_region(rect); 1668 1669 qc_verify(qc, &[ 1670 M, M, M, 1671 M, M, M, 1672 M, M, M, 1673 ]); 1674 } 1675 1676 #[test] 1677 fn quad_classify_8() { 1678 let mut qc = qc_new(3, 3, 0.0, 0.0, 100.0, 100.0); 1679 1680 let rect = LayoutRect::new(LayoutPoint::new(40.0, 40.0), LayoutPoint::new(60.0, 60.0)); 1681 qc.add_mask_region(rect); 1682 1683 qc_verify(qc, &[ 1684 P, P, P, 1685 P, M, P, 1686 P, P, P, 1687 ]); 1688 } 1689 1690 #[test] 1691 fn quad_classify_9() { 1692 let mut qc = qc_new(4, 4, 100.0, 200.0, 100.0, 100.0); 1693 1694 let rect = LayoutRect::new(LayoutPoint::new(90.0, 180.0), LayoutPoint::new(140.0, 240.0)); 1695 qc.add_mask_region(rect); 1696 1697 qc_verify(qc, &[ 1698 M, M, P, P, 1699 M, M, P, P, 1700 P, P, P, P, 1701 P, P, P, P, 1702 ]); 1703 } 1704 1705 #[test] 1706 fn quad_classify_10() { 1707 let mut qc = qc_new(4, 4, 100.0, 200.0, 100.0, 100.0); 1708 1709 let mask_rect = LayoutRect::new(LayoutPoint::new(90.0, 180.0), LayoutPoint::new(140.0, 240.0)); 1710 qc.add_mask_region(mask_rect); 1711 1712 let clip_rect = LayoutRect::new(LayoutPoint::new(120.0, 220.0), LayoutPoint::new(160.0, 280.0)); 1713 qc.add_clip_rect(clip_rect, ClipMode::Clip); 1714 1715 qc_verify(qc, &[ 1716 M, M, P, C, 1717 M, M, P, C, 1718 P, P, P, C, 1719 P, P, P, C, 1720 ]); 1721 } 1722 1723 #[test] 1724 fn quad_classify_11() { 1725 let mut qc = qc_new(4, 4, 100.0, 200.0, 100.0, 100.0); 1726 1727 let mask_rect = LayoutRect::new(LayoutPoint::new(90.0, 180.0), LayoutPoint::new(140.0, 240.0)); 1728 qc.add_mask_region(mask_rect); 1729 1730 let clip_rect = LayoutRect::new(LayoutPoint::new(120.0, 220.0), LayoutPoint::new(160.0, 280.0)); 1731 qc.add_clip_rect(clip_rect, ClipMode::Clip); 1732 1733 let clip_out_rect = LayoutRect::new(LayoutPoint::new(130.0, 200.0), LayoutPoint::new(160.0, 240.0)); 1734 qc.add_clip_rect(clip_out_rect, ClipMode::ClipOut); 1735 1736 qc_verify(qc, &[ 1737 M, M, M, C, 1738 M, M, M, C, 1739 M, M, M, C, 1740 M, M, M, C, 1741 ]); 1742 } 1743 1744 #[test] 1745 fn quad_classify_12() { 1746 let mut qc = qc_new(4, 4, 100.0, 200.0, 100.0, 100.0); 1747 1748 let clip_out_rect = LayoutRect::new(LayoutPoint::new(130.0, 200.0), LayoutPoint::new(160.0, 240.0)); 1749 qc.add_clip_rect(clip_out_rect, ClipMode::ClipOut); 1750 1751 let clip_rect = LayoutRect::new(LayoutPoint::new(120.0, 220.0), LayoutPoint::new(160.0, 280.0)); 1752 qc.add_clip_rect(clip_rect, ClipMode::Clip); 1753 1754 let mask_rect = LayoutRect::new(LayoutPoint::new(90.0, 180.0), LayoutPoint::new(140.0, 240.0)); 1755 qc.add_mask_region(mask_rect); 1756 1757 qc_verify(qc, &[ 1758 M, M, M, C, 1759 M, M, M, C, 1760 M, M, M, C, 1761 M, M, M, C, 1762 ]); 1763 }