/* 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/. */
for cluster in &mut prim_list.clusters { if !cluster.flags.contains(ClusterFlags::IS_VISIBLE) { continue;
}
profile_scope!("cluster");
pic_state.map_local_to_pic.set_target_spatial_node(
cluster.spatial_node_index,
frame_context.spatial_tree,
);
let device_pixel_scale = frame_state.surfaces[pic_context.surface_index.0].device_pixel_scale;
quad_transform.set(
cluster.spatial_node_index,
pic_context.raster_spatial_node_index,
frame_context.spatial_tree,
device_pixel_scale,
);
for prim_instance_index in cluster.prim_range() { if frame_state.surface_builder.get_cmd_buffer_targets_for_prim(
&scratch.frame.draws[prim_instance_index],
&mut cmd_buffer_targets,
) { let plane_split_anchor = PlaneSplitAnchor::new(
cluster.spatial_node_index,
PrimitiveInstanceIndex(prim_instance_index as u32),
);
// TODO(gw): Technically no need to clear visibility here, since from this point it // only matters if it got added to a command buffer. Kept here for now to // make debugging simpler, but perhaps we can remove / tidy this up.
scratch.frame.draws[prim_instance_index].reset();
}
}
}
for i in0 .. clip_chain.clips_range.count { let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, i); let clip_node = &data_stores.clip[clip_instance.handle];
match clip_node.item.kind {
ClipItemKind::RoundedRectangle { .. } | ClipItemKind::Rectangle { .. } => {}
ClipItemKind::Image { .. } => {
panic!("bug: image-masks not expected on rect/quads");
}
}
}
// If we have dependencies, we need to prepare them first, in order // to know the actual rect of this primitive. // For example, scrolling may affect the location of an item in // local space, which may force us to render this item on a larger // picture target, if being composited. letmut is_passthrough = false; iflet PrimitiveKind::Picture { pic_index, .. } = prim_instances[prim_instance_index].kind { let Some(scratch_handle) = prepare_picture(
pic_index,
store,
Some(pic_context.surface_index),
pic_context.subpixel_mode,
frame_context,
frame_state,
data_stores,
scratch,
tile_caches,
prim_instances,
) else { return;
};
// In this initial patch, we only support non-masked primitives through the new // quad rendering path. Follow up patches will extend this to support masks, and // then use by other primitives. In the new quad rendering path, we'll still want // to skip the entry point to `update_clip_task` as that does old-style segmenting // and mask generation. let should_update_clip_task = match &mut prim_instance.kind {
PrimitiveKind::Rectangle { .. }
| PrimitiveKind::RadialGradient { .. }
| PrimitiveKind::ConicGradient { .. }
| PrimitiveKind::LinearGradient { .. }
| PrimitiveKind::Image { .. }
| PrimitiveKind::YuvImage { .. }
| PrimitiveKind::NormalBorder { .. }
| PrimitiveKind::LineDecoration { .. }
=> {
use_legacy_path |= !can_use_clip_chain_for_quad_path(
&scratch.frame.draws[prim_instance_index].clip_chain,
frame_state.clip_store,
data_stores,
);
/// Prepare an interned primitive for rendering, by requesting /// resources, render tasks etc. This is equivalent to the /// prepare_prim_for_render_inner call for old style primitives. fn prepare_interned_prim_for_render(
store: &mut PrimitiveStore,
use_legacy_path: bool,
prim_instance_index: PrimitiveInstanceIndex,
prim_instance: &mut PrimitiveInstance,
cluster: &mut PrimitiveCluster,
plane_split_anchor: PlaneSplitAnchor,
quad_transform: &mut QuadTransformState,
pic_context: &PictureContext,
pic_state: &mut PictureState,
frame_context: &FrameBuildingContext,
frame_state: &mut FrameBuildingState,
data_stores: &mut DataStores,
scratch: &mut PrimitiveScratchBuffer,
targets: &[CommandBufferIndex],
) { let prim_spatial_node_index = cluster.spatial_node_index; let device_pixel_scale = frame_state.surfaces[pic_context.surface_index.0].device_pixel_scale; // Snapshot of the per-frame draw header for this prim. Copy is fine here // because the only field this function writes (clip_task_index, in the // segmented-clip path) isn't read again in this function — and the other // fields (state, clip_chain) aren't written by it. let prim_info = scratch.frame.draws[prim_instance_index.0as usize];
let prim_data = &data_stores.box_shadow[*data_handle]; let shadow_data = &prim_data.kind; let blur_radius = shadow_data.blur_radius;
// Build snapped element/inner/outer rects. The shader expects // `inner = element.translate(offset).inflate(spread)` and // `outer = inner.inflate(blur_offset)`, with element snapped to // the device pixel grid. Because the inflations can have // fractional components, snapping the prim's whole rect and // then deflating is not equivalent to snapping the element rect // directly, so we always snap the element rect itself and // re-inflate. // // The element rect's relation to the per-instance // `unsnapped_prim_rect` differs by clip_mode (set up in // `box_shadow::add_box_shadow`): // - Outset: prim rect = element.translate.inflate(spread) // .inflate(blur_offset); // recover element by reversing the construction. // - Inset: prim rect = element directly. let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil(); let unsnapped_element_rect = match shadow_data.clip_mode {
BoxShadowClipMode::Outset => prim_instance.unsnapped_prim_rect
.inflate(-blur_offset, -blur_offset)
.inflate(-shadow_data.spread_amount, -shadow_data.spread_amount)
.translate(-shadow_data.box_offset),
BoxShadowClipMode::Inset => prim_instance.unsnapped_prim_rect,
}; let element_rect = { // Snap into the prim's surface raster space, matching how the // prim's own rect was snapped in the visibility pass. letmut snapper = SpaceSnapper::new(
&frame_state.surfaces[pic_context.surface_index.0],
frame_context.spatial_tree,
);
snapper.set_target_spatial_node(prim_spatial_node_index, frame_context.spatial_tree);
snapper.snap_rect(&unsnapped_element_rect)
}; let inner_shadow_rect = element_rect
.translate(shadow_data.box_offset)
.inflate(shadow_data.spread_amount, shadow_data.spread_amount); let outer_shadow_rect = inner_shadow_rect.inflate(blur_offset, blur_offset); // The shader-facing prim rect mirrors the (re-derived) outer for // Outset and the element for Inset — i.e. whichever rect the // scene-build path originally registered as `info.rect`. This is // what the rest of this block, plus `prepare_quad` below, expects // as the prim local-space rect. let prim_rect = match shadow_data.clip_mode {
BoxShadowClipMode::Outset => outer_shadow_rect,
BoxShadowClipMode::Inset => element_rect,
};
// Compute the nine-patch source rect size per axis (= min_shadow_rect_size when // the shadow is large enough to stretch, = shadow_rect_size when corners overlap). let src_rect_size = LayoutSize::new( if shadow_rect_size.width >= min_shadow_rect_size.width {
min_shadow_rect_size.width
} else {
shadow_rect_size.width
}, if shadow_rect_size.height >= min_shadow_rect_size.height {
min_shadow_rect_size.height
} else {
shadow_rect_size.height
},
);
// The full blur alloc size in local pixels. This is the UV denominator passed to // the shader: the nine-patch maps shadow_pos/alloc_size so that shadow_pos=blur_region // maps exactly to the shadow edge in the texture (preserving the blur falloff). let shadow_rect_alloc_size = LayoutSize::new( 2.0 * blur_region + src_rect_size.width, 2.0 * blur_region + src_rect_size.height,
);
// Scale to device pixels for the render task. let blur_radius_dp = blur_radius * 0.5; letmut content_scale = LayoutToWorldScale::new(1.0) * device_pixel_scale;
content_scale.0 = clamp_to_scale_factor(content_scale.0, false);
// Opt B: pre-reduce content_scale so the blur sigma is already within // MAX_BLUR_STD_DEVIATION, eliminating downscale passes inside new_blur. // // Use the same rounding as the old code (round to nearest integer) to determine // n_downscales, so mask scale exactly matches what old new_blur downscaling would // have produced. Exception: if rounded sigma is 0 (tiny sigma from to_cache_size // downscaling), use the float sigma to avoid a zero-blur regression. let sigma_rounded = (blur_radius_dp * content_scale.0).round(); let sigma_for_n = if sigma_rounded == 0.0 { blur_radius_dp * content_scale.0 } else { sigma_rounded }; let n_downscales = if sigma_for_n > MAX_BLUR_STD_DEVIATION {
(sigma_for_n / MAX_BLUR_STD_DEVIATION).log2().ceil() as u32
} else { 0
};
content_scale.0 /= (1u32 << n_downscales) as f32;
// Safety cap: reduces content_scale further only for pathological // small-blur-huge-element cases where the alloc would exceed the max task size. let cache_size = to_cache_size(shadow_rect_alloc_size, &mut content_scale);
// Blur sigma to pass to new_blur. Use the same rounded value as the old code // (now divided by 2^n instead of being halved inside new_blur), so the blur // intensity is byte-for-byte identical to the old pipeline. let blur_std_dev = if sigma_rounded == 0.0 {
blur_radius_dp * content_scale.0
} else {
sigma_rounded / (1u32 << n_downscales) as f32
};
debug_assert!(
blur_std_dev <= MAX_BLUR_STD_DEVIATION + 1e-3, "BoxShadow sigma {blur_std_dev} exceeds MAX_BLUR_STD_DEVIATION after Opt B \
(n_downscales={n_downscales}, content_scale={})",
content_scale.0,
);
let clip_data = ClipData::rounded_rect(
src_rect_size,
&shadow_radius,
ClipMode::Clip,
);
// The shadow shape is offset by blur_region within the alloc task (local pixels). // device_pixel_scale_for_task scales it to the mask resolution. let minimal_shadow_rect_origin = LayoutPoint::new(blur_region, blur_region); let device_pixel_scale_for_task = DevicePixelScale::new(content_scale.0);
// Compensate for the rounding `create_quad_primitive` applies to // prim_rect when `aa_flags` is empty: the shader receives the // rounded p0 as `local_prim_rect.p0` (after the round-trip through // device space and back via `pattern_scale_offset`) and // reconstructs absolute positions via `local_prim_rect.p0 + // offset`. Computing offsets against the un-rounded p0 mismatches // by up to half a device pixel and produces a one-pixel seam on // trailing edges (bug 2035734). The round must be done in device // space to match `create_quad_primitive` for non-identity // transforms (e.g. Gecko at 125% display scaling). let prim_min_rounded = match quad_transform.as_2d_scale_offset() {
Some(local_to_device) => { // Use Point2D::round (euclid's Round trait, defined as // (n+0.5).floor()) to match what create_quad_primitive // uses on the rendered quad bounds. f32::round here would // round half-away-from-zero and disagree at negative // half-integer device-x values, causing a 1-pixel shift // when the shader reconstructs dest_rect.min as // local_prim_rect.p0 + dest_rect_offset. let dev: DevicePoint = local_to_device.map_point(&prim_rect.min);
local_to_device.unmap_point::<DevicePixel, LayoutPixel>(&dev.round())
}
None => prim_rect.min,
};
// For outset, prim_rect == dest_rect so offset is zero. // For inset, prim_rect is the element rect; dest_rect (outer_shadow_rect) // may be offset and smaller, so we pass its size and offset separately. let dest_rect = outer_shadow_rect; let dest_rect_offset = LayoutVector2D::new(
dest_rect.min.x - prim_min_rounded.x,
dest_rect.min.y - prim_min_rounded.y,
); let dest_rect_size = dest_rect.size();
let prim_data = &data_stores.text_run[*data_handle];
// The transform has to match the prim -> raster transform applied // by "ps_text_run" via `transform.m` + `device_pixel_scale`. // `request_resources` uses it to map glyph pen positions into // absolute device space for snapping. let transform = frame_context.spatial_tree
.get_relative_transform(
prim_spatial_node_index,
pic_context.raster_spatial_node_index,
)
.into_fast_transform();
// The run anchor is the normalized prim rect origin; glyph // positions in the template are stored relative to it. Use the // unsnapped rect so the anchor matches what the shader receives in // `PrimitiveHeader.local_rect`. let local_rect = prim_instance.unsnapped_prim_rect;
let surface = &frame_state.surfaces[pic_context.surface_index.0];
// If subpixel AA is disabled due to the backing surface the glyphs // are being drawn onto, disable it (unless we are using the // specifial subpixel mode that estimates background color). let allow_subpixel = match prim_info.state {
DrawState::Culled |
DrawState::Unset |
DrawState::PassThrough => {
panic!("bug: invalid visibility state");
}
DrawState::Visible { sub_slice_index, .. } => { // For now, we only allow subpixel AA on primary sub-slices. In future we // may support other sub-slices if we find content that does this. if sub_slice_index.is_primary() { match pic_context.subpixel_mode {
SubpixelMode::Allow => true,
SubpixelMode::Deny => false,
SubpixelMode::Conditional { allowed_rect, prohibited_rect } => { // Conditional mode allows subpixel AA to be enabled for this // text run, so long as it's inside the allowed rect.
allowed_rect.contains_box(&prim_info.clip_chain.pic_coverage_rect) &&
!prohibited_rect.intersects(&prim_info.clip_chain.pic_coverage_rect)
}
}
} else { false
}
}
};
let text_run_handle = prim_data.request_resources(
local_rect,
&transform.to_transform().with_destination::<_>(),
surface,
prim_spatial_node_index,
allow_subpixel,
frame_context.fb_config.low_quality_pinch_zoom,
frame_state.resource_cache,
&mut frame_state.frame_gpu_data.f32,
frame_context.spatial_tree,
scratch,
);
scratch.frame.draws[prim_instance_index.0as usize].kind_scratch =
KindScratchHandle::TextRun(text_run_handle);
}
PrimitiveKind::NormalBorder { data_handle } => {
profile_scope!("NormalBorder"); let prim_data = &mut data_stores.normal_border[*data_handle]; let aligned_aa_edges = prim_data.common.aligned_aa_edges; let transformed_aa_edges = prim_data.common.transformed_aa_edges; let common_data = &mut prim_data.common; let border_data = &mut prim_data.kind;
// The per-frame brush + border segments and task-id slot // were allocated in prepare_prim_for_render before // update_clip_task; the kind_scratch handle on this prim's // PrimitiveDrawHeader points to the NormalBorderScratch. let nb_handle = scratch.frame.draws[prim_instance_index.0as usize]
.kind_scratch
.unwrap_normal_border(); let nb_scratch = scratch.frame.normal_border[nb_handle];
// Hold split borrows on distinct fields of scratch.frame so // we can pass the border_segments slice and the task_ids // mutable slice into update() without copying either out. let PrimitiveFrameScratch { ref border_segments, refmut border_task_ids,
..
} = scratch.frame;
border_data.update(
&border_segments[nb_scratch.border_segments_range],
prim_spatial_node_index,
device_pixel_scale,
frame_context,
frame_state,
&mut border_task_ids[nb_scratch.task_ids],
);
if !use_legacy_path { let offset = prim_info.snapped_local_rect.min.to_vector(); // TODO: as soon as the legacy path is removed we can remove the scratch handles // and hoops we get through to access them here. let task_ids: SmallVec<[RenderTaskId; 8]> = SmallVec::from_slice(
&scratch.frame.border_task_ids[nb_scratch.task_ids],
); let brush_segments: SmallVec<[BrushSegment; 8]> =
scratch.frame.segments[nb_scratch.brush_segments_range]
.iter()
.cloned()
.collect(); for (task_id, segment) in task_ids.iter().zip(brush_segments.iter()) { let pattern = ImagePattern {
src_task_id: *task_id,
src_is_opaque: false,
premultiplied: true,
sampler_kind: api::ImageBufferKind::Texture2D,
color: ColorF::WHITE,
};
// TODO: Dealing with brush flags and more generally brush segments here // is awkward. We'll be able to clean this up once the brush code path // is removed. let flags = segment.brush_flags; let repeat_x = if flags.contains(BrushFlags::SEGMENT_REPEAT_X_ROUND) {
RepeatMode::Round
} elseif flags.contains(BrushFlags::SEGMENT_REPEAT_X) {
RepeatMode::Repeat
} else {
RepeatMode::Stretch
};
let repeat_y = if flags.contains(BrushFlags::SEGMENT_REPEAT_Y_ROUND) {
RepeatMode::Round
} elseif flags.contains(BrushFlags::SEGMENT_REPEAT_Y) {
RepeatMode::Repeat
} else {
RepeatMode::Stretch
};
let src_size = frame_state.rg_builder
.get_task(*task_id)
.get_target_size()
.to_f32();
// Corner segments have SEGMENT_TEXEL_RECT set. In that case the // source render task contains the full corner texture (image_rect) // while segment.local_rect is only the visible (non-overlapping) // part. extra_data carries the normalized texture sub-rect // that segment.local_rect maps to. Reconstruct image_rect so // the texture is drawn at its natural size, and clip the // output to segment.local_rect. if flags.contains(BrushFlags::SEGMENT_TEXEL_RECT) { let tex_rect = segment.extra_data; let tex_w = tex_rect[2] - tex_rect[0]; let tex_h = tex_rect[3] - tex_rect[1]; if tex_w > 0.0 && tex_h > 0.0 { let image_size = LayoutSize::new(
segment_local_rect.width() / tex_w,
segment_local_rect.height() / tex_h,
); let image_min = LayoutPoint::new(
segment_local_rect.min.x - tex_rect[0] * image_size.width,
segment_local_rect.min.y - tex_rect[1] * image_size.height,
);
local_clip_rect = local_clip_rect
.intersection(&segment_local_rect)
.unwrap_or(LayoutRect::zero());
segment_local_rect = LayoutRect::from_origin_and_size(
image_min,
image_size,
);
}
}
// The positioning and size of the dashesdots is not specified // but browsers are encouraged to make the pattern symetrical. // One way to do this is to apply the repeat offset computed // by compute_border_repetition. However the pattern that we // are repeating is meant to be instead stretched to so that // an integer number of repetitions fills the space.
if repeat_x == RepeatMode::Repeat { let w = segment_local_rect.width(); let sw = stretch_size.width; let scale = w / ((w / sw).round() * sw);
stretch_size.width *= scale;
}
if repeat_y == RepeatMode::Repeat { let h = segment_local_rect.height(); let sh = stretch_size.height; let scale = h / ((h / sh).round() * sh);
let brush_segments = &scratch.frame.segments[nb_scratch.brush_segments_range]; let gpu_address = border_data.write_brush_gpu_blocks(
common_data,
prim_info.snapped_local_rect.size(),
brush_segments,
frame_state,
);
scratch.frame.normal_border[nb_handle].gpu_address = gpu_address;
}
PrimitiveKind::ImageBorder { data_handle, .. } => {
profile_scope!("ImageBorder"); let prim_data = &mut data_stores.image_border[*data_handle];
// The per-frame brush segments were allocated in // prepare_prim_for_render before update_clip_task; the // kind_scratch handle on this prim's PrimitiveDrawHeader // points to the ImageBorderScratch. let ib_handle = scratch.frame.draws[prim_instance_index.0as usize]
.kind_scratch
.unwrap_image_border(); let brush_segments_range =
scratch.frame.image_border[ib_handle].brush_segments_range; let brush_segments = &scratch.frame.segments[brush_segments_range];
// Update the template this instance references, which may refresh the GPU // cache with any shared template data. let gpu_address = prim_data.kind.update(
&mut prim_data.common,
prim_info.snapped_local_rect.size(),
brush_segments,
frame_state,
);
scratch.frame.image_border[ib_handle].gpu_address = gpu_address;
}
PrimitiveKind::Rectangle { data_handle, .. } => {
profile_scope!("Rectangle");
if use_legacy_path { let prim_data = &mut data_stores.prim[*data_handle];
// Update the template this instane references, which may refresh the GPU // cache with any shared template data.
prim_data.update(
frame_state,
frame_context.scene_properties,
);
write_segment(
prim_info.segment_instance_index,
frame_state,
&mut scratch.frame.segments,
&mut scratch.frame.segment_instances,
|request| {
request.push_one(frame_context.scene_properties.resolve_color(&prim_data.kind.color).premultiplied());
}
);
} else { let prim_data = &data_stores.prim[*data_handle]; let prim_rect = prim_info.snapped_local_rect; let color = prim_data.resolve(frame_context.scene_properties);
// Update the template this instane references, which may refresh the GPU // cache with any shared template data.
yuv_image_data.update(
common_data,
prim_info.compositor_surface_kind.is_composited(),
frame_state,
);
// Update the template this instance references, which may refresh the GPU // cache with any shared template data. let img_scratch_handle = image_data.update(
common_data,
prim_instance_index,
prim_spatial_node_index,
frame_state,
frame_context,
prim_info.snapped_local_rect,
scratch,
);
scratch.frame.draws[prim_instance_index.0as usize].kind_scratch =
KindScratchHandle::Image(img_scratch_handle); let image_adjustment = scratch.frame.images[img_scratch_handle].adjustment; let effective_stretch_size =
image_data.stretch_size.resolve(&prim_info.snapped_local_rect);
// Fast-path: axis-aligned non-repeating gradients with multiple // stops decompose into per-segment two-stop quads so the GPU can // take the `sample_gradient_stops_fast` shader path. The // decomposition runs at frame-build (against the snapped prim // rect) so adjacent segments tile end-to-end at the snapped // outer-prim grid, even when the frame-time snap pass nudges // the outer rect at fractional DPR. // // `create_linear_gradient_prim` canonicalises the stored // start/end by swapping them when the original gradient line // ran "backwards" (and recording that in `reverse_stops`). // `LinearGradientTemplate::build` swaps them back at render // time; we have to do the same here so the decomposition sees // the gecko-original gradient orientation -- otherwise the // segment loop produces a gradient with stops in reverse // order (e.g. `linear-gradient(to top, red, blue)` rendering // as red-on-top instead of red-on-bottom). let (effective_start, effective_end) = if prim_data.reverse_stops {
(prim_data.end_point, prim_data.start_point)
} else {
(prim_data.start_point, prim_data.end_point)
}; if linear_gradient_decomposes(
&prim_rect,
stretch_size,
prim_data.tile_spacing,
effective_start,
effective_end,
prim_data.extend_mode,
&prim_data.stops,
frame_context.fb_config.enable_dithering,
) {
decompose_axis_aligned_gradient(
&prim_rect,
stretch_size,
effective_start,
effective_end,
&prim_data.stops,
&prim_info.clip_chain.local_clip_rect,
|seg_rect, seg_start, seg_end, seg_stops, edge_aa_mask| { let pattern = LinearGradientSegmentPattern {
start: seg_start,
end: seg_end,
stops: seg_stops,
};
quad::prepare_quad(
&pattern,
seg_rect,
&prim_info.clip_chain.local_clip_rect,
EdgeMask::empty(),
edge_aa_mask,
prim_instance_index,
&None,
&prim_info.clip_chain,
quad_transform,
frame_context,
pic_context,
targets,
&data_stores.clip,
frame_state,
scratch,
);
},
); return;
}
// For SWGL, evaluating the gradient is faster than reading from the texture cache. letmut should_cache = !frame_context.fb_config.is_software
&& frame_state.resource_cache.texture_cache.allocated_color_bytes() < 10_000_000; if should_cache { let surface = &frame_state.surfaces[pic_context.surface_index.0]; let clipped_surface_rect = surface.get_surface_rect(
&prim_info.clip_chain.pic_coverage_rect,
frame_context.spatial_tree,
);
// Conic gradients are quite slow with SWGL, so we want to cache // them as much as we can, even large ones. // TODO: get_surface_rect is not always cheap. We should reorganize // the code so that we only call it as much as we really need it, // while avoiding this much boilerplate for each primitive that uses // caching. letmut should_cache = frame_context.fb_config.is_software
&& frame_state.resource_cache.texture_cache.allocated_color_bytes() < 30_000_000; if should_cache { let surface = &frame_state.surfaces[pic_context.surface_index.0]; let clipped_surface_rect = surface.get_surface_rect(
&prim_info.clip_chain.pic_coverage_rect,
frame_context.spatial_tree,
);
if prim_info.clip_chain.needs_mask { // TODO(gw): Much of the code in this branch could be moved in to a common // function as we move more primitives to the new clip-mask paths.
// We are going to split the clip mask tasks in to a list to be rendered // on the source picture, and those to be rendered in to a mask for // compositing the picture in to the target. letmut source_masks = Vec::new(); letmut target_masks = Vec::new();
// For some composite modes, we force target mask due to limitations. That // might results in artifacts for these modes (which are already an existing // problem) but we can handle these cases as follow ups. let force_target_mask = match pic.composite_mode { // We can't currently render over top of these filters as their size // may have changed due to downscaling. We could handle this separate // case as a follow up.
Some(PictureCompositeMode::Filter(Filter::Blur { .. })) |
Some(PictureCompositeMode::Filter(Filter::DropShadows { .. })) |
Some(PictureCompositeMode::SVGFEGraph( .. )) => { true
}
_ => { false
}
};
// Work out which clips get drawn in to the source / target mask for i in0 .. prim_info.clip_chain.clips_range.count { let clip_instance = frame_state.clip_store.get_instance_from_range(&prim_info.clip_chain.clips_range, i);
let pic_surface_index = pic.raster_config.as_ref().unwrap().surface_index; let prim_local_rect: LayoutRect = frame_state
.surfaces[pic_surface_index.0]
.clipped_local_rect
.cast_unit();
// Handle masks on the source. This is the common case, and occurs for: // (a) Any masks in the same coord space as the surface // (b) All masks if the surface and parent are axis-aligned if !source_masks.is_empty() { let first_clip_node_index = frame_state.clip_store.clip_node_instances.len() as u32; let parent_task_id = scratch.frame.pictures[pic_scratch_handle].primary_render_task_id.expect("bug: no composite mode");
// Construct a new clip node range, also add image-mask dependencies as needed for instance in source_masks { let clip_instance = frame_state.clip_store.get_instance_from_range(&prim_info.clip_chain.clips_range, instance);
for tile in frame_state.clip_store.visible_mask_tiles(clip_instance) {
frame_state.rg_builder.add_dependency(
parent_task_id,
tile.task_id,
);
}
let clip_node_range = ClipNodeRange {
first: first_clip_node_index,
count: frame_state.clip_store.clip_node_instances.len() as u32 - first_clip_node_index,
};
// Add the mask as a sub-pass of the picture let pic_task_id = scratch.frame.pictures[pic_scratch_handle].primary_render_task_id.expect("uh oh"); let pic_task = frame_state.rg_builder.get_task_mut(pic_task_id);
let RenderTaskKind::Picture(info) = &pic_task.kind else { unreachable!() };
let task_rect = DeviceRect::from_origin_and_size(
info.content_origin,
pic_task.get_target_size().to_f32(),
);
// Handle masks on the target. This is the rare case, and occurs for: // Masks in parent space when non-axis-aligned to source space if !target_masks.is_empty() { let surface = &frame_state.surfaces[pic_context.surface_index.0]; let coverage_rect = prim_info.clip_chain.pic_coverage_rect;
let device_pixel_scale = surface.device_pixel_scale; let raster_spatial_node_index = surface.raster_spatial_node_index;
// Draw a normal screens-space mask to an alpha target that // can be sampled when compositing this picture. let empty_task = EmptyTask {
content_origin: clipped_surface_rect.min.to_f32(),
device_pixel_scale,
raster_spatial_node_index,
};
let task_size = clipped_surface_rect.size();
let clip_task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
task_size,
RenderTaskKind::Empty(empty_task),
));
// Construct a new clip node range, also add image-mask dependencies as needed let first_clip_node_index = frame_state.clip_store.clip_node_instances.len() as u32; for instance in target_masks { let clip_instance = frame_state.clip_store.get_instance_from_range(&prim_info.clip_chain.clips_range, instance);
for tile in frame_state.clip_store.visible_mask_tiles(clip_instance) {
frame_state.rg_builder.add_dependency(
clip_task_id,
tile.task_id,
);
}
iflet Picture3DContext::In { root_data: None, plane_splitter_index, ancestor_index, .. } = pic.context_3d { let dirty_rect = frame_state.current_dirty_region().combined; let visibility_spatial_node = frame_state.current_dirty_region().visibility_spatial_node;
let splitter = &mut frame_state.plane_splitters[plane_splitter_index.0]; let surface_index = pic.raster_config.as_ref().unwrap().surface_index; let surface = &frame_state.surfaces[surface_index.0]; let local_prim_rect = surface.clipped_local_rect.cast_unit();
PictureInstance::add_split_plane(
splitter,
frame_context.spatial_tree,
prim_spatial_node_index,
ancestor_index,
visibility_spatial_node,
local_prim_rect,
&prim_info.clip_chain.local_clip_rect,
dirty_rect,
plane_split_anchor,
);
}
}
PrimitiveKind::BackdropCapture { .. } => { // Register the owner picture of this backdrop primitive as the // target for resolve of the sub-graph
frame_state.surface_builder.register_resolve_source();
if frame_context.debug_flags.contains(DebugFlags::HIGHLIGHT_BACKDROP_FILTERS) { iflet Some(world_rect) = pic_state.map_pic_to_vis.map(&prim_info.clip_chain.pic_coverage_rect) {
scratch.push_debug_rect(
world_rect.cast_unit(), 2, crate::debug_colors::MAGENTA,
ColorF::TRANSPARENT,
);
}
}
}
PrimitiveKind::BackdropRender { pic_index, .. } => { match frame_state.surface_builder.sub_graph_output_map.get(pic_index).cloned() {
Some(sub_graph_output_id) => {
frame_state.surface_builder.add_child_render_task(
sub_graph_output_id,
frame_state.rg_builder,
); let backdrop_handle = scratch.frame.backdrop_render.push(BackdropRenderScratch {
src_task_id: sub_graph_output_id,
});
scratch.frame.draws[prim_instance_index.0as usize].kind_scratch =
KindScratchHandle::BackdropRender(backdrop_handle);
}
None => { // Backdrop capture was found not visible, didn't produce a sub-graph // so we can just skip drawing
scratch.frame.draws[prim_instance_index.0as usize].reset();
}
}
}
}
let segment_instance = &segment_instances_store[prim_segment_instance_index];
&segments_store[segment_instance.segments_range]
}
PrimitiveKind::NormalBorder { .. } |
PrimitiveKind::ImageBorder { .. } => { // Per-frame brush segments live in scratch.frame.segments; // the range was captured in prepare_prim_for_render and is // stored on the prim's per-kind scratch. The caller // resolves the range from there and passes it through. if prim_brush_segments_range.is_empty() { return None;
}
&segments_store[prim_brush_segments_range]
}
PrimitiveKind::LinearGradient { .. } => {
unreachable!("BUG: linear gradients should always use quad path");
}
PrimitiveKind::RadialGradient { .. } => {
unreachable!("BUG: radial gradients should always use quad path");
}
PrimitiveKind::ConicGradient { .. } => {
unreachable!("BUG: conic gradients should always use quad path");
}
};
// If there are no segments, early out to avoid setting a valid // clip task instance location below. if segments.is_empty() { return None;
}
// Set where in the clip mask instances array the clip mask info // can be found for this primitive. Each segment will push the // clip mask information for itself in update_clip_task below. let clip_task_index = ClipTaskIndex(clip_mask_instances.len() as _);
// If we only built 1 segment, there is no point in re-running // the clip chain builder. Instead, just use the clip chain // instance that was built for the main primitive. This is a // significant optimization for the common case. if segments.len() == 1 { let clip_mask_kind = update_brush_segment_clip_task(
&segments[0],
Some(prim_clip_chain),
root_spatial_node_index,
pic_context.surface_index,
frame_context,
frame_state,
device_pixel_scale,
);
clip_mask_instances.push(clip_mask_kind);
} else { let dirty_rect = frame_state.current_dirty_region().combined;
for segment in segments { // Build a clip chain for the smaller segment rect. This will // often manage to eliminate most/all clips, and sometimes // clip the segment completely.
frame_state.clip_store.set_active_clips_from_clip_chain(
prim_clip_chain,
prim_spatial_node_index,
visibility_spatial_node_index,
&frame_context.spatial_tree,
);
// First try to render this primitive's mask using optimized brush rendering. let prim_segment_instance_index = scratch.frame.draws[prim_instance_index.0as usize].segment_instance_index; // For prim kinds with per-frame brush segments, resolve the range // from the prim's per-kind scratch (allocated in // prepare_prim_for_render before this point). Empty range for any // other kind. let prim_brush_segments_range = match instance.kind {
PrimitiveKind::NormalBorder { .. } => { let nb_handle = scratch.frame.draws[prim_instance_index.0as usize]
.kind_scratch
.unwrap_normal_border();
scratch.frame.normal_border[nb_handle].brush_segments_range
}
PrimitiveKind::ImageBorder { .. } => { let ib_handle = scratch.frame.draws[prim_instance_index.0as usize]
.kind_scratch
.unwrap_image_border();
scratch.frame.image_border[ib_handle].brush_segments_range
}
_ => storage::Range::empty(),
}; let new_clip_task_index = iflet Some(clip_task_index) = update_clip_task_for_brush(
instance,
prim_segment_instance_index,
prim_brush_segments_range,
&clip_chain_snapshot,
prim_origin,
prim_spatial_node_index,
root_spatial_node_index,
visibility_spatial_node_index,
pic_context,
pic_state,
frame_context,
frame_state,
data_stores,
&mut scratch.frame.segments,
&mut scratch.frame.segment_instances,
&mut scratch.frame.clip_mask_instances,
device_pixel_scale,
) {
clip_task_index
} elseif scratch.frame.draws[prim_instance_index.0as usize].clip_chain.needs_mask { // Get a minimal device space rect, clipped to the screen that we // need to allocate for the clip mask, as well as interpolated // snap offsets. let unadjusted_device_rect = match frame_state.surfaces[pic_context.surface_index.0].get_surface_rect(
&scratch.frame.draws[prim_instance_index.0as usize].clip_chain.pic_coverage_rect,
frame_context.spatial_tree,
) {
Some(rect) => rect,
None => returnfalse,
};
let (device_rect, device_pixel_scale) = adjust_mask_scale_for_max_size(
unadjusted_device_rect,
device_pixel_scale,
);
fn write_brush_segment_description(
prim_local_rect: LayoutRect,
prim_local_clip_rect: LayoutRect,
clip_chain: &ClipChainInstance,
segment_builder: &mut SegmentBuilder,
clip_store: &ClipStore,
data_stores: &DataStores,
) -> bool { // If the brush is small, we want to skip building segments // and just draw it as a single primitive with clip mask. if prim_local_rect.area() < MIN_BRUSH_SPLIT_AREA { returnfalse;
}
// NOTE: The local clip rect passed to the segment builder must be the unmodified // local clip rect from the clip leaf, not the local_clip_rect from the // clip-chain instance. The clip-chain instance may have been reduced by // clips that are in the same coordinate system, but not the same spatial // node as the primitive. This can result in the clip for the segment building // being affected by scrolling clips, which we can't handle (since the segments // are not invalidated during frame building after being built).
segment_builder.initialize(
prim_local_rect,
None,
prim_local_clip_rect,
);
// Segment the primitive on all the local-space clip sources that we can. for i in0 .. clip_chain.clips_range.count { let clip_instance = clip_store
.get_instance_from_range(&clip_chain.clips_range, i); let clip_node = &data_stores.clip[clip_instance.handle];
// If this clip item is positioned by another positioning node, its relative position // could change during scrolling. This means that we would need to resegment. Instead // of doing that, only segment with clips that have the same positioning node. // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only // when necessary while scrolling. if !clip_instance.flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) { continue;
}
let (local_clip_rect, radius, mode) = match clip_node.item.kind {
ClipItemKind::RoundedRectangle { radius, mode } => { let radius = clamped_radius(&radius, clip_instance.clip_rect.size());
(clip_instance.clip_rect, Some(radius), mode)
}
ClipItemKind::Rectangle { mode } => {
(clip_instance.clip_rect, None, mode)
}
ClipItemKind::Image { .. } => {
panic!("bug: masks not supported on old segment path");
}
};
// Usually, the primitive rect can be found from information // in the instance and primitive template. let prim_local_rect = data_stores.get_local_prim_rect(
instance,
scratch.frame.draws[prim_instance_index.0as usize].snapped_local_rect,
&prim_store.pictures,
frame_state.surfaces,
);
// Decide whether this kind opts in to segmentation this frame. If // not, leave the per-draw segment_instance_index as its initialized // UNUSED value and bail. match instance.kind {
PrimitiveKind::Rectangle { .. } => { // Always opts in.
}
PrimitiveKind::YuvImage { .. } => { // Only use segments for YUV images if not drawing as a compositor surface let csk = scratch.frame.draws[prim_instance_index.0as usize].compositor_surface_kind; if !csk.supports_segments() { return;
}
}
PrimitiveKind::Image { data_handle, .. } => { let image_data = &data_stores.image[data_handle].kind; let csk = scratch.frame.draws[prim_instance_index.0as usize].compositor_surface_kind;
//Note: tiled images don't support automatic segmentation, // they strictly produce one segment per visible tile instead. if !csk.supports_segments() ||
frame_state.resource_cache
.get_image_properties(image_data.key)
.and_then(|properties| properties.tiling)
.is_some()
{ return;
}
}
PrimitiveKind::Picture { .. } |
PrimitiveKind::TextRun { .. } |
PrimitiveKind::NormalBorder { .. } |
PrimitiveKind::ImageBorder { .. } |
PrimitiveKind::LinearGradient { .. } |
PrimitiveKind::RadialGradient { .. } |
PrimitiveKind::ConicGradient { .. } |
PrimitiveKind::LineDecoration { .. } |
PrimitiveKind::BackdropCapture { .. } |
PrimitiveKind::BackdropRender { .. } => { // These primitives don't support / need segments. return;
}
PrimitiveKind::BoxShadow { .. } => {
unreachable!("BUG: box-shadows should not hit legacy brush clip path");
}
};
// Per-frame, unconditional segment build. The previous // INVALID-sentinel skip is gone — segments + segment_instances are // per-frame now, so they start empty each frame and we always // rebuild for every visible segmented prim. letmut segments: SmallVec<[BrushSegment; 8]> = SmallVec::new(); let clip_leaf = frame_state.clip_tree.get_leaf(instance.clip_leaf_id);
// If only a single segment is produced, there is no benefit to writing // a segment instance array. Instead, just use the main primitive rect // written into the GPU cache. // TODO(gw): This is (sortof) a bandaid - due to a limitation in the current // brush encoding, we can only support a total of up to 2^16 segments. // This should be (more than) enough for any real world case, so for // now we can handle this by skipping cases where we were generating // segments where there is no benefit. The long term / robust fix // for this is to move the segment building to be done as a more // limited nine-patch system during scene building, removing arbitrary // segmentation during frame-building (see bug #1617491). if segments.len() <= 1 { // Leave the per-draw index as its initialized UNUSED value. return;
}
let segments_range = scratch.frame.segments.extend(segments); let new_index = scratch.frame.segment_instances.push(BrushSegmentation {
segments_range,
gpu_data: GpuBufferAddress::INVALID,
});
scratch.frame.draws[prim_instance_index.0as usize].segment_instance_index = new_index;
}
// Ensures that the size of mask render tasks are within MAX_MASK_SIZE. fn adjust_mask_scale_for_max_size(device_rect: DeviceIntRect, device_pixel_scale: DevicePixelScale) -> (DeviceIntRect, DevicePixelScale) { if device_rect.width() > MAX_MASK_SIZE || device_rect.height() > MAX_MASK_SIZE { // round_out will grow by 1 integer pixel if origin is on a // fractional position, so keep that margin for error with -1: let device_rect_f = device_rect.to_f32(); let scale = (MAX_MASK_SIZE - 1) as f32 /
f32::max(device_rect_f.width(), device_rect_f.height()); let new_device_pixel_scale = device_pixel_scale * Scale::new(scale); let new_device_rect = (device_rect_f * Scale::new(scale))
.round_out()
.to_i32();
(new_device_rect, new_device_pixel_scale)
} else {
(device_rect, device_pixel_scale)
}
}
/// Pattern builder for a single fast-path two-stop segment emitted by
/// `decompose_axis_aligned_gradient`. Holds the segment's gradient line and /// stop colors (in segment-local coords); `build` translates start/end into /// the prim's spatial-node space by adding `ctx.prim_origin`. struct LinearGradientSegmentPattern {
start: LayoutPoint,
end: LayoutPoint,
stops: [GradientStop; 2],
}
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.