/* 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/. */
/// Per-frame scratch data for a NormalBorder primitive. #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] pubstruct NormalBorderScratch { /// Range into `PrimitiveScratchBuffer::border_task_ids` holding the /// cached render-task ids for this border's segments. pub task_ids: storage::Range<RenderTaskId>, /// Range into `PrimitiveFrameScratch::segments` holding the per- /// frame brush segments for this border. Built fresh each frame /// against the prim's current size in `prepare_prim_for_render`, /// so the segmentation matches the rendered rect. pub brush_segments_range: storage::Range<BrushSegment>, /// Range into `PrimitiveFrameScratch::border_segments` holding the /// per-frame edge/corner cache-key + task-size records for this /// border. Parallel to `brush_segments_range` and built alongside. pub border_segments_range: storage::Range<BorderSegmentInfo>, /// Per-instance GPU buffer address for the brush + segment blocks /// written by `NormalBorderData::write_brush_gpu_blocks`. Per- /// instance because the block contents (stretch_size and segments) /// depend on the prim's per-instance size. pub gpu_address: GpuBufferAddress, /// True if any side uses a Dotted or Dashed style. Read by batch /// to set `BatchFeatures::REPETITION` so the cached dot/dash tile /// repeats across the rendered segment via brush_image. pub may_need_repetition: bool,
}
impl NormalBorderScratch { /// Build the per-frame brush + border segments and the parallel /// task-id slot for a NormalBorder prim, push the resulting /// `NormalBorderScratch` entry, and wire it up to the prim's /// `PrimitiveDrawHeader.kind_scratch`. /// /// Called from the prep-pass before `update_clip_task` runs, since /// `update_clip_task_for_brush` reads the brush segments via the /// `NormalBorderScratch` allocated here. The segment list is built /// against the prim's per-instance size, with the two arenas /// (`scratch.frame.segments` and `scratch.frame.border_segments`) /// receiving direct pushes through `data_mut` to avoid intermediate /// `Vec` allocations. pubfn build_for_prim(
data_handle: NormalBorderDataHandle,
prim_instance_index: PrimitiveInstanceIndex,
prim_size: LayoutSize,
data_stores: &DataStores,
scratch: &mut PrimitiveScratchBuffer,
) { let prim_data = &data_stores.normal_border[data_handle]; let border = &prim_data.kind.border; let widths = &prim_data.kind.widths;
let brush_open = scratch.frame.segments.open_range(); let border_open = scratch.frame.border_segments.open_range();
border::create_border_segments(
prim_size,
border,
widths,
scratch.frame.border_segments.data_mut(),
scratch.frame.segments.data_mut(),
); let brush_segments_range = scratch.frame.segments.close_range(brush_open); let border_segments_range = scratch.frame.border_segments.close_range(border_open);
impl NormalBorderData { /// Update the GPU cache for a given primitive template. This may be called multiple /// times per frame, by each primitive reference that refers to this interned /// template. The initial request call to the GPU cache ensures that work is only /// done if the cache entry is invalid (due to first use or eviction). pubfn write_brush_gpu_blocks(
&mutself,
common: &mut PrimTemplateCommonData,
prim_size: LayoutSize,
brush_segments: &[BrushSegment],
frame_state: &mut FrameBuildingState,
) -> GpuBufferAddress { letmut writer = frame_state.frame_gpu_data.f32.write_blocks(3 + brush_segments.len() * VECS_PER_SEGMENT);
// Border primitives currently used for // image borders, and run through the // normal brush_image shader.
writer.push(&ImageBrushPrimitiveData {
color: PremultipliedColorF::WHITE,
background_color: PremultipliedColorF::WHITE,
stretch_size: prim_size,
});
for segment in brush_segments {
segment.write_gpu_blocks(&mut writer);
}
let gpu_address = writer.finish();
common.opacity = PrimitiveOpacity::translucent();
gpu_address
}
pubfn update(
&mutself,
border_segments: &[BorderSegmentInfo],
prim_spatial_node_index: SpatialNodeIndex,
device_pixel_scale: DevicePixelScale,
frame_context: &FrameBuildingContext,
frame_state: &mut FrameBuildingState,
task_ids: &mut [RenderTaskId],
) { // TODO(gw): For now, the scale factors to rasterize borders at are // based on the true world transform of the primitive. When // raster roots with local scale are supported in future, // that will need to be accounted for here. let scale = frame_context
.spatial_tree
.get_world_transform(prim_spatial_node_index)
.scale_factors();
// Scale factors are normalized to a power of 2 to reduce the number of // resolution changes. // For frames with a changing scale transform round scale factors up to // nearest power-of-2 boundary so that we don't keep having to redraw // the content as it scales up and down. Rounding up to nearest // power-of-2 boundary ensures we never scale up, only down --- avoiding // jaggies. It also ensures we never scale down by more than a factor of // 2, avoiding bad downscaling quality. let scale_width = clamp_to_scale_factor(scale.0, false); let scale_height = clamp_to_scale_factor(scale.1, false); // Pick the maximum dimension as scale let world_scale = LayoutToWorldScale::new(scale_width.max(scale_height)); letmut scale = world_scale * device_pixel_scale; let max_scale = get_max_scale_for_border(border_segments);
scale.0 = scale.0.min(max_scale.0);
// For each edge and corner, request the render task by content key // from the render task cache. This ensures that the render task for // this segment will be available for batching later in the frame. // TODO: this does not ensure that segments will be in the same cache // texture, though? The brush code path relies on that.
for (i, segment) in border_segments.iter().enumerate() { // Update the cache key device size based on requested scale. let cache_size = to_cache_size(segment.local_task_size, &mut scale); let cache_key = RenderTaskCacheKey {
kind: RenderTaskCacheKeyKind::BorderSegment(segment.cache_key.clone()),
origin: DeviceIntPoint::zero(),
size: cache_size,
};
impl From<NormalBorderKey> for NormalBorderTemplate { fn from(key: NormalBorderKey) -> Self { let common = PrimTemplateCommonData::with_key_common(key.common);
letmut border: NormalBorder = key.kind.border.into(); let widths = LayoutSideOffsets::from_au(key.kind.widths);
// FIXME(emilio): Is this the best place to do this?
border.normalize(&widths);
/// Per-frame scratch data for an ImageBorder primitive. #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] pubstruct ImageBorderScratch { /// Range into `PrimitiveFrameScratch::segments` holding the per- /// frame nine-patch brush segments for this border. Built fresh /// each frame against the prim's current size in /// `prepare_prim_for_render`. pub brush_segments_range: storage::Range<BrushSegment>, /// Per-instance GPU buffer address for the brush + segment blocks /// written by `ImageBorderData::update`. Per-instance because the /// block contents (stretch_size and segments) depend on the prim's /// per-instance size. pub gpu_address: GpuBufferAddress,
}
impl ImageBorderScratch { /// Build the per-frame nine-patch brush segments for an ImageBorder /// prim, push the resulting `ImageBorderScratch` entry, and wire it /// up to the prim's `PrimitiveDrawHeader.kind_scratch`. /// /// Called from the prep early pass before `update_clip_task` runs, /// since `update_clip_task_for_brush` reads the brush segments via /// the scratch entry allocated here. pubfn build_for_prim(
data_handle: ImageBorderDataHandle,
prim_instance_index: PrimitiveInstanceIndex,
prim_size: LayoutSize,
data_stores: &DataStores,
scratch: &mut PrimitiveScratchBuffer,
) { let prim_data = &data_stores.image_border[data_handle]; let nine_patch = &prim_data.kind.nine_patch;
let brush_open = scratch.frame.segments.open_range();
scratch.frame.segments.data_mut().extend(
nine_patch.create_brush_segments(prim_size),
); let brush_segments_range = scratch.frame.segments.close_range(brush_open);
impl ImageBorderData { /// Update the GPU cache for a given primitive template. This may be called multiple /// times per frame, by each primitive reference that refers to this interned /// template. The initial request call to the GPU cache ensures that work is only /// done if the cache entry is invalid (due to first use or eviction). pubfn update(
&mutself,
common: &mut PrimTemplateCommonData,
prim_size: LayoutSize,
brush_segments: &[BrushSegment],
frame_state: &mut FrameBuildingState,
) -> GpuBufferAddress { letmut writer = frame_state.frame_gpu_data.f32.write_blocks(3 + brush_segments.len() * VECS_PER_SEGMENT); self.write_prim_gpu_blocks(&mut writer, &prim_size); Self::write_segment_gpu_blocks(&mut writer, brush_segments); let gpu_address = writer.finish();
fn write_prim_gpu_blocks(
&self,
writer: &mut GpuBufferWriterF,
prim_size: &LayoutSize,
) { // Border primitives currently used for // image borders, and run through the // normal brush_image shader.
writer.push(&ImageBrushPrimitiveData {
color: PremultipliedColorF::WHITE,
background_color: PremultipliedColorF::WHITE,
stretch_size: *prim_size,
});
}
fn write_segment_gpu_blocks(
writer: &mut GpuBufferWriterF,
brush_segments: &[BrushSegment],
) { for segment in brush_segments {
segment.write_gpu_blocks(writer);
}
}
}
impl From<ImageBorderKey> for ImageBorderTemplate { fn from(key: ImageBorderKey) -> Self { let common = PrimTemplateCommonData::with_key_common(key.common);
#[test] #[cfg(target_pointer_width = "64")] fn test_struct_sizes() { use std::mem; // The sizes of these structures are critical for performance on a number of // talos stress tests. If you get a failure here on CI, there's two possibilities: // (a) You made a structure smaller than it currently is. Great work! Update the // test expectations and move on. // (b) You made a structure larger. This is not necessarily a problem, but should only // be done with care, and after checking if talos performance regresses badly.
assert_eq!(mem::size_of::<NormalBorderPrim>(), 100, "NormalBorderPrim size changed");
assert_eq!(mem::size_of::<NormalBorderTemplate>(), 156, "NormalBorderTemplate size changed");
assert_eq!(mem::size_of::<NormalBorderKey>(), 104, "NormalBorderKey size changed");
assert_eq!(mem::size_of::<ImageBorder>(), 68, "ImageBorder size changed");
assert_eq!(mem::size_of::<ImageBorderTemplate>(), 104, "ImageBorderTemplate size changed");
assert_eq!(mem::size_of::<ImageBorderKey>(), 72, "ImageBorderKey size changed");
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-08-26)
¤
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.