/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Special sentinel value recognized by the shader. It is considered to be // a dummy task that doesn't mask out anything. const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fffffff);
/// Used to signal there are no segments provided with this primitive. pubconst INVALID_SEGMENT_INDEX: i32 = 0xffff;
/// Size in device pixels for tiles that clip masks are drawn in. const CLIP_RECTANGLE_TILE_SIZE: i32 = 128;
/// The minimum size of a clip mask before trying to draw in tiles. const CLIP_RECTANGLE_AREA_THRESHOLD: f32 = (CLIP_RECTANGLE_TILE_SIZE * CLIP_RECTANGLE_TILE_SIZE * 4) as f32;
impl TextureSource { fn combine(&self, other: TextureSource) -> TextureSource { if other == TextureSource::Invalid {
*self
} else {
other
}
}
}
/// Optional textures that can be used as a source in the shaders. /// Textures that are not used by the batch are equal to TextureId::invalid(). #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct BatchTextures { pub input: TextureSet, pub clip_mask: TextureSource,
}
pubstruct BatchRects { /// Union of all of the batch's item rects. /// /// Very often we can skip iterating over item rects by testing against /// this one first.
batch: PictureRect, /// When the batch rectangle above isn't a good enough approximation, we /// store per item rects.
items: Option<FrameVec<PictureRect>>, // TODO: batch rects don't need to be part of the frame but they currently // are. It may be cleaner to remove them from the frame's final data structure // and not use the frame's allocator.
allocator: FrameAllocator,
}
#[inline] fn add_rect(&mutself, rect: &PictureRect) { let union = self.batch.union(rect); // If we have already started storing per-item rects, continue doing so. // Otherwise, check whether only storing the batch rect is a good enough // approximation. iflet Some(items) = &mutself.items {
items.push(*rect);
} elseifself.batch.area() + rect.area() < union.area() { letmut items = self.allocator.clone().new_vec_with_capacity(16);
items.push(self.batch);
items.push(*rect); self.items = Some(items);
}
iflet Some(items) = &self.items {
items.iter().any(|item| item.intersects(rect))
} else { // If we don't have per-item rects it means the batch rect is a good // enough approximation and we didn't bother storing per-rect items. true
}
}
}
/// Clear all current batches in this list. This is typically used /// when a primitive is encountered that occludes all previous /// content in this batch list. fn clear(&mutself) { self.current_batch_index = usize::MAX; self.current_z_id = ZBufferId::invalid(); self.batches.clear(); self.batch_rects.clear();
}
pubfn set_params_and_get_batch(
&mutself,
key: BatchKey,
features: BatchFeatures, // The bounding box of everything at this Z plane. We expect potentially // multiple primitive segments coming with the same `z_id`.
z_bounding_rect: &PictureRect,
z_id: ZBufferId,
) -> &mut FrameVec<PrimitiveInstanceData> { if z_id != self.current_z_id || self.current_batch_index == usize::MAX ||
!self.batches[self.current_batch_index].key.is_compatible_with(&key)
{ letmut selected_batch_index = None;
match key.blend_mode {
BlendMode::Advanced(_) ifself.break_advanced_blend_batches => { // don't try to find a batch
}
_ => { for (batch_index, batch) inself.batches.iter().enumerate().rev() { // For normal batches, we only need to check for overlaps for batches // other than the first batch we consider. If the first batch // is compatible, then we know there isn't any potential overlap // issues to worry about. if batch.key.is_compatible_with(&key) {
selected_batch_index = Some(batch_index); break;
}
if selected_batch_index.is_none() { // Text runs tend to have a lot of instances per batch, causing a lot of reallocation // churn as items are added one by one, so we give it a head start. Ideally we'd start // with a larger number, closer to 1k but in some bad cases with lots of batch break // we would be wasting a lot of memory. // Generally it is safe to preallocate small-ish values for other batch kinds because // the items are small and there are no zero-sized batches so there will always be // at least one allocation. let prealloc = match key.kind {
BatchKind::TextRun(..) => 128,
_ => 16,
}; letmut new_batch = PrimitiveBatch::new(key, self.batches.allocator().clone());
new_batch.instances.reserve(prealloc);
selected_batch_index = Some(self.batches.len()); self.batches.push(new_batch); self.batch_rects.push(BatchRects::new(self.batches.allocator().clone()));
}
/// Clear all current batches in this list. This is typically used /// when a primitive is encountered that occludes all previous /// content in this batch list. fn clear(&mutself) { self.current_batch_index = usize::MAX; self.batches.clear();
}
pubfn set_params_and_get_batch(
&mutself,
key: BatchKey,
features: BatchFeatures, // The bounding box of everything at the current Z, whatever it is. We expect potentially // multiple primitive segments produced by a primitive, which we allow to check // `current_batch_index` instead of iterating the batches.
z_bounding_rect: &PictureRect,
) -> &mut FrameVec<PrimitiveInstanceData> { // If the area of this primitive is larger than the given threshold, // then it is large enough to warrant breaking a batch for. In this // case we just see if it can be added to the existing batch or // create a new one. let is_large_occluder = z_bounding_rect.area() > self.pixel_area_threshold_for_new_batch; // Since primitives of the same kind tend to come in succession, we keep track // of the current batch index to skip the search in some cases. We ignore the // current batch index in the case of large occluders to make sure they get added // at the top of the bach list. if is_large_occluder || self.current_batch_index == usize::MAX ||
!self.batches[self.current_batch_index].key.is_compatible_with(&key) { letmut selected_batch_index = None; if is_large_occluder { iflet Some(batch) = self.batches.last() { if batch.key.is_compatible_with(&key) {
selected_batch_index = Some(self.batches.len() - 1);
}
}
} else { // Otherwise, look back through a reasonable number of batches. for (batch_index, batch) inself.batches.iter().enumerate().rev().take(self.lookback_count) { if batch.key.is_compatible_with(&key) {
selected_batch_index = Some(batch_index); break;
}
}
}
if selected_batch_index.is_none() { let new_batch = PrimitiveBatch::new(key, self.batches.allocator().clone());
selected_batch_index = Some(self.batches.len()); self.batches.push(new_batch);
}
let batch = &mutself.batches[self.current_batch_index];
batch.features |= features;
batch.key.textures.merge(&key.textures);
&mut batch.instances
}
fn finalize(&mutself) { // Reverse the instance arrays in the opaque batches // to get maximum z-buffer efficiency by drawing // front-to-back. // TODO(gw): Maybe we can change the batch code to // build these in reverse and avoid having // to reverse the instance array here. for batch in &mutself.batches {
batch.instances.reverse();
}
}
}
bitflags! { /// Features of the batch that, if not requested, may allow a fast-path. /// /// Rather than breaking batches when primitives request different features, /// we always request the minimum amount of features to satisfy all items in /// the batch. /// The goal is to let the renderer be optionally select more specialized /// versions of a shader if the batch doesn't require code certain code paths. /// Not all shaders necessarily implement all of these features. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] pubstruct BatchFeatures: u8 { const ALPHA_PASS = 1 << 0; const ANTIALIASING = 1 << 1; const REPETITION = 1 << 2; /// Indicates a primitive in this batch may use a clip mask. const CLIP_MASK = 1 << 3;
}
}
#[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct AlphaBatchContainer { pub opaque_batches: FrameVec<PrimitiveBatch>, pub alpha_batches: FrameVec<PrimitiveBatch>, /// The overall scissor rect for this render task, if one /// is required. pub task_scissor_rect: Option<DeviceIntRect>, /// The rectangle of the owning render target that this /// set of batches affects. pub task_rect: DeviceIntRect,
}
for other_batch in builder.opaque_batch_list.batches { let batch_index = self.opaque_batches.iter().position(|batch| {
batch.key.is_compatible_with(&other_batch.key)
});
for other_batch in builder.alpha_batch_list.batches { let batch_index = self.alpha_batches.iter().skip(min_batch_index).position(|batch| {
batch.key.is_compatible_with(&other_batch.key)
});
match batch_index {
Some(batch_index) => { let index = batch_index + min_batch_index; self.alpha_batches[index].merge(other_batch);
min_batch_index = index;
}
None => { self.alpha_batches.push(other_batch);
min_batch_index = self.alpha_batches.len();
}
}
}
}
}
/// Each segment can optionally specify a per-segment /// texture set and one user data field. #[derive(Debug, Copy, Clone)] struct SegmentInstanceData {
textures: TextureSet,
specific_resource_address: i32,
}
/// Encapsulates the logic of building batches for items that are blended. pubstruct AlphaBatchBuilder { pub alpha_batch_list: AlphaBatchList, pub opaque_batch_list: OpaqueBatchList, pub render_task_id: RenderTaskId,
render_task_address: RenderTaskAddress,
}
impl AlphaBatchBuilder { pubfn new(
screen_size: DeviceIntSize,
break_advanced_blend_batches: bool,
lookback_count: usize,
render_task_id: RenderTaskId,
render_task_address: RenderTaskAddress,
memory: &FrameMemory,
) -> Self { // The threshold for creating a new batch is // one quarter the screen size. let batch_area_threshold = (screen_size.width * screen_size.height) as f32 / 4.0;
/// Clear all current batches in this builder. This is typically used /// when a primitive is encountered that occludes all previous /// content in this batch list. fn clear(&mutself) { self.alpha_batch_list.clear(); self.opaque_batch_list.clear();
}
/// Supports (recursively) adding a list of primitives and pictures to an alpha batch /// builder. In future, it will support multiple dirty regions / slices, allowing the /// contents of a picture to be spliced into multiple batch builders. pubstruct BatchBuilder { /// A temporary buffer that is used during glyph fetching, stored here /// to reduce memory allocations.
glyph_fetch_buffer: Vec<GlyphFetchResult>,
/// Clear all current batchers. This is typically used when a primitive /// is encountered that occludes all previous content in this batch list. fn clear_batches(&mutself) { self.batcher.clear();
}
// Adds a primitive to a batch. // It can recursively call itself in some situations, for // example if it encounters a picture where the items // in that picture are being drawn into the same target. pubfn add_prim_to_batch(
&mutself,
cmd: &PrimitiveCommand,
prim_spatial_node_index: SpatialNodeIndex,
ctx: &RenderTargetContext,
render_tasks: &RenderTaskGraph,
prim_headers: &mut PrimitiveHeaders,
transforms: &mut TransformPalette,
root_spatial_node_index: SpatialNodeIndex,
surface_spatial_node_index: SpatialNodeIndex,
z_generator: &mut ZBufferIdGenerator,
prim_instances: &[PrimitiveInstance],
gpu_buffer_builder: &mut GpuBufferBuilder,
segments: &[RenderTaskId],
) { let (draw_index, extra_prim_gpu_address) = match cmd {
PrimitiveCommand::Simple { draw_index } => {
(draw_index, None)
}
PrimitiveCommand::Complex { draw_index, gpu_address } => {
(draw_index, Some(gpu_address.as_int()))
}
PrimitiveCommand::Instance { draw_index, gpu_buffer_address } => {
(draw_index, Some(gpu_buffer_address.as_int()))
}
PrimitiveCommand::Quad { pattern, pattern_input, draw_index, gpu_buffer_address, quad_flags, edge_flags, transform_id, src_color_task_id, blend_mode } => { let prim_info = &ctx.scratch.frame.draws[draw_index.0as usize]; let bounding_rect = &prim_info.clip_chain.pic_coverage_rect; let render_task_address = self.batcher.render_task_address;
if segments.is_empty() { let z_id = z_generator.next();
quad::add_to_batch(
*pattern,
*pattern_input,
render_task_address,
*transform_id,
*gpu_buffer_address,
*quad_flags,
*edge_flags,
INVALID_SEGMENT_INDEX as u8,
*src_color_task_id,
z_id,
*blend_mode,
render_tasks,
gpu_buffer_builder,
|key, instance| { let batch = self.batcher.set_params_and_get_batch(
key,
BatchFeatures::empty(),
bounding_rect,
z_id,
);
batch.push(instance);
},
);
} else { for (i, task_id) in segments.iter().enumerate() { // TODO(gw): edge_flags should be per-segment, when used for more than composites
debug_assert!(edge_flags.is_empty());
// If this primitive is a backdrop, that means that it is known to cover // the entire picture cache background. In that case, the renderer will // use the backdrop color as a clear color, and so we can drop this // primitive and any prior primitives from the batch lists for this // picture cache slice. if vis_flags.contains(PrimitiveVisibilityFlags::IS_BACKDROP) { self.clear_batches(); return;
}
let transform_id = transforms.gpu.get_id(
prim_spatial_node_index,
root_spatial_node_index,
ctx.spatial_tree,
);
// TODO(gw): Calculating this for every primitive is a bit // wasteful. We should probably cache this in // the scroll node... let transform_metadata = transform_id.metadata(); let prim_info = &ctx.scratch.frame.draws[draw_index.0as usize]; let bounding_rect = &prim_info.clip_chain.pic_coverage_rect;
letmut z_id = z_generator.next();
let prim_rect = ctx.data_stores.get_local_prim_rect(
prim_instance,
prim_info.snapped_local_rect,
&ctx.prim_store.pictures,
ctx.surfaces,
);
letmut batch_features = BatchFeatures::empty(); let may_need_repetition = match prim_instance.kind {
PrimitiveKind::Image { .. } => { let idx = prim_info.kind_scratch.unwrap_image();
ctx.scratch.frame.images[idx].may_need_repetition
}
PrimitiveKind::NormalBorder { .. } => { let idx = prim_info.kind_scratch.unwrap_normal_border();
ctx.scratch.frame.normal_border[idx].may_need_repetition
} // Image borders always go through brush_image and may tile // their mid sections, so request the repetition-capable // shader.
PrimitiveKind::ImageBorder { .. } => true, // Patterned line decorations (Dashed / Dotted / Wavy) batch // as `BrushBatchKind::Image` over a cached pattern tile and // rely on shader-level repetition to span the segment. // Solid lines batch as `BrushBatchKind::Solid`, where the // REPETITION flag is harmless.
PrimitiveKind::LineDecoration { .. } => true, // Other prim kinds don't reach the brush_image consumer of // BatchFeatures::REPETITION; the flag is dead state for // them.
_ => false,
}; if may_need_repetition {
batch_features |= BatchFeatures::REPETITION;
}
if !transform_id.is_2d_axis_aligned() || is_anti_aliased {
batch_features |= BatchFeatures::ANTIALIASING;
}
// Check if the primitive might require a clip mask. if prim_info.clip_task_index != ClipTaskIndex::INVALID {
batch_features |= BatchFeatures::CLIP_MASK;
}
if !bounding_rect.is_empty() {
debug_assert_eq!(prim_info.clip_chain.pic_spatial_node_index, surface_spatial_node_index, "The primitive's bounding box is specified in a different coordinate system from the current batch!");
}
iflet PrimitiveKind::Picture { pic_index, .. } = prim_instance.kind { let pic_scratch_handle = ctx.scratch.frame.draws[draw_index.0as usize].kind_scratch.unwrap_picture(); let picture = &ctx.prim_store.pictures[pic_index.0]; let picture_scratch = &ctx.scratch.frame.pictures[pic_scratch_handle]; iflet Some(snapshot) = picture.snapshot { if snapshot.detached { return;
}
}
let blend_mode = BlendMode::PremultipliedAlpha; let prim_cache_address = ctx.globals.default_image_data;
match picture.raster_config {
Some(ref raster_config) => { // If the child picture was rendered in local space, we can safely // interpolate the UV coordinates with perspective correction. let brush_flags = brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION;
let surface = &ctx.surfaces[raster_config.surface_index.0]; letmut local_clip_rect = prim_info.clip_chain.local_clip_rect;
// If we are drawing with snapping enabled, form a simple transform that just applies // the scale / translation from the raster transform. Otherwise, in edge cases where the // intermediate surface has a non-identity but axis-aligned transform (e.g. a 180 degree // rotation) it can be applied twice. let transform_id = if surface.surface_spatial_node_index == surface.raster_spatial_node_index {
transform_id
} else { let map_local_to_raster = SpaceMapper::new_with_target(
root_spatial_node_index,
surface.surface_spatial_node_index,
LayoutRect::max_rect(),
ctx.spatial_tree,
);
let raster_rect = map_local_to_raster
.map(&prim_rect)
.unwrap();
let sx = (raster_rect.max.x - raster_rect.min.x) / (prim_rect.max.x - prim_rect.min.x); let sy = (raster_rect.max.y - raster_rect.min.y) / (prim_rect.max.y - prim_rect.min.y);
let tx = raster_rect.min.x - sx * prim_rect.min.x; let ty = raster_rect.min.y - sy * prim_rect.min.y;
let transform = ScaleOffset::new(sx, sy, tx, ty);
let raster_clip_rect = map_local_to_raster
.map(&prim_info.clip_chain.local_clip_rect)
.unwrap();
local_clip_rect = transform.unmap_rect(&raster_clip_rect);
match raster_config.composite_mode {
PictureCompositeMode::TileCache { .. } => { // TODO(gw): For now, TileCache is still a composite mode, even though // it will only exist as a top level primitive and never // be encountered during batching. Consider making TileCache // a standalone type, not a picture. return;
}
PictureCompositeMode::IntermediateSurface { .. } => { // TODO(gw): As an optimization, support making this a pass-through // and/or drawing directly from here when possible // (e.g. if not wrapped by filters / different spatial node). return;
}
_=>{}
}
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let pic_task_id = picture_scratch.primary_render_task_id.unwrap();
let (uv_rect_address, texture) = render_tasks.resolve_location(
pic_task_id,
).unwrap();
// The set of input textures that most composite modes use, // howevr some override it. let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
let (key, prim_user_data, resource_address) = match raster_config.composite_mode {
PictureCompositeMode::TileCache { .. }
| PictureCompositeMode::IntermediateSurface { .. }
=> return,
PictureCompositeMode::Filter(ref filter) => {
assert!(filter.is_visible()); match filter {
Filter::Blur { .. } => { let kind = BatchKind::Brush(
BrushBatchKind::Image(ImageBufferKind::Texture2D)
);
let key = BatchKey::new(
kind,
blend_mode,
textures,
);
(key, prim_user_data, uv_rect_address.as_int())
}
Filter::DropShadows(shadows) => { // Draw an instance per shadow first, following by the content.
// The shadows and the content get drawn as a brush image. let kind = BatchKind::Brush(
BrushBatchKind::Image(ImageBufferKind::Texture2D),
);
// Gets the saved render task ID of the content, which is // deeper in the render task graph than the direct child. let secondary_id = picture_scratch.secondary_render_task_id.expect("no secondary!?"); let content_source = { let secondary_task = &render_tasks[secondary_id]; let texture_id = secondary_task.get_target_texture();
TextureSource::TextureCache(
texture_id,
Swizzle::default(),
)
};
// Retrieve the UV rect addresses for shadow/content. let shadow_uv_rect_address = uv_rect_address; let shadow_textures = textures;
let content_uv_rect_address = render_tasks[secondary_id]
.get_texture_address()
.as_int();
// Build BatchTextures for shadow/content let content_textures = BatchTextures::prim_textured(
content_source,
clip_mask_texture_id,
);
// Build batch keys for shadow/content let shadow_key = BatchKey::new(kind, blend_mode, shadow_textures); let content_key = BatchKey::new(kind, blend_mode, content_textures);
for (shadow, shadow_prim_address) in shadows.iter().zip(picture_scratch.extra_gpu_data.iter()) { let shadow_rect = picture_prim_header.local_rect.translate(shadow.offset);
// These filters are handled via different paths.
Filter::ComponentTransfer |
Filter::Blur { .. } |
Filter::DropShadows(..) |
Filter::Opacity(..) |
Filter::SVGGraphNode(..) => unreachable!(),
};
// Other filters that may introduce opacity are handled via different // paths. iflet Filter::ColorMatrix(..) = filter {
is_opaque = false;
}
let blend_mode = if is_opaque {
BlendMode::None
} else {
BlendMode::PremultipliedAlpha
};
let key = BatchKey::new(
BatchKind::Brush(BrushBatchKind::Blend),
blend_mode,
textures,
);
let prim_user_data = [
uv_rect_address.as_int(),
filter_mode,
user_data, 0,
];
(key, prim_user_data, 0)
}
}
}
PictureCompositeMode::ComponentTransferFilter(handle) => { // This is basically the same as the general filter case above // except we store a little more data in the filter mode and // a gpu cache handle in the user data. let filter_data = &ctx.data_stores.filter_data[handle]; let filter_mode : i32 = Filter::ComponentTransfer.as_int() |
((filter_data.data.r_func.to_int() << 28 |
filter_data.data.g_func.to_int() << 24 |
filter_data.data.b_func.to_int() << 20 |
filter_data.data.a_func.to_int() << 16) as i32);
let user_data = filter_data.gpu_buffer_address.as_int();
let key = BatchKey::new(
BatchKind::Brush(BrushBatchKind::Blend),
BlendMode::PremultipliedAlpha,
textures,
);
let prim_user_data = [
uv_rect_address.as_int(),
filter_mode,
user_data, 0,
];
let color0 = render_tasks[backdrop_id].get_target_texture(); let color1 = render_tasks[pic_task_id].get_target_texture();
// Create a separate brush instance for each batcher. For most cases, // there is only one batcher. However, in the case of drawing onto // a picture cache, there is one batcher per tile. Although not // currently used, the implementation of mix-blend-mode now supports // doing partial readbacks per-tile. In future, this will be enabled // and allow mix-blends to operate on picture cache surfaces without // a separate isolated intermediate surface.
let batch_key = BatchKey::new(
BatchKind::Brush(
BrushBatchKind::MixBlend {
task_id: self.batcher.render_task_id,
backdrop_id,
},
),
BlendMode::PremultipliedAlpha,
BatchTextures {
input: TextureSet {
colors: [
TextureSource::TextureCache(
color0,
Swizzle::default(),
),
TextureSource::TextureCache(
color1,
Swizzle::default(),
),
TextureSource::Invalid,
],
},
clip_mask: clip_mask_texture_id,
},
); let src_uv_address = render_tasks[pic_task_id].get_texture_address(); let readback_uv_address = render_tasks[backdrop_id].get_texture_address(); let prim_header = PrimitiveHeader {
user_data: [
mode as u32 as i32,
readback_uv_address.as_int(),
src_uv_address.as_int(), 0,
],
..picture_prim_header
}; let prim_header_index = prim_headers.push(&prim_header);
return;
}
PictureCompositeMode::Blit(_) => { match picture.context_3d {
Picture3DContext::In { root_data: Some(_), .. } => {
unreachable!("bug: should not have a raster_config");
}
Picture3DContext::In { root_data: None, .. } => { // TODO(gw): Store this inside the split picture so that we // don't need to pass in extra_prim_gpu_address for // every prim instance. // TODO(gw): Ideally we'd skip adding 3d child prims to batches // without gpu cache address but it's currently // used by the prepare pass. Refactor this! let extra_prim_gpu_address = match extra_prim_gpu_address {
Some(prim_address) => prim_address,
None => return,
};
// Need a new z-id for each child preserve-3d context added // by this inner loop. let z_id = z_generator.next();
let prim_header = PrimitiveHeader {
z: z_id,
transform_id: transforms.gpu.get_id(
prim_spatial_node_index,
root_spatial_node_index,
ctx.spatial_tree,
),
user_data: [
uv_rect_address.as_int(),
BrushFlags::PERSPECTIVE_INTERPOLATION.bits() as i32, 0,
clip_task_address.0as i32,
],
..picture_prim_header
}; let prim_header_index = prim_headers.push(&prim_header);
let key = BatchKey::new(
BatchKind::SplitComposite,
BlendMode::PremultipliedAlpha,
textures,
);
let base_prim_header = PrimitiveHeader {
local_rect: prim_rect,
local_clip_rect: prim_info.clip_chain.local_clip_rect,
transform_id,
z: z_id,
render_task_address: self.batcher.render_task_address,
specific_prim_address: GpuBufferAddress::INVALID.as_int(), // Will be overridden by most uses
user_data: [0; 4], // Will be overridden by most uses
};
let common_data = ctx.data_stores.as_common_data(prim_instance);
let (prim_cache_address, segments) = if segment_instance_index == SegmentInstanceIndex::UNUSED {
(common_data.gpu_buffer_address, None)
} else { let segment_instance = &ctx.scratch.frame.segment_instances[segment_instance_index]; let segments = Some(&ctx.scratch.frame.segments[segment_instance.segments_range]);
(segment_instance.gpu_data, segments)
};
// The following primitives lower to the image brush shader in the same way. // For ImageBorder, the GPU block lives on per-instance scratch // (`ImageBorderScratch.gpu_address`), so override // `prim_cache_address` here. letmut prim_cache_address = prim_cache_address; let img_brush_data = match prim_instance.kind {
PrimitiveKind::RadialGradient { .. } => {
unreachable!("BUG: radial gradients should always use quad path");
}
PrimitiveKind::ConicGradient { .. } => {
unreachable!("BUG: conic gradients should always use quad path");
}
PrimitiveKind::ImageBorder { data_handle, .. } => { let prim_data = &ctx.data_stores.image_border[data_handle]; let ib_handle = prim_info.kind_scratch.unwrap_image_border(); let ib_scratch = ctx.scratch.frame.image_border[ib_handle];
prim_cache_address = ib_scratch.gpu_address; let brush_segments = &ctx.scratch.frame.segments[ib_scratch.brush_segments_range];
Some((prim_data.kind.src_color, brush_segments))
}
_ => None,
};
iflet Some((src_color, brush_segments)) = img_brush_data { let src_color = render_tasks.resolve_location(src_color);
let (uv_rect_address, texture_source) = match src_color {
Some(src) => src,
None => { return;
}
};
let textures = TextureSet::prim_textured(texture_source);
let prim_header = PrimitiveHeader {
specific_prim_address: prim_cache_address.as_int(),
user_data: batch_params.prim_user_data,
..base_prim_header
}; let prim_header_index = prim_headers.push(&prim_header);
let brush_segments = &ctx.scratch.frame.segments[nb_scratch.brush_segments_range]; self.add_segmented_prim_to_batch(
Some(brush_segments),
common_data.opacity,
&batch_params,
blend_mode,
batch_features,
brush_flags,
common_data.transformed_aa_edges,
prim_header_index,
bounding_rect,
transform_metadata,
z_id,
prim_info.clip_task_index,
ctx,
render_tasks,
);
}
PrimitiveKind::TextRun { data_handle, .. } => { let text_run_scratch_handle = prim_info.kind_scratch.unwrap_text_run(); let run_scratch = &ctx.scratch.frame.text_runs[text_run_scratch_handle]; let subpx_dir = run_scratch.used_font.get_subpx_dir(); let prim_data = &ctx.data_stores.text_run[data_handle];
let glyph_keys = &ctx.scratch.frame.glyph_keys[run_scratch.glyph_keys_range];
// `local_rect.p0` is the run anchor (the normalized prim rect // origin). In device mode the shader transforms it to device // space and adds the per-glyph device offsets stored at // `gpu_address`. `user_data` carries the raster scale (for // local-raster mode's raster -> local mapping) and the mode flag // (0 = device, 1 = local raster). let prim_header = PrimitiveHeader {
local_rect: run_scratch.local_rect,
specific_prim_address: run_scratch.gpu_address.as_int(),
user_data: [
(run_scratch.raster_scale * 65535.0).round() as i32,
run_scratch.local_raster as i32, 0, 0,
],
..base_prim_header
}; let prim_header_index = prim_headers.push(&prim_header); let base_instance = GlyphInstance::new(
prim_header_index,
); let batcher = &mutself.batcher;
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
// The run_scratch.used_font.clone() is here instead of inline in the `fetch_glyph` // function call to work around a miscompilation. // https://github.com/rust-lang/rust/issues/80111 let font = run_scratch.used_font.clone();
ctx.resource_cache.fetch_glyphs(
font,
&glyph_keys,
&gpu_buffer_builder.f32,
&mutself.glyph_fetch_buffer,
|texture_id, glyph_format, glyphs| {
debug_assert_ne!(texture_id, TextureSource::Invalid);
let subpx_dir = subpx_dir.limit_by(glyph_format);
let textures = BatchTextures::prim_textured(
texture_id,
clip_mask_texture_id,
);
let kind = BatchKind::TextRun(glyph_format);
let (blend_mode, color_mode) = match glyph_format {
GlyphFormat::Subpixel |
GlyphFormat::TransformedSubpixel => {
debug_assert!(ctx.use_dual_source_blending);
(
BlendMode::SubpixelDualSource,
ShaderColorMode::SubpixelDualSource,
)
}
GlyphFormat::Alpha |
GlyphFormat::TransformedAlpha |
GlyphFormat::Bitmap => {
(
BlendMode::PremultipliedAlpha,
ShaderColorMode::Alpha,
)
}
GlyphFormat::ColorBitmap => {
(
BlendMode::PremultipliedAlpha, if prim_data.shadow { // Ignore color and only sample alpha when shadowing.
ShaderColorMode::BitmapShadow
} else {
ShaderColorMode::ColorBitmap
},
)
}
};
// Calculate a tighter bounding rect of just the glyphs passed to this // callback from request_glyphs(), rather than using the bounds of the // entire text run. This improves batching when glyphs are fragmented // over multiple textures in the texture cache. // This mirrors the glyph positioning in the ps_text_run shader. The // TRANSFORM_GLYPHS branch covers device mode for 2D rotated/skewed // glyphs; the other branch covers device-mode axis-aligned and // local-raster mode (distinguished by `run_scratch.raster_scale`). // `text_offset` is zero because glyph positions are stored absolutely // (relative to the prim origin via `local_rect.min`), not relative to // a separate snapped reference-frame offset; the TRANSFORM_GLYPHS // branch's `raster_text_offset` then reduces to the reference-frame // device snap that `request_resources` applies. let tight_bounding_rect = { let snap_bias = match subpx_dir {
SubpixelDirection::None => DeviceVector2D::new(0.5, 0.5),
SubpixelDirection::Horizontal => DeviceVector2D::new(0.125, 0.5),
SubpixelDirection::Vertical => DeviceVector2D::new(0.5, 0.125),
}; let text_offset = LayoutVector2D::zero();
let pic_bounding_rect = if run_scratch.used_font.flags.contains(FontInstanceFlags::TRANSFORM_GLYPHS) { letmut device_bounding_rect = DeviceRect::default();
let glyph_transform = ctx.spatial_tree.get_relative_transform(
prim_spatial_node_index,
root_spatial_node_index,
).into_transform()
.with_destination::<WorldPixel>()
.then(&euclid::Transform3D::from_scale(ctx.global_device_pixel_scale));
let glyph_translation = DeviceVector2D::new(glyph_transform.m41, glyph_transform.m42);
letmut use_tight_bounding_rect = true; for glyph in glyphs { let glyph_offset = prim_data.glyphs[glyph.index_in_text_run as usize].point + prim_header.local_rect.min.to_vector();
let transformed_offset = match glyph_transform.transform_point2d(glyph_offset) {
Some(transformed_offset) => transformed_offset,
None => {
use_tight_bounding_rect = false; break;
}
}; let raster_glyph_offset = (transformed_offset + snap_bias).floor(); let raster_text_offset = (
glyph_transform.transform_vector2d(text_offset) +
glyph_translation +
DeviceVector2D::new(0.5, 0.5)
).floor() - glyph_translation;
let intersected = match pic_bounding_rect { // The text run may have been clipped, for example if part of it is offscreen. // So intersect our result with the original bounding rect.
Some(rect) => rect.intersection(bounding_rect).unwrap_or_else(PictureRect::zero), // If space mapping went off the rails, fall back to the old behavior. //TODO: consider skipping the glyph run completely in this case.
None => *bounding_rect,
};
intersected
};
let key = BatchKey::new(kind, blend_mode, textures);
let batch = batcher.alpha_batch_list.set_params_and_get_batch(
key,
batch_features,
&tight_bounding_rect,
z_id,
);
// All yuv textures should be the same type. let buffer_kind = textures.colors[0].image_buffer_kind();
assert!(
textures.colors[1 .. yuv_image_data.format.get_plane_num()]
.iter()
.all(|&tid| buffer_kind == tid.image_buffer_kind())
);
let kind = BrushBatchKind::YuvImage(
buffer_kind,
yuv_image_data.format,
yuv_image_data.color_depth,
yuv_image_data.color_space,
yuv_image_data.color_range,
);
let image_data = &ctx.data_stores.image[data_handle].kind; let image_scratch = &ctx.scratch.frame.images[img_scratch_handle]; let visible_tiles = &ctx.scratch.frame.visible_image_tiles[image_scratch.visible_tiles]; let prim_user_data = ImageBrushUserData {
color_mode: ShaderColorMode::Image,
alpha_type: image_data.alpha_type,
raster_space: RasterizationSpace::Local,
opacity: 1.0,
}.encode();
let blend_mode = if needs_blending { match image_data.alpha_type {
AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
AlphaType::Alpha => BlendMode::Alpha,
}
} else {
BlendMode::None
};
if visible_tiles.is_empty() { if cfg!(debug_assertions) { match ctx.resource_cache.get_image_properties(image_data.key) {
Some(ImageProperties { tiling: None, .. }) | None => (),
other => panic!("Non-tiled image with no visible images detected! Properties {:?}", other),
}
}
let src_color = render_tasks.resolve_location(image_scratch.src_color);
let (uv_rect_address, texture_source) = match src_color {
Some(src) => src,
None => { return;
}
};
let batch_params = BrushBatchParameters::shared(
BrushBatchKind::Image(texture_source.image_buffer_kind()),
TextureSet::prim_textured(texture_source),
prim_user_data,
uv_rect_address.as_int(),
);
let (prim_cache_address, segments) = if prim_info.segment_instance_index == SegmentInstanceIndex::UNUSED {
(image_scratch.gpu_address, None)
} else { let segment_instance = &ctx.scratch.frame.segment_instances[prim_info.segment_instance_index]; let segments = Some(&ctx.scratch.frame.segments[segment_instance.segments_range]);
(segment_instance.gpu_data, segments)
};
let local_rect = image_scratch.adjustment.map_local_rect(&prim_rect); let local_clip_rect = image_scratch.tight_local_clip_rect
.intersection_unchecked(&local_rect);
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
// use temporary block storage since we don't know the number of visible tiles beforehand letmut gpu_blocks = Vec::<GpuBufferBlockF>::with_capacity(3 + max_tiles_per_header * 2); for chunk in visible_tiles.chunks(max_tiles_per_header) {
gpu_blocks.clear();
gpu_blocks.push(image_data.color.premultiplied().into()); //color
gpu_blocks.push(PremultipliedColorF::WHITE.into()); //bg color
gpu_blocks.push([-1.0, 0.0, 0.0, 0.0].into()); //stretch size // negative first value makes the shader code ignore it and use the local size instead for tile in chunk { let tile_rect = tile.local_rect.translate(-prim_rect.min.to_vector());
gpu_blocks.push(tile_rect.into());
gpu_blocks.push([0.0; 4].into());
}
letmut writer = gpu_buffer_builder.f32.write_blocks(gpu_blocks.len()); for block in &gpu_blocks {
writer.push_one(*block);
} let specific_prim_address = writer.finish();
let prim_header = PrimitiveHeader {
local_clip_rect: image_scratch.tight_local_clip_rect,
specific_prim_address: specific_prim_address.as_int(),
user_data: prim_user_data,
..base_prim_header
}; let prim_header_index = prim_headers.push(&prim_header);
for (i, tile) in chunk.iter().enumerate() { let (uv_rect_address, texture) = match render_tasks.resolve_location(tile.src_color) {
Some(result) => result,
None => { return;
}
};
let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
);
let batch_key = BatchKey {
blend_mode,
kind: BatchKind::Brush(BrushBatchKind::Image(texture.image_buffer_kind())),
textures,
};
self.add_brush_instance_to_batches(
batch_key,
batch_features,
bounding_rect,
z_id,
i as i32,
tile.edge_flags,
clip_task_address,
brush_flags | BrushFlags::SEGMENT_RELATIVE | BrushFlags::PERSPECTIVE_INTERPOLATION,
prim_header_index,
uv_rect_address.as_int(),
);
}
}
}
}
PrimitiveKind::LinearGradient { .. } => {
unreachable!("BUG: linear gradients should always use quad path");
}
PrimitiveKind::BackdropCapture { .. } => {}
PrimitiveKind::BackdropRender { .. } => { let scratch_handle = prim_info.kind_scratch.unwrap_backdrop_render(); let blend_mode = BlendMode::PremultipliedAlpha; let pic_task_id = Some(ctx.scratch.frame.backdrop_render[scratch_handle].src_task_id);
let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
prim_info.clip_task_index,
render_tasks,
).unwrap();
let kind = BatchKind::Brush(
BrushBatchKind::Image(ImageBufferKind::Texture2D)
); let (_, texture) = render_tasks.resolve_location(pic_task_id).unwrap(); let textures = BatchTextures::prim_textured(
texture,
clip_mask_texture_id,
); let key = BatchKey::new(
kind,
blend_mode,
textures,
);
let pic_task = &render_tasks[pic_task_id.unwrap()]; let pic_info = match pic_task.kind {
RenderTaskKind::Picture(ref info) => info,
_ => panic!("bug: not a picture"),
}; let target_rect = pic_task.get_target_rect();
let backdrop_rect = DeviceRect::from_origin_and_size(
pic_info.content_origin,
target_rect.size().to_f32(),
);
let map_prim_to_backdrop = SpaceMapper::new_with_target(
pic_info.surface_spatial_node_index,
prim_spatial_node_index,
WorldRect::max_rect(),
ctx.spatial_tree,
);
let points = [
map_prim_to_backdrop.map_point(prim_rect.top_left()),
map_prim_to_backdrop.map_point(prim_rect.top_right()),
map_prim_to_backdrop.map_point(prim_rect.bottom_left()),
map_prim_to_backdrop.map_point(prim_rect.bottom_right()),
];
let uv_rect_handle = source.write_gpu_blocks(&mut gpu_buffer_builder.f32); let uv_rect_address = gpu_buffer_builder.f32.resolve_handle(uv_rect_handle);
/// Draw a (potentially masked) alpha cutout so that a video underlay will be blended /// through by the compositor fn add_compositor_surface_cutout(
&mutself,
prim_rect: LayoutRect,
local_clip_rect: LayoutRect,
clip_task_index: ClipTaskIndex,
transform_id: GpuTransformId,
z_id: ZBufferId,
bounding_rect: &PictureRect,
ctx: &RenderTargetContext,
render_tasks: &RenderTaskGraph,
prim_headers: &mut PrimitiveHeaders,
) { let (clip_task_address, clip_mask_texture_id) = ctx.get_prim_clip_task_and_texture(
clip_task_index,
render_tasks,
).unwrap();
/// Add a single segment instance to a batch. /// /// `edge_aa_mask` Specifies the edges that are *allowed* to have anti-aliasing, if and only /// if the segments enable it. /// In other words passing EdgeAaSegmentFlags::all() does not necessarily mean all edges will /// be anti-aliased, only that they could be. fn add_segment_to_batch(
&mutself,
segment: &BrushSegment,
segment_data: &SegmentInstanceData,
segment_index: i32,
batch_kind: BrushBatchKind,
prim_header_index: PrimitiveHeaderIndex,
alpha_blend_mode: BlendMode,
features: BatchFeatures,
brush_flags: BrushFlags,
edge_aa_mask: EdgeMask,
bounding_rect: &PictureRect,
transform_metadata: TransformMetadata,
z_id: ZBufferId,
prim_opacity: PrimitiveOpacity,
clip_task_index: ClipTaskIndex,
ctx: &RenderTargetContext,
render_tasks: &RenderTaskGraph,
) {
debug_assert!(clip_task_index != ClipTaskIndex::INVALID);
// Get GPU address of clip task for this segment, or None if // the entire segment is clipped out. iflet Some((clip_task_address, clip_mask)) = ctx.get_clip_task_and_texture(
clip_task_index,
segment_index,
render_tasks,
) { // If a got a valid (or OPAQUE) clip task address, add the segment. let is_inner = segment.edge_flags.is_empty(); let needs_blending = !prim_opacity.is_opaque ||
clip_task_address != OPAQUE_TASK_ADDRESS ||
(!is_inner && !transform_metadata.is_2d_axis_aligned) ||
brush_flags.contains(BrushFlags::FORCE_AA);
let textures = BatchTextures {
input: segment_data.textures,
clip_mask,
};
let batch_key = BatchKey {
blend_mode: if needs_blending { alpha_blend_mode } else { BlendMode::None },
kind: BatchKind::Brush(batch_kind),
textures,
};
/// Add any segment(s) from a brush to batches. /// /// `edge_aa_mask` Specifies the edges that are *allowed* to have anti-aliasing, if and only /// if the segments enable it. /// In other words passing EdgeAaSegmentFlags::all() does not necessarily mean all edges will /// be anti-aliased, only that they could be. fn add_segmented_prim_to_batch(
&mutself,
brush_segments: Option<&[BrushSegment]>,
prim_opacity: PrimitiveOpacity,
params: &BrushBatchParameters,
blend_mode: BlendMode,
features: BatchFeatures,
brush_flags: BrushFlags,
edge_aa_mask: EdgeMask,
prim_header_index: PrimitiveHeaderIndex,
bounding_rect: &PictureRect,
transform_metadata: TransformMetadata,
z_id: ZBufferId,
clip_task_index: ClipTaskIndex,
ctx: &RenderTargetContext,
render_tasks: &RenderTaskGraph,
) { match (brush_segments, ¶ms.segment_data) {
(Some(ref brush_segments), SegmentDataKind::Instanced(ref segment_data)) => { // In this case, we have both a list of segments, and a list of // per-segment instance data. Zip them together to build batches.
debug_assert_eq!(brush_segments.len(), segment_data.len()); for (segment_index, (segment, segment_data)) in brush_segments
.iter()
.zip(segment_data.iter())
.enumerate()
{ self.add_segment_to_batch(
segment,
segment_data,
segment_index as i32,
params.batch_kind,
prim_header_index,
blend_mode,
features,
brush_flags,
edge_aa_mask,
bounding_rect,
transform_metadata,
z_id,
prim_opacity,
clip_task_index,
ctx,
render_tasks,
);
}
}
(Some(ref brush_segments), SegmentDataKind::Shared(ref segment_data)) => { // A list of segments, but the per-segment data is common // between all segments. for (segment_index, segment) in brush_segments
.iter()
.enumerate()
{ self.add_segment_to_batch(
segment,
segment_data,
segment_index as i32,
params.batch_kind,
prim_header_index,
blend_mode,
features,
brush_flags,
edge_aa_mask,
bounding_rect,
transform_metadata,
z_id,
prim_opacity,
clip_task_index,
ctx,
render_tasks,
);
}
}
(None, SegmentDataKind::Shared(ref segment_data)) => {
// No segments, and thus no per-segment instance data.
// Note: the blend mode already takes opacity into account
let (clip_task_address, clip_mask) = ctx.get_prim_clip_task_and_texture(
clip_task_index,
render_tasks,
).unwrap();
let textures = BatchTextures {
input: segment_data.textures,
clip_mask,
};
let batch_key = BatchKey {
blend_mode,
kind: BatchKind::Brush(params.batch_kind),
textures,
};
self.add_brush_instance_to_batches(
batch_key,
features,
bounding_rect,
z_id,
INVALID_SEGMENT_INDEX,
edge_aa_mask,
clip_task_address,
brush_flags | BrushFlags::PERSPECTIVE_INTERPOLATION,
prim_header_index,
segment_data.specific_resource_address,
);
}
(None, SegmentDataKind::Instanced(..)) => {
// We should never hit the case where there are no segments,
// but a list of segment instance data.
unreachable!();
}
}
}
}
/// Either a single texture / user data for all segments,
/// or a list of one per segment.
enum SegmentDataKind {
Shared(SegmentInstanceData),
Instanced(SmallVec<[SegmentInstanceData; 8]>),
}
/// The parameters that are specific to a kind of brush,
/// used by the common method to add a brush to batches.
struct BrushBatchParameters {
batch_kind: BrushBatchKind,
prim_user_data: [i32; 4],
segment_data: SegmentDataKind,
}
impl BrushBatchParameters {
/// This brush instance has a list of per-segment
/// instance data.
fn instanced(
batch_kind: BrushBatchKind,
prim_user_data: [i32; 4],
segment_data: SmallVec<[SegmentInstanceData; 8]>,
) -> Self {
BrushBatchParameters {
batch_kind,
prim_user_data,
segment_data: SegmentDataKind::Instanced(segment_data),
}
}
/// This brush instance shares the per-segment data
/// across all segments.
fn shared(
batch_kind: BrushBatchKind,
textures: TextureSet,
prim_user_data: [i32; 4],
specific_resource_address: i32,
) -> Self {
BrushBatchParameters {
batch_kind,
prim_user_data,
segment_data: SegmentDataKind::Shared(
SegmentInstanceData {
textures,
specific_resource_address,
}
),
}
}
}
/// A list of clip instances to be drawn into a target.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipMaskInstanceList {
pub mask_instances_fast: FrameVec<MaskInstance>,
pub mask_instances_slow: FrameVec<MaskInstance>,
pub fn is_empty(&self) -> bool {
// Destructure self to make sure we don't forget to update this method if
// a new member is added.
let ClipMaskInstanceList {
mask_instances_fast,
mask_instances_slow,
mask_instances_fast_with_scissor,
mask_instances_slow_with_scissor,
image_mask_instances,
image_mask_instances_with_scissor,
} = self;
/// A list of clip instances to be drawn into a target.
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipBatchList {
/// Rectangle draws fill up the rectangles with rounded corners.
pub slow_rectangles: FrameVec<ClipMaskInstanceRect>,
pub fast_rectangles: FrameVec<ClipMaskInstanceRect>,
}
/// Batcher managing draw calls into the clip mask (in the RT cache).
#[derive(Debug)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct ClipBatcher {
/// The first clip in each clip task. This will overwrite all pixels
/// in the clip region, so we can skip doing a clear and write with
/// blending disabled, which is a big performance win on Intel GPUs.
pub primary_clips: ClipBatchList,
/// Any subsequent clip masks (rare) for a clip task get drawn in
/// a second pass with multiplicative blending enabled.
pub secondary_clips: ClipBatchList,
/// Where appropriate, draw a clip rectangle as a small series of tiles,
/// instead of one large rectangle.
fn add_tiled_clip_mask(
&mut self,
mask_screen_rect: DeviceRect,
local_clip_rect: LayoutRect,
clip_spatial_node_index: SpatialNodeIndex,
spatial_tree: &SpatialTree,
world_rect: &WorldRect,
global_device_pixel_scale: DevicePixelScale,
common: &ClipMaskInstanceCommon,
is_first_clip: bool,
) -> bool {
// Only try to draw in tiles if the clip mark is big enough.
if mask_screen_rect.area() < CLIP_RECTANGLE_AREA_THRESHOLD {
return false;
}
let mask_screen_rect_size = mask_screen_rect.size().to_i32();
let clip_spatial_node = spatial_tree.get_spatial_node(clip_spatial_node_index);
// Only support clips that are axis-aligned to the root coordinate space,
// for now, to simplify the logic below. This handles the vast majority
// of real world cases, but could be expanded in future if needed.
if clip_spatial_node.coordinate_system_id != CoordinateSystemId::root() {
return false;
}
// Get the world rect of the clip rectangle. If we can't transform it due
// to the matrix, just fall back to drawing the entire clip mask.
let transform = spatial_tree.get_world_transform(
clip_spatial_node_index,
);
let world_clip_rect = match project_rect(
&transform.into_transform(),
&local_clip_rect,
&world_rect,
) {
Some(rect) => rect,
None => return false,
};
// Work out how many tiles to draw this clip mask in, stretched across the
// device rect of the primitive clip mask.
let world_device_rect = world_clip_rect * global_device_pixel_scale;
let x_tiles = (mask_screen_rect_size.width + CLIP_RECTANGLE_TILE_SIZE-1) / CLIP_RECTANGLE_TILE_SIZE;
let y_tiles = (mask_screen_rect_size.height + CLIP_RECTANGLE_TILE_SIZE-1) / CLIP_RECTANGLE_TILE_SIZE;
// Because we only run this code path for axis-aligned rects (the root coord system check above),
// and only for rectangles (not rounded etc), the world_device_rect is not conservative - we know
// that there is no inner_rect, and the world_device_rect should be the real, axis-aligned clip rect.
let mask_origin = mask_screen_rect.min.to_vector();
let clip_list = self.get_batch_list(is_first_clip);
for y in 0 .. y_tiles {
for x in 0 .. x_tiles {
let p0 = DeviceIntPoint::new(
x * CLIP_RECTANGLE_TILE_SIZE,
y * CLIP_RECTANGLE_TILE_SIZE,
);
let p1 = DeviceIntPoint::new(
(p0.x + CLIP_RECTANGLE_TILE_SIZE).min(mask_screen_rect_size.width),
(p0.y + CLIP_RECTANGLE_TILE_SIZE).min(mask_screen_rect_size.height),
);
let normalized_sub_rect = DeviceIntRect {
min: p0,
max: p1,
}.to_f32();
let world_sub_rect = normalized_sub_rect.translate(mask_origin);
// If the clip rect completely contains this tile rect, then drawing
// these pixels would be redundant - since this clip can't possibly
// affect the pixels in this tile, skip them!
if !world_device_rect.contains_box(&world_sub_rect) {
clip_list.slow_rectangles.push(ClipMaskInstanceRect {
common: ClipMaskInstanceCommon {
sub_rect: normalized_sub_rect,
..*common
},
local_pos: local_clip_rect.min,
clip_data: ClipData::uniform(local_clip_rect.size(), 0.0, ClipMode::Clip),
});
}
}
}
true
}
/// Retrieve the correct clip batch list to append to, depending
/// on whether this is the first clip mask for a clip task.
fn get_batch_list(
&mut self,
is_first_clip: bool,
) -> &mut ClipBatchList {
if is_first_clip && !self.gpu_supports_fast_clears {
&mut self.primary_clips
} else {
&mut self.secondary_clips
}
}
for i in 0 .. clip_node_range.count {
let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i);
let clip_node = &ctx.data_stores.clip[clip_instance.handle];
let clip_transform_id = transforms.gpu.get_id(
clip_instance.spatial_node_index,
ctx.root_spatial_node_index,
ctx.spatial_tree,
);
let prim_transform_id = transforms.gpu.get_id(
root_spatial_node_index,
ctx.root_spatial_node_index,
ctx.spatial_tree,
);
let common = ClipMaskInstanceCommon {
sub_rect: DeviceRect::from_size(actual_rect.size()),
task_origin,
screen_origin,
device_pixel_scale: surface_device_pixel_scale.0,
clip_transform_id,
prim_transform_id,
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.