/* 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/. */
//! The high-level module responsible for managing the pipeline and preparing //! commands to be issued by the `Renderer`. //! //! See the comment at the top of the `renderer` module for a description of //! how these two pieces interact.
use api::{DebugFlags, Parameter, BoolParameter, PrimitiveFlags, MinimapData}; use api::{DocumentId, ExternalScrollId, HitTestResult}; use api::{IdNamespace, PipelineId, RenderNotifier, SampledScrollOffset}; use api::{NotificationRequest, Checkpoint, QualitySettings}; use api::{FramePublishId, RenderReasons}; use api::units::*; use api::channel::{single_msg_channel, Sender, Receiver}; usecrate::bump_allocator::ChunkPool; usecrate::AsyncPropertySampler; usecrate::box_shadow::BoxShadow; usecrate::prim_store::rectangle::RectanglePrim; #[cfg(any(feature = "capture", feature = "replay"))] usecrate::render_api::CaptureBits; #[cfg(feature = "replay")] usecrate::render_api::CapturedDocument; usecrate::render_api::{MemoryReport, TransactionMsg, ResourceUpdate, ApiMsg, FrameMsg, ClearCache, DebugCommand}; usecrate::clip::{ClipIntern, PolygonIntern, ClipStoreScratchBuffer}; usecrate::filterdata::FilterDataIntern; #[cfg(any(feature = "capture", feature = "replay"))] usecrate::capture::CaptureConfig; usecrate::composite::{CompositorKind, CompositeDescriptor}; usecrate::frame_builder::{FrameBuilder, FrameBuilderConfig, FrameScratchBuffer}; use glyph_rasterizer::FontInstance; usecrate::hit_test::{HitTest, HitTester, SharedHitTester}; usecrate::intern::DataStore; #[cfg(any(feature = "capture", feature = "replay"))] usecrate::internal_types::DebugOutput; usecrate::internal_types::{FastHashMap, FrameId, FrameStamp, RenderedDocument, ResultMsg}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; usecrate::picture::{PictureScratchBuffer, SurfaceInfo, RasterConfig}; usecrate::tile_cache::{SliceId, TileCacheInstance, TileCacheParams}; usecrate::picture::PictureInstance; usecrate::prim_store::{PrimitiveScratchBuffer, PrimitiveInstance}; usecrate::prim_store::{PrimitiveKind, PrimTemplateCommonData}; usecrate::prim_store::interned::*; usecrate::profiler::{self, TransactionProfile}; usecrate::render_task_graph::RenderTaskGraphBuilder; usecrate::renderer::{FullFrameStats, PipelineInfo}; usecrate::resource_cache::ResourceCache; #[cfg(feature = "replay")] usecrate::resource_cache::PlainCacheOwn; #[cfg(feature = "replay")] usecrate::resource_cache::PlainResources; #[cfg(feature = "replay")] usecrate::scene::Scene; usecrate::scene::{BuiltScene, SceneProperties}; usecrate::scene_builder_thread::*; usecrate::spatial_tree::SpatialTree; #[cfg(feature = "replay")] usecrate::spatial_tree::SceneSpatialTree; usecrate::telemetry::Telemetry; #[cfg(feature = "capture")] use serde::Serialize; #[cfg(feature = "replay")] use serde::Deserialize; #[cfg(feature = "replay")] use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use std::{mem, u32}; #[cfg(feature = "capture")] use std::path::PathBuf; #[cfg(feature = "replay")] usecrate::frame_builder::Frame; use core::time::Duration; usecrate::util::{Recycler, VecHelper, drain_filter}; #[cfg(feature = "debugger")] usecrate::debugger::DebugQueryKind;
impl DataStores { /// Returns the local rect for a primitive. For most primitives, this is /// the device-snapped local rect carried on the per-draw header. For /// pictures, the rect is reconstructed from the picture's raster surface /// since it's only known during frame building. pubfn get_local_prim_rect(
&self,
prim_instance: &PrimitiveInstance,
snapped_local_rect: LayoutRect,
pictures: &[PictureInstance],
surfaces: &[SurfaceInfo],
) -> LayoutRect { match prim_instance.kind {
PrimitiveKind::Picture { pic_index, .. } => { let pic = &pictures[pic_index.0];
match pic.raster_config {
Some(RasterConfig { surface_index, ref composite_mode, .. }) => { let surface = &surfaces[surface_index.0];
composite_mode.get_rect(surface, None)
}
None => {
panic!("bug: get_local_prim_rect should not be called for pass-through pictures");
}
}
}
_ => snapped_local_rect,
}
}
/// Returns the local coverage (space occupied) for a primitive. For most /// primitives, this is the device-snapped local rect carried on the /// per-draw header. For pictures, the coverage is reconstructed from the /// picture's raster surface since it's only known during frame building. pubfn get_local_prim_coverage_rect(
&self,
prim_instance: &PrimitiveInstance,
snapped_local_rect: LayoutRect,
pictures: &[PictureInstance],
surfaces: &[SurfaceInfo],
) -> LayoutRect { match prim_instance.kind {
PrimitiveKind::Picture { pic_index, .. } => { let pic = &pictures[pic_index.0];
match pic.raster_config {
Some(RasterConfig { surface_index, ref composite_mode, .. }) => { let surface = &surfaces[surface_index.0];
composite_mode.get_coverage(surface, None)
}
None => {
panic!("bug: get_local_prim_coverage_rect should not be called for pass-through pictures");
}
}
}
_ => snapped_local_rect,
}
}
/// Returns true if this primitive has anti-aliasing enabled. pubfn prim_has_anti_aliasing(
&self,
prim_instance: &PrimitiveInstance,
) -> bool { match prim_instance.kind {
PrimitiveKind::Picture { .. } => { false
}
_ => { self.as_common_data(prim_instance).flags.contains(PrimitiveFlags::ANTIALISED)
}
}
}
struct Document { /// The id of this document
id: DocumentId,
/// Temporary list of removed pipelines received from the scene builder /// thread and forwarded to the renderer.
removed_pipelines: Vec<(PipelineId, DocumentId)>,
view: DocumentView,
/// The id and time of the current frame.
stamp: FrameStamp,
/// The latest built scene, usable to build frames. /// received from the scene builder thread.
scene: BuiltScene,
/// The builder object that prodces frames, kept around to preserve some retained state.
frame_builder: FrameBuilder,
/// Allows graphs of render tasks to be created, and then built into an immutable graph output.
rg_builder: RenderTaskGraphBuilder,
/// A data structure to allow hit testing against rendered frames. This is updated /// every time we produce a fully rendered frame.
hit_tester: Option<Arc<HitTester>>, /// To avoid synchronous messaging we update a shared hit-tester that other threads /// can query.
shared_hit_tester: Arc<SharedHitTester>,
/// Properties that are resolved during frame building and can be changed at any time /// without requiring the scene to be re-built.
dynamic_properties: SceneProperties,
/// Track whether the last built frame is up to date or if it will need to be re-built /// before rendering again.
frame_is_valid: bool,
hit_tester_is_valid: bool,
rendered_frame_is_valid: bool, /// We track this information to be able to display debugging information from the /// renderer.
has_built_scene: bool,
data_stores: DataStores,
/// Retained frame-building version of the spatial tree
spatial_tree: SpatialTree,
/// Contains various vecs of data that is used only during frame building, /// where we want to recycle the memory each new display list, to avoid constantly /// re-allocating and moving memory around.
scratch: ScratchBuffer,
#[cfg(feature = "replay")]
loaded_scene: Scene,
/// Tracks the state of the picture cache tiles that were composited on the previous frame.
prev_composite_descriptor: CompositeDescriptor,
/// Tracks if we need to invalidate dirty rects for this document, due to the picture /// cache slice configuration having changed when a new scene is swapped in.
dirty_rects_are_valid: bool,
// Advance to the next frame. self.stamp.advance();
assert!(self.stamp.frame_id() != FrameId::INVALID, "First frame increment must happen before build_frame()");
let frame = { let frame = self.frame_builder.build(
&mutself.scene,
present,
resource_cache,
&mutself.rg_builder, self.stamp, self.view.scene.device_rect.min,
&self.dynamic_properties,
&mutself.data_stores,
&mutself.scratch,
debug_flags,
tile_caches,
&mutself.spatial_tree, self.dirty_rects_are_valid,
&mutself.profile, // Consume the minimap data. If APZ wants a minimap rendered // on the next frame, it will add new entries to the minimap // data during sampling.
mem::take(&mutself.minimap_data),
chunk_pool,
);
/// Build a frame without changing the state of the current scene. /// /// This is useful to render arbitrary content into to images in /// the resource cache for later use without affecting what is /// currently being displayed. fn process_offscreen_scene(
&mutself, mut txn: OffscreenBuiltScene,
resource_cache: &mut ResourceCache,
chunk_pool: Arc<ChunkPool>,
debug_flags: DebugFlags,
) -> RenderedDocument { letmut profile = TransactionProfile::new(); self.stamp.advance();
let frame = self.frame_builder.build(
&mut txn.scene,
present,
resource_cache,
&mutself.rg_builder, self.stamp, // TODO(nical) self.view.scene.device_rect.min,
&self.dynamic_properties,
&mut data_stores,
&mutself.scratch,
debug_flags,
&mut tile_caches,
&mut spatial_tree, self.dirty_rects_are_valid,
&mut profile, // Consume the minimap data. If APZ wants a minimap rendered // on the next frame, it will add new entries to the minimap // data during sampling.
mem::take(&mutself.minimap_data),
chunk_pool,
);
/// Returns true if the node actually changed position or false otherwise. pubfn set_scroll_offsets(
&mutself,
id: ExternalScrollId,
offsets: Vec<SampledScrollOffset>,
) -> bool { self.spatial_tree.set_scroll_offsets(id, offsets)
}
/// Update the state of tile caches when a new scene is being swapped in to /// the render backend. Retain / reuse existing caches if possible, and /// destroy any now unused caches. fn update_tile_caches_for_new_scene(
&mutself, mut requested_tile_caches: FastHashMap<SliceId, TileCacheParams>,
tile_caches: &mut FastHashMap<SliceId, Box<TileCacheInstance>>,
resource_cache: &mut ResourceCache,
) { letmut new_tile_caches = FastHashMap::default();
new_tile_caches.reserve(requested_tile_caches.len());
// Step through the tile caches that are needed for the new scene, and see // if we have an existing cache that can be reused. for (slice_id, params) in requested_tile_caches.drain() { let tile_cache = match tile_caches.remove(&slice_id) {
Some(mut existing_tile_cache) => { // Found an existing cache - update the cache params and reuse it
existing_tile_cache.prepare_for_new_scene(
params,
resource_cache,
);
existing_tile_cache
}
None => { // No cache exists so create a new one Box::new(TileCacheInstance::new(params))
}
};
new_tile_caches.insert(slice_id, tile_cache);
}
// Replace current tile cache map, and return what was left over, // which are now unused. let unused_tile_caches = mem::replace(
tile_caches,
new_tile_caches,
);
if !unused_tile_caches.is_empty() { // If the slice configuration changed, assume we can't rely on the // current dirty rects for next composite self.dirty_rects_are_valid = false;
// Destroy any native surfaces allocated by these unused caches for (_, tile_cache) in unused_tile_caches {
tile_cache.destroy(resource_cache);
}
}
}
/// The unique id for WR resource identification. /// The namespace_id should start from 1. static NEXT_NAMESPACE_ID: AtomicUsize = AtomicUsize::new(1);
/// The render backend is responsible for transforming high level display lists into /// GPU-friendly work which is then submitted to the renderer in the form of a frame::Frame. /// /// The render backend operates on its own thread. pubstruct RenderBackend {
api_rx: Receiver<ApiMsg>,
result_tx: Sender<ResultMsg>,
scene_tx: Sender<SceneBuilderRequest>,
#[cfg(feature = "capture")] /// If `Some`, do 'sequence capture' logging, recording updated documents, /// frames, etc. This is set only through messages from the scene builder, /// so all control of sequence capture goes through there.
capture_config: Option<CaptureConfig>,
/// A map of tile caches. These are stored in the backend as they are /// persisted between both frame and scenes.
tile_caches: FastHashMap<SliceId, Box<TileCacheInstance>>,
/// The id of the latest PublishDocument
frame_publish_id: FramePublishId,
}
iflet RenderBackendStatus::StopRenderBackend = status { whilelet Ok(msg) = self.api_rx.recv() { match msg {
ApiMsg::SceneBuilderResult(SceneBuilderResult::ExternalEvent(evt)) => { self.notifier.external_event(evt);
}
ApiMsg::SceneBuilderResult(SceneBuilderResult::FlushComplete(tx)) => { // If somebody's blocked waiting for a flush, how did they // trigger the RB thread to shut down? This shouldn't happen // but handle it gracefully anyway.
debug_assert!(false);
tx.send(()).ok();
}
ApiMsg::SceneBuilderResult(SceneBuilderResult::ShutDown(sender)) => {
info!("Recycling stats: {:?}", self.recycler);
status = RenderBackendStatus::ShutDown(sender); break;
}
_ => {},
}
}
}
// Ensure we read everything the scene builder is sending us from // inflight messages, otherwise the scene builder might panic. whilelet Ok(msg) = self.api_rx.try_recv() { match msg {
ApiMsg::SceneBuilderResult(SceneBuilderResult::FlushComplete(tx)) => { // If somebody's blocked waiting for a flush, how did they // trigger the RB thread to shut down? This shouldn't happen // but handle it gracefully anyway.
debug_assert!(false);
tx.send(()).ok();
}
_ => {},
}
}
// Before updating the spatial tree, save the most recently sampled // scroll offsets (which include async deltas). let last_sampled_scroll_offsets = ifself.sampler.is_some() {
Some(doc.spatial_tree.get_last_sampled_scroll_offsets())
} else {
None
};
// If there are any additions or removals of clip modes // during the scene build, apply them to the data store now. // This needs to happen before we build the hit tester. iflet Some(updates) = txn.interner_updates.take() {
doc.data_stores.apply_updates(updates, &mut doc.profile);
}
// Apply the last sampled scroll offsets from the previous scene, // to the current scene. The offsets are identified by scroll ids // which are stable across scenes. This ensures that a hit test, // which could occur in between post-swap hook and the call to // update_document() below, does not observe raw main-thread offsets // from the new scene that don't have async deltas applied to them. iflet Some(last_sampled) = last_sampled_scroll_offsets {
doc.spatial_tree
.apply_last_sampled_scroll_offsets(last_sampled);
}
// Build the hit tester while the APZ lock is held so that its content // is in sync with the gecko APZ tree. if !doc.hit_tester_is_valid {
doc.rebuild_hit_tester();
}
iflet Some(ref tx) = result_tx { let (resume_tx, resume_rx) = single_msg_channel();
tx.send(SceneSwapResult::Complete(resume_tx)).unwrap(); // Block until the post-swap hook has completed on // the scene builder thread. We need to do this before // we can sample from the sampler hook which might happen // in the update_document call below.
resume_rx.recv().ok();
}
for offscreen_scene in txn.offscreen_scenes.drain(..) { self.resource_cache.post_scene_building_update(
txn.resource_updates.take(),
&mut doc.profile,
);
let rendered_document = doc.process_offscreen_scene(
offscreen_scene,
&mutself.resource_cache, self.chunk_pool.clone(), self.debug_flags,
);
let pending_update = self.resource_cache.pending_updates();
let msg = ResultMsg::PublishDocument( self.frame_publish_id,
txn.document_id,
rendered_document,
pending_update,
); self.result_tx.send(msg).unwrap();
self.notifier.new_frame_ready(
txn.document_id, self.frame_publish_id,
¶ms
);
}
} else { // The document was removed while we were building it, skip it. // TODO: we might want to just ensure that removed documents are // always forwarded to the scene builder thread to avoid this case. iflet Some(ref tx) = result_tx {
tx.send(SceneSwapResult::Aborted).unwrap();
} continue;
}
ifself.debug_flags.contains(DebugFlags::DUMP_SPATIAL_TREE) { iflet Some(doc) = self.documents.get(&txn.document_id) { let spatial_tree = doc.spatial_tree.print_to_string(); if !spatial_tree.is_empty() {
eprintln!( "-- WebRender spatial tree ({:?}) --\n{}",
txn.document_id, spatial_tree
);
}
}
}
}
built_frame
}
fn process_api_msg(
&mutself,
msg: ApiMsg,
frame_counter: &mut u32,
) -> RenderBackendStatus { match msg {
ApiMsg::CloneApi(sender) => {
assert!(!self.namespace_alloc_by_client);
sender.send(Self::next_namespace_id()).unwrap();
}
ApiMsg::CloneApiByClient(namespace_id) => {
assert!(self.namespace_alloc_by_client);
debug_assert!(!self.documents.iter().any(|(did, _doc)| did.namespace_id == namespace_id));
}
ApiMsg::AddDocument(document_id, initial_size) => { let document = Document::new(
document_id,
initial_size,
); let old = self.documents.insert(document_id, document);
debug_assert!(old.is_none());
}
ApiMsg::MemoryPressure => { // This is drastic. It will basically flush everything out of the cache, // and the next frame will have to rebuild all of its resources. // We may want to look into something less extreme, but on the other hand this // should only be used in situations where are running low enough on memory // that we risk crashing if we don't do something about it. // The advantage of clearing the cache completely is that it gets rid of any // remaining fragmentation that could have persisted if we kept around the most // recently used resources. self.resource_cache.clear(ClearCache::all());
for (_, doc) in &mutself.documents {
doc.scratch.memory_pressure(); for tile_cache inself.tile_caches.values_mut() {
tile_cache.memory_pressure(&mutself.resource_cache);
}
}
let resource_updates = self.resource_cache.pending_updates(); let msg = ResultMsg::UpdateResources {
resource_updates,
memory_pressure: true,
}; self.result_tx.send(msg).unwrap(); self.notifier.wake_up(false);
return RenderBackendStatus::Continue;
} #[cfg(feature = "debugger")]
DebugCommand::CaptureRenderDoc(..) => { // A single-frame RenderDoc capture can't replay WebRender's // persistent caches (picture tiles, glyph atlas, image cache) // populated in earlier frames. So make the captured frame // re-render everything from scratch: clear cached resources so // glyphs/images re-rasterize and re-upload, and force a full // invalidated rebuild so all picture cache tiles re-rasterize. // Then forward the command so the renderer captures that frame. self.resource_cache.clear(ClearCache::all());
// We don't want to forward this message to the renderer. return RenderBackendStatus::Continue;
}
DebugCommand::SetBatchingLookback(count) => { self.frame_config.batch_lookback_count = count as usize; self.update_frame_builder_config();
// Now that we are likely out of the critical path, purge a few chunks // from the pool. The underlying deallocation can be expensive, especially // with build configurations where all of the memory is zeroed, so we // spread the load over potentially many iterations of the event loop. self.chunk_pool.purge_chunks(2, 3);
/// In certain cases, resources shared by multiple documents have to run /// maintenance operations, like cleaning up unused cache items. In those /// cases, we are forced to build frames for all documents, however we /// may not have a transaction ready for every document - this method /// calls update_document with the details of a fake, nop transaction just /// to force a frame build. fn maybe_force_nop_documents<F>(&mutself,
frame_counter: &mut u32,
document_already_present: F) where
F: Fn(DocumentId) -> bool { ifself.requires_frame_build() { let nop_documents : Vec<DocumentId> = self.documents.keys()
.cloned()
.filter(|key| !document_already_present(*key))
.collect(); letmut built_frame = false; for &document_id in &nop_documents {
built_frame |= self.update_document(
document_id,
Vec::default(),
Vec::default(),
Vec::default(), false, false, false,
RenderReasons::empty(),
None, false,
frame_counter, false,
None);
} match built_frame { true =>
{ #[cfg(feature = "capture")] self.save_capture_sequence()
}
_ => {},
}
}
}
let requested_frame = render_frame || self.frame_config.force_invalidation;
let requires_frame_build = self.requires_frame_build(); let doc = self.documents.get_mut(&document_id).unwrap();
// If we have a sampler, get more frame ops from it and add them // to the transaction. This is a hook to allow the WR user code to // fiddle with things after a potentially long scene build, but just // before rendering. This is useful for rendering with the latest // async transforms. if requested_frame { iflet Some(ref sampler) = self.sampler {
frame_ops.append(&mut sampler.sample(document_id, generated_frame_id));
}
}
doc.has_built_scene |= has_built_scene;
// TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used // for something wrench specific and we should remove it. letmut scroll = false; for frame_msg in frame_ops { let op = doc.process_frame_msg(frame_msg);
scroll |= op.scroll;
}
for update in &resource_updates { iflet ResourceUpdate::UpdateImage(..) = update {
doc.frame_is_valid = false;
}
}
if doc.dynamic_properties.flush_pending_updates() {
doc.frame_is_valid = false;
doc.hit_tester_is_valid = false;
}
if !doc.can_render() { // TODO: this happens if we are building the first scene asynchronously and // scroll at the same time. we should keep track of the fact that we skipped // composition here and do it as soon as we receive the scene.
render_frame = false;
}
// Avoid re-building the frame if the current built frame is still valid. // However, if the resource_cache requires a frame build, _always_ do that, unless // doc.can_render() is false, as in that case a frame build can't happen anyway. // We want to ensure we do this because even if the doc doesn't have pixels it // can still try to access stale texture cache items. let build_frame = (render_frame && !doc.frame_is_valid && doc.has_pixels()) ||
(requires_frame_build && doc.can_render());
// Request composite is true when we want to composite frame even when // there is no frame update. This happens when video frame is updated under // external image with NativeTexture or when platform requested to composite frame. if invalidate_rendered_frame {
doc.rendered_frame_is_valid = false; if doc.scene.config.compositor_kind.should_redraw_on_invalidation() { let msg = ResultMsg::ForceRedraw; self.result_tx.send(msg).unwrap();
}
}
if build_frame { if !requested_frame { // When we don't request a frame, present defaults to false. If for some // reason we did not request the frame but must render it anyway, set // present to true (it was false as a byproduct of expecting we wouldn't // produce the frame but we did not explicitly opt out of it).
present = true;
}
if start_time.is_some() {
Telemetry::record_time_to_frame_build(Duration::from_nanos(zeitstempel::now() - start_time.unwrap()));
}
profile_scope!("generate frame");
*frame_counter += 1;
// borrow ck hack for profile_counters let (pending_update, mut rendered_document) = { let timer_id = Telemetry::start_framebuild_time();
let pending_update = self.resource_cache.pending_updates();
(pending_update, rendered_document)
};
// Invalidate dirty rects if the compositing config has changed significantly
rendered_document
.frame
.composite_state
.update_dirty_rect_validity(&doc.prev_composite_descriptor);
// Build a small struct that represents the state of the tiles to be composited. let composite_descriptor = rendered_document
.frame
.composite_state
.descriptor
.clone();
// If there are texture cache updates to apply, or if the produced // frame is not a no-op, or the compositor state has changed, // then we cannot skip compositing this frame. if !pending_update.is_nop() ||
!rendered_document.frame.is_nop() ||
composite_descriptor != doc.prev_composite_descriptor {
doc.rendered_frame_is_valid = false;
}
doc.prev_composite_descriptor = composite_descriptor;
let update_doc_time = profiler::ns_to_ms(zeitstempel::now() - update_doc_start);
rendered_document.profile.set(profiler::UPDATE_DOCUMENT_TIME, update_doc_time);
let msg = ResultMsg::PublishPipelineInfo(doc.updated_pipeline_info()); self.result_tx.send(msg).unwrap();
// Publish the frame self.frame_publish_id.advance(); let msg = ResultMsg::PublishDocument( self.frame_publish_id,
document_id,
rendered_document,
pending_update,
); self.result_tx.send(msg).unwrap();
} elseif requested_frame { // WR-internal optimization to avoid doing a bunch of render work if // there's no pixels. We still want to pretend to render and request // a render to make sure that the callbacks (particularly the // new_frame_ready callback below) has the right flags. let msg = ResultMsg::PublishPipelineInfo(doc.updated_pipeline_info()); self.result_tx.send(msg).unwrap();
}
if !notifications.is_empty() { self.result_tx.send(ResultMsg::AppendNotificationRequests(notifications)).unwrap();
}
// Always forward the transaction to the renderer if a frame was requested, // otherwise gecko can get into a state where it waits (forever) for the // transaction to complete before sending new work. if requested_frame { // If rendered frame is already valid, there is no need to render frame. if doc.rendered_frame_is_valid {
render_frame = false;
} elseif render_frame {
doc.rendered_frame_is_valid = true;
} let params = api::FrameReadyParams {
present,
render: render_frame,
scrolled: scroll,
tracked,
}; self.notifier.new_frame_ready(document_id, self.frame_publish_id, ¶ms);
}
if !doc.hit_tester_is_valid {
doc.rebuild_hit_tester();
}
// Send a message to report memory on the scene-builder thread, which // will add its report to this one and send the result back to the original // thread waiting on the request. self.send_backend_message(
SceneBuilderRequest::ReportMemory(report, tx)
);
}
//TODO: write down doc's pipeline info? // it has `pipeline_epoch_map`, // which may capture necessary details for some cases. let file_name = format!("frame-{}-{}", id.namespace_id.0, id.id);
config.serialize_for_frame(&rendered_document.frame, file_name); let file_name = format!("spatial-{}-{}", id.namespace_id.0, id.id);
config.serialize_tree_for_frame(&doc.spatial_tree, file_name); let file_name = format!("built-primitives-{}-{}", id.namespace_id.0, id.id);
config.serialize_for_frame(&doc.scene.prim_store, file_name); let file_name = format!("built-clips-{}-{}", id.namespace_id.0, id.id);
config.serialize_for_frame(&doc.scene.clip_store, file_name); let file_name = format!("scratch-{}-{}", id.namespace_id.0, id.id);
config.serialize_for_frame(&doc.scratch.primitive, file_name); let file_name = format!("render-tasks-{}-{}.svg", id.namespace_id.0, id.id); letmut render_tasks_file = fs::File::create(&config.file_path_for_frame(file_name, "svg"))
.expect("Failed to open the SVG file.");
dump_render_tasks_as_svg(
&rendered_document.frame.render_tasks,
&mut render_tasks_file
).unwrap();
let file_name = format!("texture-cache-color-linear-{}-{}.svg", id.namespace_id.0, id.id); letmut texture_file = fs::File::create(&config.file_path_for_frame(file_name, "svg"))
.expect("Failed to open the SVG file."); self.resource_cache.texture_cache.dump_color8_linear_as_svg(&mut texture_file).unwrap();
let file_name = format!("texture-cache-color8-glyphs-{}-{}.svg", id.namespace_id.0, id.id); letmut texture_file = fs::File::create(&config.file_path_for_frame(file_name, "svg"))
.expect("Failed to open the SVG file."); self.resource_cache.texture_cache.dump_color8_glyphs_as_svg(&mut texture_file).unwrap();
let file_name = format!("texture-cache-alpha8-glyphs-{}-{}.svg", id.namespace_id.0, id.id); letmut texture_file = fs::File::create(&config.file_path_for_frame(file_name, "svg"))
.expect("Failed to open the SVG file."); self.resource_cache.texture_cache.dump_alpha8_glyphs_as_svg(&mut texture_file).unwrap();
let file_name = format!("texture-cache-alpha8-linear-{}-{}.svg", id.namespace_id.0, id.id); letmut texture_file = fs::File::create(&config.file_path_for_frame(file_name, "svg"))
.expect("Failed to open the SVG file."); self.resource_cache.texture_cache.dump_alpha8_linear_as_svg(&mut texture_file).unwrap();
}
let data_stores_name = format!("data-stores-{}-{}", id.namespace_id.0, id.id);
config.serialize_for_frame(&doc.data_stores, data_stores_name);
let frame_spatial_tree_name = format!("frame-spatial-tree-{}-{}", id.namespace_id.0, id.id);
config.serialize_for_frame::<SpatialTree, _>(&doc.spatial_tree, frame_spatial_tree_name);
let properties_name = format!("properties-{}-{}", id.namespace_id.0, id.id);
config.serialize_for_frame(&doc.dynamic_properties, properties_name);
}
if config.bits.contains(CaptureBits::FRAME) { // TODO: there is no guarantee that we won't hit this case, but we want to // report it here if we do. If we don't, it will simply crash in // Renderer::render_impl and give us less information about the source.
assert!(!self.requires_frame_build(), "Caches were cleared during a capture.");
}
#[cfg(feature = "replay")] fn load_capture(
&mutself, mut config: CaptureConfig,
) {
debug!("capture: loading {:?}", config.frame_root()); let backend = config.deserialize_for_frame::<PlainRenderBackend, _>("backend")
.expect("Unable to open backend.ron");
// If this is a capture sequence, then the ID will be non-zero, and won't // match what is loaded, but for still captures, the ID will be zero. let first_load = backend.resource_sequence_id == 0; ifself.loaded_resource_sequence_id != backend.resource_sequence_id || first_load { // FIXME(aosmond): We clear the documents because when we update the // resource cache, we actually wipe and reload, because we don't // know what is the same and what has changed. If we were to keep as // much of the resource cache state as possible, we could avoid // flushing the document state (which has its own dependecies on the // cache). // // FIXME(aosmond): If we try to load the next capture in the // sequence too quickly, we may lose resources we depend on in the // current frame. This can cause panics. Ideally we would not // advance to the next frame until the FrameRendered event for all // of the pipelines. self.documents.clear();
let plain_resources = config.deserialize_for_resource::<PlainResources, _>("plain-resources")
.expect("Unable to open plain-resources.ron"); let caches_maybe = config.deserialize_for_resource::<PlainCacheOwn, _>("resource_cache");
// Note: it would be great to have `RenderBackend` to be split // rather explicitly on what's used before and after scene building // so that, for example, we never miss anything in the code below:
let plain_externals = self.resource_cache.load_capture(
plain_resources,
caches_maybe,
&config,
);
let msg_load = ResultMsg::DebugOutput(
DebugOutput::LoadCapture(config.clone(), plain_externals)
); self.result_tx.send(msg_load).unwrap();
}
self.frame_config = backend.frame_config;
letmut scenes_to_build = Vec::new();
for (id, view) in backend.documents {
debug!("\tdocument {:?}", id); let scene_name = format!("scene-{}-{}", id.namespace_id.0, id.id); let scene = config.deserialize_for_scene::<Scene, _>(&scene_name)
.expect(&format!("Unable to open {}.ron", scene_name));
let scene_spatial_tree_name = format!("scene-spatial-tree-{}-{}", id.namespace_id.0, id.id); let scene_spatial_tree = config.deserialize_for_scene::<SceneSpatialTree, _>(&scene_spatial_tree_name)
.expect(&format!("Unable to open {}.ron", scene_spatial_tree_name));
let interners_name = format!("interners-{}-{}", id.namespace_id.0, id.id); let interners = config.deserialize_for_scene::<Interners, _>(&interners_name)
.expect(&format!("Unable to open {}.ron", interners_name));
let data_stores_name = format!("data-stores-{}-{}", id.namespace_id.0, id.id); let data_stores = config.deserialize_for_frame::<DataStores, _>(&data_stores_name)
.expect(&format!("Unable to open {}.ron", data_stores_name));
let properties_name = format!("properties-{}-{}", id.namespace_id.0, id.id); let properties = config.deserialize_for_frame::<SceneProperties, _>(&properties_name)
.expect(&format!("Unable to open {}.ron", properties_name));
let frame_spatial_tree_name = format!("frame-spatial-tree-{}-{}", id.namespace_id.0, id.id); let frame_spatial_tree = config.deserialize_for_frame::<SpatialTree, _>(&frame_spatial_tree_name)
.expect(&format!("Unable to open {}.ron", frame_spatial_tree_name));
// Update the document if it still exists, rather than replace it entirely. // This allows us to preserve state information such as the frame stamp, // which is necessary for cache sanity. matchself.documents.entry(id) {
Occupied(entry) => { let doc = entry.into_mut();
doc.view = view;
doc.loaded_scene = scene.clone();
doc.data_stores = data_stores;
doc.spatial_tree = frame_spatial_tree;
doc.dynamic_properties = properties;
doc.frame_is_valid = false;
doc.rendered_frame_is_valid = false;
doc.has_built_scene = false;
doc.hit_tester_is_valid = false;
}
Vacant(entry) => { let doc = Document {
id,
scene: BuiltScene::empty(),
removed_pipelines: Vec::new(),
view,
stamp: FrameStamp::first(id),
frame_builder: FrameBuilder::new(),
dynamic_properties: properties,
hit_tester: None,
shared_hit_tester: Arc::new(SharedHitTester::new()),
frame_is_valid: false,
hit_tester_is_valid: false,
rendered_frame_is_valid: false,
has_built_scene: false,
data_stores,
scratch: ScratchBuffer::default(),
spatial_tree: frame_spatial_tree,
minimap_data: FastHashMap::default(),
loaded_scene: scene.clone(),
prev_composite_descriptor: CompositeDescriptor::empty(),
dirty_rects_are_valid: false,
profile: TransactionProfile::new(),
rg_builder: RenderTaskGraphBuilder::new(),
frame_stats: None,
};
entry.insert(doc);
}
};
let frame_name = format!("frame-{}-{}", id.namespace_id.0, id.id); let frame = config.deserialize_for_frame::<Frame, _>(frame_name); let build_frame = match frame {
Some(frame) => {
info!("\tloaded a built frame with {} passes", frame.passes.len());
// We deserialized the state of the frame so we don't want to build // it (but we do want to update the scene builder's state) false
}
None => true,
};
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.