/* 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/. */
/// A set of default / global resources that are re-built each frame. #[cfg_attr(feature = "capture", derive(Serialize))] pubstruct FrameGlobalResources { /// The image shader block for the most common / default /// set of image parameters (color white, stretch == rect.size). pub default_image_data: GpuBufferAddress,
/// A GPU cache config for drawing cut-out rectangle primitives. /// This is used to 'cut out' overlay tiles where a compositor /// surface exists. pub default_black_rect_address: GpuBufferAddress,
}
impl FrameGlobalResources { pubfn new(gpu_buffers: &mut GpuBufferBuilder) -> Self { letmut writer = gpu_buffers.f32.write_blocks(ImageBrushPrimitiveData::NUM_BLOCKS);
writer.push(&ImageBrushPrimitiveData {
color: PremultipliedColorF::WHITE,
background_color: PremultipliedColorF::WHITE, // -ve means use prim rect for stretch size
stretch_size: LayoutSize::new(-1.0, 0.0),
}); let default_image_data = writer.finish();
letmut writer = gpu_buffers.f32.write_blocks(1);
writer.push_one(PremultipliedColorF::BLACK); let default_black_rect_address = writer.finish();
pubstruct FrameBuildingState<'a> { pub rg_builder: &'a mut RenderTaskGraphBuilder, pub clip_store: &'a mut ClipStore, pub resource_cache: &'a mut ResourceCache, pub transforms: &'a mut TransformPalette, pub segment_builder: SegmentBuilder, pub surfaces: &'a mut Vec<SurfaceInfo>, pub dirty_region_stack: Vec<DirtyRegion>, pub composite_state: &'a mut CompositeState, pub num_visible_primitives: u32, pub plane_splitters: &'a mut [PlaneSplitter], pub surface_builder: SurfaceBuilder, pub cmd_buffers: &'a mut CommandBufferList, pub clip_tree: &'a ClipTree, pub frame_gpu_data: &'a mut GpuBufferBuilder, /// When using a render task to produce pixels that are associated with /// an image key (for example snapshotted pictures), inserting the image /// key / task id association in this hashmap allows the image item to /// register a dependency to the render task. This ensures that the /// render task is produced before the image that renders it if they /// are happening in the same frame. /// This mechanism relies on the item producing the render task to be /// traversed before the image that displays it (in other words, the /// picture must appear before the image in the display list). pub image_dependencies: FastHashMap<ImageKey, RenderTaskId>, /// Per-picture scratch-index slot used by the prepare pass. `None` /// means "not yet visited this frame"; `Some(handle)` means the /// picture was visited and `handle` indexes into /// `PrimitiveFrameScratch.pictures` (or is `INVALID` if take_context /// failed). Replaces the prepare pass's earlier `visited_pictures` /// bool slice — visibility keeps its own bool slice on /// `FrameVisibilityState`. pub picture_scratch_handles: &'a mut [Option<storage::Index<PictureScratch>>],
}
impl<'a> FrameBuildingState<'a> { /// Retrieve the current dirty region during primitive traversal. pubfn current_dirty_region(&self) -> &DirtyRegion { self.dirty_region_stack.last().unwrap()
}
/// Push a new dirty region for child primitives to cull / clip against. pubfn push_dirty_region(&mutself, region: DirtyRegion) { self.dirty_region_stack.push(region);
}
/// Pop the top dirty region from the stack. pubfn pop_dirty_region(&mutself) { self.dirty_region_stack.pop().unwrap();
}
/// Push a primitive command to a set of command buffers pubfn push_prim(
&mutself,
cmd: &PrimitiveCommand,
spatial_node_index: SpatialNodeIndex,
targets: &[CommandBufferIndex],
) { for cmd_buffer_index in targets { let cmd_buffer = self.cmd_buffers.get_mut(*cmd_buffer_index);
cmd_buffer.add_prim(cmd, spatial_node_index);
}
}
/// Push a command to a set of command buffers pubfn push_cmd(
&mutself,
cmd: &PrimitiveCommand,
targets: &[CommandBufferIndex],
) { for cmd_buffer_index in targets { let cmd_buffer = self.cmd_buffers.get_mut(*cmd_buffer_index);
cmd_buffer.add_cmd(cmd);
}
}
/// Set the active list of segments in a set of command buffers pubfn set_segments(
&mutself,
segments: &[QuadSegment],
targets: &[CommandBufferIndex],
) { for cmd_buffer_index in targets { let cmd_buffer = self.cmd_buffers.get_mut(*cmd_buffer_index);
cmd_buffer.set_segments(segments);
}
}
}
/// Immutable context of a picture when processing children. #[derive(Debug)] pubstruct PictureContext { pub pic_index: PictureIndex, pub surface_spatial_node_index: SpatialNodeIndex, pub raster_spatial_node_index: SpatialNodeIndex, pub visibility_spatial_node_index: SpatialNodeIndex, /// The surface that this picture will render on. pub surface_index: SurfaceIndex, pub dirty_region_count: usize, pub subpixel_mode: SubpixelMode,
}
/// Mutable state of a picture that gets modified when /// the children are processed. pubstruct PictureState { pub map_local_to_pic: SpaceMapper<LayoutPixel, PicturePixel>, pub map_pic_to_vis: SpaceMapper<PicturePixel, VisPixel>,
}
let root_spatial_node_index = spatial_tree.root_reference_frame_index();
const MAX_CLIP_COORD: f32 = 1.0e9;
// Reset all plane splitters. These are retained from frame to frame to reduce // per-frame allocations self.plane_splitters.resize_with(scene.num_plane_splitters, BspSplitter::new); for splitter in &mutself.plane_splitters {
splitter.reset();
}
// Add a "fake" surface that we will use as parent for // snapshotted pictures. let root_spatial_node = frame_context.spatial_tree.root_reference_frame_index(); let snapshot_surface = SurfaceIndex(scene.surfaces.len());
scene.surfaces.push(SurfaceInfo::new(
root_spatial_node,
root_spatial_node,
WorldRect::max_rect(),
&frame_context.spatial_tree,
euclid::Scale::new(1.0),
(1.0, 1.0),
(1.0, 1.0), false, false,
));
// Build the per-frame draw header storage with one entry per prim // instance. Identity-indexed by `PrimitiveInstanceIndex.0` for now; // a follow-up will switch this to push-per-draw. The per-prim // `snapped_local_rect` is filled in by the visibility pass.
scratch.primitive.frame.draws.clear();
scratch.primitive.frame.draws.resize_with(
scene.prim_instances.len(), crate::visibility::PrimitiveDrawHeader::new,
);
// Cluster, prim, and clip-leaf rects are snapped to the device pixel // grid as they are produced by the in-frame picture-graph passes: // `propagate_bounding_rects` snaps each cluster bounding rect, and the // visibility pass snaps each prim's `snapped_local_rect` and clip-leaf // rect. Both snap against the consuming surface's raster node, so only // pictures reachable this frame are touched.
scene.picture_graph.propagate_bounding_rects(
&mut scene.prim_store.pictures,
&mut scene.surfaces,
&frame_context,
);
// In order to handle picture snapshots consistently we need // the visibility and prepare passes to visit them first before // traversing the scene. This ensures that out-of-view snapshots // are rendered and that snapshots are consistently produced // relative to the root spatial node. // However it means that the visibility and prepare passes may // visit some pictures multiple times, so we keep track of visited // pictures during each traversal to avoid that. let n_pics = scene.prim_store.pictures.len(); letmut visited_pictures = frame_memory.new_vec_with_capacity(n_pics); for _ in0..n_pics {
visited_pictures.push(false);
}
// For now, snapshots are updated every frame. For the // pictures displaying the snapshot via images pick up // the changes, we have to make sure that the image's // generation counter is incremented early in the frame, // before the main visibility pass visits the image items. let pic = &scene.prim_store.pictures[pic_index.0]; let snapshot = pic.snapshot
.unwrap(); let key = snapshot.key.as_image();
visibility_state.resource_cache
.increment_image_generation(key);
for pic_index in scene.tile_cache_pictures.iter().rev() { if !render_picture_cache_slices { break;
} let pic = &mut scene.prim_store.pictures[pic_index.0];
// If we have a tile cache for this picture, see if any of the // relative transforms have changed, which means we need to // re-map the dependencies of any child primitives. let world_culling_rect = tile_cache.pre_update(
surface_index,
&visibility_context,
&mut visibility_state,
);
// Push a new surface, supplying the list of clips that should be // ignored, since they are handled by clipping when drawing this surface.
visibility_state.push_surface(
*pic_index,
surface_index,
);
visibility_state.clip_tree.push_clip_root_node(tile_cache.shared_clip_node_id);
if !scene.snapshot_pictures.is_empty() { // Push a default dirty region which does not cull any // primitive. letmut default_dirty_region = DirtyRegion::new(
root_spatial_node_index,
root_spatial_node_index,
);
default_dirty_region.add_dirty_region(
PictureRect::max_rect(),
frame_context.spatial_tree,
);
frame_state.push_dirty_region(default_dirty_region);
if !scene.snapshot_pictures.is_empty() {
frame_state.surface_builder.pop_empty_surface();
frame_state.pop_dirty_region();
}
// Push a default dirty region which culls primitives // against the screen world rect, in absence of any // other dirty regions. letmut default_dirty_region = DirtyRegion::new(
root_spatial_node_index,
root_spatial_node_index,
);
default_dirty_region.add_dirty_region(
frame_context.global_screen_world_rect.cast_unit(),
frame_context.spatial_tree,
);
frame_state.push_dirty_region(default_dirty_region);
for pic_index in &scene.tile_cache_pictures { if !render_picture_cache_slices { break;
}
// Used to generated a unique z-buffer value per primitive. letmut z_generator = ZBufferIdGenerator::new(scene.config.max_depth_ids); let use_dual_source_blending = scene.config.dual_source_blending_is_supported;
// In our main walk over the spatial tree (below), for nodes inside a // subtree rooted at a root-content node, we need some information from // that enclosing root-content node. To collect this information, do an // preliminary walk over the spatial tree now and collect the root-content // info in a HashMap. struct RootContentInfo {
transform: LayoutToWorldTransform,
clip: LayoutRect
} letmut root_content_info = FastHashMap::<ExternalScrollId, RootContentInfo>::default();
spatial_tree.visit_nodes(|index, node| { iflet SpatialNodeType::ScrollFrame(ref scroll_frame_info) = node.node_type { iflet Some(minimap_data) = minimap_data_store.get(&scroll_frame_info.external_id) { if minimap_data.is_root_content { let transform = spatial_tree.get_world_viewport_transform(index).into_transform();
root_content_info.insert(scroll_frame_info.external_id, RootContentInfo{
transform,
clip: scroll_frame_info.viewport_rect
});
}
}
}
});
// This is the main walk over the spatial tree. For every scroll frame node which // has minimap data, compute the rects we want to render for that minimap in world // coordinates and add them to `scratch.frame.debug_items`.
spatial_tree.visit_nodes(|index, node| { iflet SpatialNodeType::ScrollFrame(ref scroll_frame_info) = node.node_type { iflet Some(minimap_data) = minimap_data_store.get(&scroll_frame_info.external_id) { const HORIZONTAL_PADDING: f32 = 5.0; const VERTICAL_PADDING: f32 = 10.0; const PAGE_BORDER_COLOR: ColorF = debug_colors::BLACK; const BACKGROUND_COLOR: ColorF = ColorF { r: 0.3, g: 0.3, b: 0.3, a: 0.3}; const DISPLAYPORT_BACKGROUND_COLOR: ColorF = ColorF { r: 1.0, g: 1.0, b: 1.0, a: 0.4}; const LAYOUT_PORT_COLOR: ColorF = debug_colors::RED; const VISUAL_PORT_COLOR: ColorF = debug_colors::BLUE; const DISPLAYPORT_COLOR: ColorF = debug_colors::LIME;
let viewport = scroll_frame_info.viewport_rect;
// Scale the minimap to make it 100px wide (if there's space), and the full height // of the scroll frame's viewport, minus some padding. Position it at the left edge // of the scroll frame's viewport. let scale_factor_x = 100f32.min(viewport.width() - (2.0 * HORIZONTAL_PADDING))
/ minimap_data.scrollable_rect.width(); let scale_factor_y = (viewport.height() - (2.0 * VERTICAL_PADDING))
/ minimap_data.scrollable_rect.height(); if scale_factor_x <= 0.0 || scale_factor_y <= 0.0 { return;
} let transform = LayoutTransform::scale(scale_factor_x, scale_factor_y, 1.0)
.then_translate(LayoutVector3D::new(HORIZONTAL_PADDING, VERTICAL_PADDING, 0.0))
.then_translate(LayoutVector3D::new(viewport.min.x, viewport.min.y, 0.0));
// Transforms for transforming rects in this scroll frame's local coordintes, to world coordinates. // For scroll frames inside a root-content subtree, we apply this transform in two parts // (local to root-content, and root-content to world), so that we can make additional // adjustments in root-content space. For scroll frames outside of a root-content subtree, // the entire world transform will be in `local_to_root_content`. let world_transform = spatial_tree
.get_world_viewport_transform(index)
.into_transform(); letmut local_to_root_content =
world_transform.with_destination::<LayoutPixel>(); letmut root_content_to_world = LayoutToWorldTransform::default(); letmut root_content_clip = None; if minimap_data.root_content_scroll_id != 0 { iflet Some(RootContentInfo{transform: root_content_transform, clip}) = root_content_info.get(&ExternalScrollId(minimap_data.root_content_scroll_id, minimap_data.root_content_pipeline_id)) { // Exclude the root-content node's zoom transform from `local_to_root_content`. // This ensures that the minimap remains unaffected by pinch-zooming // (in essence, remaining attached to the *visual* viewport, rather than to // the *layout* viewport which is what happens by default). let zoom_transform = minimap_data.zoom_transform;
local_to_root_content = world_transform
.then(&root_content_transform.inverse().unwrap())
.then(&zoom_transform.inverse().unwrap());
root_content_to_world = root_content_transform.clone();
root_content_clip = Some(clip);
}
}
letmut add_rect = |rect, border, fill| -> Option<()> { const STROKE_WIDTH: f32 = 2.0; // Place rect in scroll frame's local coordinate space let transformed_rect = transform.outer_transformed_box2d(&rect)?;
// Transform to world coordinates, using root-content coords as an intermediate step. letmut root_content_rect = local_to_root_content.outer_transformed_box2d(&transformed_rect)?; // In root-content coords, apply the root content node's viewport clip. // This prevents subframe minimaps from leaking into the chrome area when the root // scroll frame is scrolled. // TODO: The minimaps of nested subframes can still leak outside of the viewports of // their containing subframes. Should have a more proper fix for this. iflet Some(clip) = root_content_clip {
root_content_rect = root_content_rect.intersection(clip)?;
} let world_rect = root_content_to_world.outer_transformed_box2d(&root_content_rect)?;
// Add world coordinate rects to scratch.frame.debug_items iflet Some(fill_color) = fill { let interior_world_rect = WorldRect::new(
world_rect.min + WorldVector2D::new(STROKE_WIDTH, STROKE_WIDTH),
world_rect.max - WorldVector2D::new(STROKE_WIDTH, STROKE_WIDTH)
);
scratch.push_debug_rect(interior_world_rect * DevicePixelScale::new(1.0), 1, border, fill_color);
}
Some(())
};
add_rect(minimap_data.scrollable_rect, PAGE_BORDER_COLOR, Some(BACKGROUND_COLOR));
add_rect(minimap_data.displayport, DISPLAYPORT_COLOR, Some(DISPLAYPORT_BACKGROUND_COLOR)); // Only render a distinct layout viewport for the root content. // For other scroll frames, the visual and layout viewports coincide. if minimap_data.is_root_content {
add_rect(minimap_data.layout_viewport, LAYOUT_PORT_COLOR, None);
}
add_rect(minimap_data.visual_viewport, VISUAL_PORT_COLOR, None);
}
}
});
}
/// Skip pictures that are fully covered by another opaque picture with the same rounded rect clip. /// /// Since the clip adds some transparency to the occluder, the opaque rectangles occlusion detection /// fails to discard stacks of opaque rounded corners. /// This is done specifically to avoid conflation artifacts with the rounded rectangle around web content /// in Firefox. fn skip_occluded_pictures_with_clips(
&self,
tile_cache_pictures: &Vec<PictureIndex>,
pictures: &mut [PictureInstance],
tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
frame_context: &FrameBuildingContext,
composite_state: &mut CompositeState,
) { letmut current_opaque_clip = None;
for pic_index in tile_cache_pictures.iter().rev() { let pic = &mut pictures[pic_index.0];
match pic.raster_config {
Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { slice_id }, .. }) => { let tile_cache = tile_caches
.get_mut(&slice_id)
.expect("bug: non-existent tile cache"); // Only check for compositor clips (rounded clips). The main // rectangle occlusion for compositor tiles handles the rest fine. if tile_cache.compositor_clip.is_none() { continue;
}
// Get the rounded clip for this picture cache. let (rounded_clip_rect, rounded_clip_radii) = composite_state.compositor_clip_params(
tile_cache.compositor_clip,
DeviceRect::max_rect(),
);
// Simple compare against the previous compositor clip we found iflet Some((current_clip_rect, current_clip_radius)) = current_opaque_clip { if current_clip_rect == rounded_clip_rect &&
current_clip_radius == rounded_clip_radii { for sub_slice in tile_cache.sub_slices.iter_mut() { for tile in sub_slice.tiles.values_mut() {
tile.is_visible = false;
}
}
}
}
let backdrop_rect = tile_cache.backdrop.backdrop_rect
.intersection(&tile_cache.local_rect)
.and_then(|r| {
r.intersection(&tile_cache.local_clip_rect)
});
iflet Some(backdrop_rect) = backdrop_rect { let map_local_to_world = SpaceMapper::new_with_target(
frame_context.root_spatial_node_index,
tile_cache.spatial_node_index,
frame_context.global_screen_world_rect,
frame_context.spatial_tree,
); let world_backdrop_rect = map_local_to_world
.map(&backdrop_rect)
.expect("bug: unable to map backdrop rect"); let device_backdrop_rect = (world_backdrop_rect * frame_context.global_device_pixel_scale).round();
if device_backdrop_rect.contains_box(&rounded_clip_rect) { // Save compositor clip for checking against subsequent slices
current_opaque_clip = Some((rounded_clip_rect, rounded_clip_radii));
}
}
}
_ => {
panic!("bug: found a top-level prim that isn't a tile cache");
}
}
}
}
fn build_composite_pass(
&self,
scene: &BuiltScene,
ctx: &RenderTargetContext,
gpu_buffers: &mut GpuBufferBuilder,
deferred_resolves: &mut FrameVec<DeferredResolve>,
composite_state: &mut CompositeState,
) { for pic_index in &scene.tile_cache_pictures { let pic = &ctx.prim_store.pictures[pic_index.0];
match pic.raster_config {
Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { slice_id }, .. }) => { // Tile cache instances are added to the composite config, rather than // directly added to batches. This allows them to be drawn with various // present modes during render, such as partial present etc. let tile_cache = &ctx.tile_caches[&slice_id]; let map_local_to_world = SpaceMapper::new_with_target(
ctx.root_spatial_node_index,
tile_cache.spatial_node_index,
ctx.screen_world_rect,
ctx.spatial_tree,
); let world_clip_rect = map_local_to_world
.map(&tile_cache.local_clip_rect)
.expect("bug: unable to map clip rect"); let device_clip_rect = (world_clip_rect * ctx.global_device_pixel_scale).round();
composite_state.push_surface(
tile_cache,
device_clip_rect,
ctx.resource_cache,
&mut gpu_buffers.f32,
deferred_resolves,
);
}
_ => {
panic!("bug: found a top-level prim that isn't a tile cache");
}
}
}
}
}
/// Processes this pass to prepare it for rendering. /// /// Among other things, this allocates output regions for each of our tasks /// (added via `add_render_task`) in a RenderTarget and assigns it into that /// target. pubfn build_render_pass(
src_pass: &Pass,
screen_size: DeviceIntSize,
ctx: &mut RenderTargetContext,
gpu_buffer_builder: &mut GpuBufferBuilder,
render_tasks: &RenderTaskGraph,
clip_store: &ClipStore,
transforms: &mut TransformPalette,
prim_headers: &mut PrimitiveHeaders,
z_generator: &mut ZBufferIdGenerator,
gpu_supports_fast_clears: bool,
prim_instances: &[PrimitiveInstance],
cmd_buffers: &CommandBufferList,
) -> RenderPass {
profile_scope!("build_render_pass");
// TODO(gw): In this initial frame graph work, we try to maintain the existing // build_render_pass code as closely as possible, to make the review // simpler and reduce chance of regressions. However, future work should // include refactoring this to more closely match the built frame graph. letmut pass = RenderPass::new(src_pass, ctx.frame_memory);
for sub_pass in &src_pass.sub_passes { match sub_pass.surface {
SubPassSurface::Dynamic { target_kind, texture_id, used_rect } => { match target_kind {
RenderTargetKind::Color => { letmut target = RenderTarget::new(
RenderTargetKind::Color, false,
texture_id,
screen_size,
gpu_supports_fast_clears,
Some(used_rect),
&ctx.frame_memory,
);
for task_id in &sub_pass.task_ids {
target.add_task(
*task_id,
ctx,
gpu_buffer_builder,
render_tasks,
clip_store,
transforms,
);
}
match task.kind {
RenderTaskKind::Picture(ref pic_task) => { let cmd_buffer = cmd_buffers.get(pic_task.cmd_buffer_index); letmut dirty_rect = pic_task.scissor_rect.expect("bug: must be set for cache tasks"); letmut valid_rect = pic_task.valid_rect.expect("bug: must be set for cache tasks");
// If we have a surface size, clip the dirty and vaild rects // to that size. This ensures that native compositors will // pass sanity checks (Bug 1971296). iflet ResolvedSurfaceTexture::Native { size, .. } = surface { let surface_size_rect = <DeviceIntRect>::from_size(*size);
dirty_rect = dirty_rect.intersection(&surface_size_rect).unwrap_or_default();
valid_rect = valid_rect.intersection(&surface_size_rect).unwrap_or_default();
}
for target in &mut pass.texture_cache.values_mut() {
target.build(
ctx,
render_tasks,
prim_headers,
transforms,
z_generator,
prim_instances,
cmd_buffers,
gpu_buffer_builder,
);
}
pass
}
/// A rendering-oriented representation of the frame built by the render backend /// and presented to the renderer. /// /// # Safety /// /// The frame's allocator memory must be dropped after all of the frame's containers. /// This is handled in the renderer and in `RenderedDocument`'s Drop implementation. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct Frame { /// The rectangle to show the frame in, on screen. pub device_rect: DeviceIntRect, pub present: bool, pub passes: FrameVec<RenderPass>,
/// List of textures that we don't know about yet /// from the backend thread. The render thread /// will use a callback to resolve these and /// patch the data structures. pub deferred_resolves: FrameVec<DeferredResolve>,
/// True if this frame contains any render tasks /// that write to the texture cache. pub has_texture_cache_tasks: bool,
/// True if this frame has been drawn by the /// renderer. pub has_been_rendered: bool,
/// Debugging information to overlay for this frame. pub debug_items: Vec<DebugItem>,
/// Contains picture cache tiles, and associated information. /// Used by the renderer to composite tiles into the framebuffer, /// or hand them off to an OS compositor. pub composite_state: CompositeState,
/// Main GPU data buffer constructed (primarily) during the prepare /// pass for primitives that were visible and dirty. pub gpu_buffer_f: GpuBufferF, pub gpu_buffer_i: GpuBufferI,
/// The backing store for the frame's allocator. /// /// # Safety /// /// Must not be dropped while frame allocations are alive. /// /// Rust has deterministic drop order [1]. We rely on `allocator_memory` /// being the last member of the `Frame` struct so that it is dropped /// after the frame's containers. /// /// [1]: https://doc.rust-lang.org/reference/destructors.html pub allocator_memory: FrameMemory,
}
impl Frame { // This frame must be flushed if it writes to the // texture cache, and hasn't been drawn yet. pubfn must_be_drawn(&self) -> bool { self.has_texture_cache_tasks && !self.has_been_rendered
}
// Returns true if this frame doesn't alter what is on screen currently. pubfn is_nop(&self) -> bool { // If there are no off-screen passes, that implies that there are no // picture cache tiles, and no texture cache tasks being updates. If this // is the case, we can consider the frame a nop (higher level checks // test if a composite is needed due to picture cache surfaces moving // or external surfaces being updated). self.passes.is_empty()
}
}
Messung V0.5 in Prozent
¤ 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.0.19Bemerkung:
¤
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.