Bug 1561367 - Implement initial support for capturing multiple frames. r=kvark

This patch adds support for the capture and replaying of multiple frames
during normal operation of Firefox. Ctrl + Shift + 6 starts capturing
and pressing it again stops capturing. It attempts to capture the minimum
amount of data required to replay a sequence for debugging purposes.

There are several known limitations, particularly surrounding replaying
when transitioning between snapshots of the resource cache. It will
reload the entire document set, causing greater delay between frames.
Should you advance too quickly, it may also panic due to a race between
the current frame still being generated, and the new frame resetting the
resource cache state. These should be resolved with time, and the
current implementation should be workable to at least capture/debug most
animated issues with some effort.

It also adds support for loading dynamic properties which is necessary
for accurate replaying of a captured frame (sequence or individual)
which are in the middle of an animation.

Differential Revision: https://phabricator.services.mozilla.com/D59755
This commit is contained in:
Andrew Osmond 2020-04-20 12:53:03 +00:00
Родитель 388d8ca712
Коммит 69a83fc13a
23 изменённых файлов: 866 добавлений и 248 удалений

Просмотреть файл

@ -15,6 +15,9 @@ var gGfxUtils = {
init() {
if (Services.prefs.getBoolPref("gfx.webrender.enable-capture")) {
document.getElementById("wrCaptureCmd").removeAttribute("disabled");
document
.getElementById("wrToggleCaptureSequenceCmd")
.removeAttribute("disabled");
}
},
@ -31,6 +34,13 @@ var gGfxUtils = {
webrenderCapture() {
window.windowUtils.wrCapture();
},
/**
* Trigger a WebRender capture of the current state and future state
* into a local folder. If called again, it will stop capturing.
*/
toggleWebrenderCaptureSequence() {
window.windowUtils.wrToggleCaptureSequence();
},
/**
* Toggle transaction logging to text file.

Просмотреть файл

@ -103,6 +103,7 @@
<command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
<command id="wrCaptureCmd" oncommand="gGfxUtils.webrenderCapture();" disabled="true"/>
<command id="wrToggleCaptureSequenceCmd" oncommand="gGfxUtils.toggleWebrenderCaptureSequence();" disabled="true"/>
#ifdef NIGHTLY_BUILD
<command id="windowRecordingCmd" oncommand="gGfxUtils.toggleWindowRecording();"/>
<command id="wrTransactionLoggingCmd" oncommand="gGfxUtils.toggleTransactionLogging();"/>
@ -338,6 +339,13 @@
key="#" modifiers="control"
#endif
command="wrCaptureCmd"/>
<key id="key_wrToggleCaptureSequenceCmd"
#ifdef XP_MACOSX
key="6" modifiers="control,shift"
#else
key="^" modifiers="control"
#endif
command="wrToggleCaptureSequenceCmd"/>
#ifdef NIGHTLY_BUILD
<key id="key_windowRecordingCmd"
#ifdef XP_MACOSX

Просмотреть файл

@ -4113,6 +4113,14 @@ nsDOMWindowUtils::WrCapture() {
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::WrToggleCaptureSequence() {
if (WebRenderBridgeChild* wrbc = GetWebRenderBridge()) {
wrbc->ToggleCaptureSequence();
}
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::SetCompositionRecording(bool aValue, Promise** aOutPromise) {
return aValue ? StartCompositionRecording(aOutPromise)

Просмотреть файл

@ -1945,6 +1945,14 @@ interface nsIDOMWindowUtils : nsISupports {
*/
void wrCapture();
/**
* Start capturing the contents of the current WebRender frame and
* save them to a folder relative to the current working directory,
* as well as any frames generated from this point onward. If called
* again, it will stop capturing.
*/
void wrToggleCaptureSequence();
/**
* Toggle recording of composition on and off.
*

Просмотреть файл

@ -74,6 +74,8 @@ parent:
async ScheduleComposite();
// Save the frame capture to disk
async Capture();
// Start capturing each frame and save to disk, and if already started, stop.
async ToggleCaptureSequence();
// Enable/Disable transaction logging
async SetTransactionLogging(bool aValue);

Просмотреть файл

@ -616,6 +616,9 @@ void WebRenderBridgeChild::DeallocResourceShmem(RefCountedShmem& aShm) {
}
void WebRenderBridgeChild::Capture() { this->SendCapture(); }
void WebRenderBridgeChild::ToggleCaptureSequence() {
this->SendToggleCaptureSequence();
}
void WebRenderBridgeChild::SetTransactionLogging(bool aValue) {
this->SendSetTransactionLogging(aValue);

Просмотреть файл

@ -182,6 +182,7 @@ class WebRenderBridgeChild final : public PWebRenderBridgeChild,
void DeallocResourceShmem(RefCountedShmem& aShm);
void Capture();
void ToggleCaptureSequence();
void SetTransactionLogging(bool aValue);
private:

Просмотреть файл

@ -2156,6 +2156,13 @@ mozilla::ipc::IPCResult WebRenderBridgeParent::RecvCapture() {
return IPC_OK();
}
mozilla::ipc::IPCResult WebRenderBridgeParent::RecvToggleCaptureSequence() {
if (!mDestroyed) {
mApis[wr::RenderRoot::Default]->ToggleCaptureSequence();
}
return IPC_OK();
}
mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetTransactionLogging(
const bool& aValue) {
if (!mDestroyed) {

Просмотреть файл

@ -193,6 +193,7 @@ class WebRenderBridgeParent final
mozilla::ipc::IPCResult RecvInvalidateRenderedFrame() override;
mozilla::ipc::IPCResult RecvScheduleComposite() override;
mozilla::ipc::IPCResult RecvCapture() override;
mozilla::ipc::IPCResult RecvToggleCaptureSequence() override;
mozilla::ipc::IPCResult RecvSetTransactionLogging(const bool&) override;
mozilla::ipc::IPCResult RecvSyncWithCompositor() override;

Просмотреть файл

@ -375,6 +375,7 @@ WebRenderAPI::WebRenderAPI(wr::DocumentHandle* aHandle, wr::WindowId aId,
mUseANGLE(aUseANGLE),
mUseDComp(aUseDComp),
mUseTripleBuffering(aUseTripleBuffering),
mCaptureSequence(false),
mSyncHandle(aSyncHandle),
mRenderRoot(aRenderRoot) {}
@ -648,11 +649,22 @@ void WebRenderAPI::WaitFlushed() {
void WebRenderAPI::Capture() {
// see CaptureBits
// SCENE | FRAME | TILE_CACHE
uint8_t bits = 7; // TODO: get from JavaScript
uint8_t bits = 15; // TODO: get from JavaScript
const char* path = "wr-capture"; // TODO: get from JavaScript
wr_api_capture(mDocHandle, path, bits);
}
void WebRenderAPI::ToggleCaptureSequence() {
mCaptureSequence = !mCaptureSequence;
if (mCaptureSequence) {
uint8_t bits = 9; // TODO: get from JavaScript
const char* path = "wr-capture-sequence"; // TODO: get from JavaScript
wr_api_start_capture_sequence(mDocHandle, path, bits);
} else {
wr_api_stop_capture_sequence(mDocHandle);
}
}
void WebRenderAPI::SetTransactionLogging(bool aValue) {
wr_api_set_transaction_logging(mDocHandle, aValue);
}

Просмотреть файл

@ -279,6 +279,9 @@ class WebRenderAPI final {
layers::SyncHandle GetSyncHandle() const { return mSyncHandle; }
void Capture();
void ToggleCaptureSequence();
void SetTransactionLogging(bool aValue);
void SetCompositionRecorder(
@ -322,6 +325,7 @@ class WebRenderAPI final {
bool mUseANGLE;
bool mUseDComp;
bool mUseTripleBuffering;
bool mCaptureSequence;
layers::SyncHandle mSyncHandle;
wr::RenderRoot mRenderRoot;

Просмотреть файл

@ -2003,9 +2003,8 @@ pub extern "C" fn wr_resource_updates_add_raw_font(
txn.add_raw_font(key, bytes.flush_into_vec(), index);
}
#[no_mangle]
pub extern "C" fn wr_api_capture(dh: &mut DocumentHandle, path: *const c_char, bits_raw: u32) {
use std::fs::{create_dir_all, File};
fn generate_capture_path(path: *const c_char) -> Option<PathBuf> {
use std::fs::{File, create_dir_all};
use std::io::Write;
let cstr = unsafe { CStr::from_ptr(path) };
@ -2053,12 +2052,34 @@ pub extern "C" fn wr_api_capture(dh: &mut DocumentHandle, path: *const c_char, b
}
Err(e) => {
warn!("Unable to create path '{:?}' for capture: {:?}", path, e);
return;
return None
}
}
let bits = CaptureBits::from_bits(bits_raw as _).unwrap();
dh.api.save_capture(path, bits);
Some(path)
}
#[no_mangle]
pub extern "C" fn wr_api_capture(dh: &mut DocumentHandle, path: *const c_char, bits_raw: u32) {
if let Some(path) = generate_capture_path(path) {
let bits = CaptureBits::from_bits(bits_raw as _).unwrap();
dh.api.save_capture(path, bits);
}
}
#[no_mangle]
pub extern "C" fn wr_api_start_capture_sequence(dh: &mut DocumentHandle, path: *const c_char, bits_raw: u32) {
if let Some(path) = generate_capture_path(path) {
let bits = CaptureBits::from_bits(bits_raw as _).unwrap();
dh.api.start_capture_sequence(path, bits);
}
}
#[no_mangle]
pub extern "C" fn wr_api_stop_capture_sequence(dh: &mut DocumentHandle) {
let border = "--------------------------\n";
warn!("{} Stop capturing WR state\n{}", &border, &border);
dh.api.stop_capture_sequence();
}
#[no_mangle]

Просмотреть файл

@ -21,6 +21,12 @@ use serde;
pub struct CaptureConfig {
pub root: PathBuf,
pub bits: CaptureBits,
/// Scene sequence ID when capturing multiple frames. Zero for a single frame capture.
pub scene_id: u32,
/// Frame sequence ID when capturing multiple frames. Zero for a single frame capture.
pub frame_id: u32,
/// Resource sequence ID when capturing multiple frames. Zero for a single frame capture.
pub resource_id: u32,
#[cfg(feature = "capture")]
pretty: ron::ser::PrettyConfig,
}
@ -31,6 +37,9 @@ impl CaptureConfig {
CaptureConfig {
root,
bits,
scene_id: 0,
frame_id: 0,
resource_id: 0,
#[cfg(feature = "capture")]
pretty: ron::ser::PrettyConfig {
enumerate_arrays: true,
@ -40,35 +49,111 @@ impl CaptureConfig {
}
#[cfg(feature = "capture")]
pub fn file_path<P>(&self, name: P, ext: &str) -> PathBuf
where P: AsRef<Path> {
self.root.join(name).with_extension(ext)
pub fn prepare_scene(&mut self) {
use std::fs::create_dir_all;
self.scene_id += 1;
let _ = create_dir_all(&self.scene_root());
}
#[cfg(feature = "capture")]
pub fn serialize<T, P>(&self, data: &T, name: P)
pub fn prepare_frame(&mut self) {
use std::fs::create_dir_all;
self.frame_id += 1;
let _ = create_dir_all(&self.frame_root());
}
#[cfg(feature = "capture")]
pub fn prepare_resource(&mut self) {
use std::fs::create_dir_all;
self.resource_id += 1;
let _ = create_dir_all(&self.resource_root());
}
#[cfg(any(feature = "capture", feature = "replay"))]
pub fn scene_root(&self) -> PathBuf {
if self.scene_id > 0 {
let path = format!("scenes/{:05}", self.scene_id);
self.root.join(path)
} else {
self.root.clone()
}
}
#[cfg(any(feature = "capture", feature = "replay"))]
pub fn frame_root(&self) -> PathBuf {
if self.frame_id > 0 {
let path = format!("frames/{:05}", self.frame_id);
self.scene_root().join(path)
} else {
self.root.clone()
}
}
#[cfg(any(feature = "capture", feature = "replay"))]
pub fn resource_root(&self) -> PathBuf {
if self.resource_id > 0 {
let path = format!("resources/{:05}", self.resource_id);
self.root.join(path)
} else {
self.root.clone()
}
}
#[cfg(feature = "capture")]
pub fn serialize_for_scene<T, P>(&self, data: &T, name: P)
where
T: serde::Serialize,
P: AsRef<Path>,
{
self.serialize(data, self.scene_root(), name)
}
#[cfg(feature = "capture")]
pub fn serialize_for_frame<T, P>(&self, data: &T, name: P)
where
T: serde::Serialize,
P: AsRef<Path>,
{
self.serialize(data, self.frame_root(), name)
}
#[cfg(feature = "capture")]
pub fn serialize_for_resource<T, P>(&self, data: &T, name: P)
where
T: serde::Serialize,
P: AsRef<Path>,
{
self.serialize(data, self.resource_root(), name)
}
#[cfg(feature = "capture")]
pub fn file_path_for_frame<P>(&self, name: P, ext: &str) -> PathBuf
where P: AsRef<Path> {
self.frame_root().join(name).with_extension(ext)
}
#[cfg(feature = "capture")]
fn serialize<T, P>(&self, data: &T, path: PathBuf, name: P)
where
T: serde::Serialize,
P: AsRef<Path>,
{
use std::io::Write;
let ron = ron::ser::to_string_pretty(data, self.pretty.clone())
.unwrap();
let path = self.file_path(name, "ron");
let mut file = File::create(path)
let mut file = File::create(path.join(name).with_extension("ron"))
.unwrap();
write!(file, "{}\n", ron)
.unwrap();
}
#[cfg(feature = "capture")]
pub fn serialize_tree<T, P>(&self, data: &T, name: P)
fn serialize_tree<T, P>(data: &T, root: PathBuf, name: P)
where
T: PrintableTree,
P: AsRef<Path>
{
let path = self.root
let path = root
.join(name)
.with_extension("tree");
let file = File::create(path)
@ -77,8 +162,17 @@ impl CaptureConfig {
data.print_with(&mut pt);
}
#[cfg(feature = "capture")]
pub fn serialize_tree_for_frame<T, P>(&self, data: &T, name: P)
where
T: PrintableTree,
P: AsRef<Path>
{
Self::serialize_tree(data, self.frame_root(), name)
}
#[cfg(feature = "replay")]
pub fn deserialize<T, P>(root: &PathBuf, name: P) -> Option<T>
fn deserialize<T, P>(root: &PathBuf, name: P) -> Option<T>
where
T: for<'a> serde::Deserialize<'a>,
P: AsRef<Path>,
@ -99,6 +193,33 @@ impl CaptureConfig {
}
}
#[cfg(feature = "replay")]
pub fn deserialize_for_scene<T, P>(&self, name: P) -> Option<T>
where
T: for<'a> serde::Deserialize<'a>,
P: AsRef<Path>,
{
Self::deserialize(&self.scene_root(), name)
}
#[cfg(feature = "replay")]
pub fn deserialize_for_frame<T, P>(&self, name: P) -> Option<T>
where
T: for<'a> serde::Deserialize<'a>,
P: AsRef<Path>,
{
Self::deserialize(&self.frame_root(), name)
}
#[cfg(feature = "replay")]
pub fn deserialize_for_resource<T, P>(&self, name: P) -> Option<T>
where
T: for<'a> serde::Deserialize<'a>,
P: AsRef<Path>,
{
Self::deserialize(&self.resource_root(), name)
}
#[cfg(feature = "png")]
pub fn save_png(
path: PathBuf, size: DeviceIntSize, format: ImageFormat, stride: Option<i32>, data: &[u8],

Просмотреть файл

@ -540,7 +540,7 @@ pub enum DebugOutput {
#[cfg(feature = "capture")]
SaveCapture(CaptureConfig, Vec<ExternalCaptureImage>),
#[cfg(feature = "replay")]
LoadCapture(PathBuf, Vec<PlainExternalImage>),
LoadCapture(CaptureConfig, Vec<PlainExternalImage>),
}
#[allow(dead_code)]

Просмотреть файл

@ -16,11 +16,13 @@ use api::{NotificationRequest, Checkpoint, QualitySettings};
use api::{ClipIntern, FilterDataIntern, PrimitiveKeyKind};
use api::channel::Payload;
use api::units::*;
#[cfg(feature = "capture")]
#[cfg(any(feature = "capture", feature = "replay"))]
use api::CaptureBits;
#[cfg(feature = "replay")]
use api::CapturedDocument;
use crate::spatial_tree::SpatialNodeIndex;
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::capture::CaptureConfig;
use crate::composite::{CompositorKind, CompositeDescriptor};
#[cfg(feature = "debugger")]
use crate::debug_server;
@ -43,7 +45,7 @@ use crate::renderer::{AsyncPropertySampler, PipelineInfo};
use crate::resource_cache::ResourceCache;
#[cfg(feature = "replay")]
use crate::resource_cache::PlainCacheOwn;
#[cfg(any(feature = "capture", feature = "replay"))]
#[cfg(feature = "replay")]
use crate::resource_cache::PlainResources;
#[cfg(feature = "replay")]
use crate::scene::Scene;
@ -53,6 +55,8 @@ use crate::scene_builder_thread::*;
use serde::{Serialize, Deserialize};
#[cfg(feature = "debugger")]
use serde_json;
#[cfg(feature = "replay")]
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
@ -741,7 +745,7 @@ struct PlainRenderBackend {
default_device_pixel_ratio: f32,
frame_config: FrameBuilderConfig,
documents: FastHashMap<DocumentId, DocumentView>,
resources: PlainResources,
resource_sequence_id: u32,
}
/// The render backend is responsible for transforming high level display lists into
@ -774,6 +778,10 @@ pub struct RenderBackend {
namespace_alloc_by_client: bool,
recycler: Recycler,
#[cfg(feature = "capture")]
capture_config: Option<CaptureConfig>,
#[cfg(feature = "replay")]
loaded_resource_sequence_id: u32,
}
impl RenderBackend {
@ -814,6 +822,10 @@ impl RenderBackend {
debug_flags,
namespace_alloc_by_client,
recycler: Recycler::new(),
#[cfg(feature = "capture")]
capture_config: None,
#[cfg(feature = "replay")]
loaded_resource_sequence_id: 0,
}
}
@ -927,97 +939,38 @@ impl RenderBackend {
while let Ok(msg) = self.scene_rx.try_recv() {
match msg {
SceneBuilderResult::Transactions(mut txns, result_tx) => {
self.prepare_for_frames();
self.maybe_force_nop_documents(
SceneBuilderResult::Transactions(txns, result_tx) => {
self.process_transaction(
txns,
result_tx,
&mut frame_counter,
&mut profile_counters,
|document_id| txns.iter().any(|txn| txn.document_id == document_id));
for mut txn in txns.drain(..) {
let has_built_scene = txn.built_scene.is_some();
if let Some(timings) = txn.timings {
if has_built_scene {
profile_counters.scene_changed = true;
}
profile_counters.txn.set(
timings.builder_start_time_ns,
timings.builder_end_time_ns,
timings.send_time_ns,
timings.scene_build_start_time_ns,
timings.scene_build_end_time_ns,
timings.display_list_len,
);
);
self.bookkeep_after_frames();
},
#[cfg(feature = "capture")]
SceneBuilderResult::CapturedTransactions(txns, capture_config, result_tx) => {
if let Some(ref mut old_config) = self.capture_config {
assert!(old_config.scene_id <= capture_config.scene_id);
if old_config.scene_id < capture_config.scene_id {
old_config.scene_id = capture_config.scene_id;
old_config.frame_id = 0;
}
if let Some(doc) = self.documents.get_mut(&txn.document_id) {
doc.removed_pipelines.append(&mut txn.removed_pipelines);
if let Some(built_scene) = txn.built_scene.take() {
doc.new_async_scene_ready(
built_scene,
&mut self.recycler,
);
}
// 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.
if let Some(updates) = txn.interner_updates.take() {
#[cfg(feature = "capture")]
{
if self.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) {
self.tile_cache_logger.serialize_updates(&updates);
}
}
doc.data_stores.apply_updates(updates, &mut profile_counters);
}
// 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();
}
if let Some(ref tx) = result_tx {
let (resume_tx, resume_rx) = 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();
}
} 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.
if let Some(ref tx) = result_tx {
tx.send(SceneSwapResult::Aborted).unwrap();
}
continue;
}
self.resource_cache.add_rasterized_blob_images(
txn.rasterized_blobs.take(),
&mut profile_counters.resources.texture_cache,
);
self.update_document(
txn.document_id,
txn.resource_updates.take(),
txn.frame_ops.take(),
txn.notifications.take(),
txn.render_frame,
txn.invalidate_rendered_frame,
&mut frame_counter,
&mut profile_counters,
has_built_scene,
);
} else {
self.capture_config = Some(capture_config);
}
let built_frame = self.process_transaction(
txns,
result_tx,
&mut frame_counter,
&mut profile_counters,
);
if built_frame {
self.save_capture_sequence();
}
self.bookkeep_after_frames();
},
SceneBuilderResult::FlushComplete(tx) => {
@ -1088,6 +1041,107 @@ impl RenderBackend {
}
}
fn process_transaction(
&mut self,
mut txns: Vec<Box<BuiltTransaction>>,
result_tx: Option<Sender<SceneSwapResult>>,
frame_counter: &mut u32,
profile_counters: &mut BackendProfileCounters,
) -> bool {
self.prepare_for_frames();
self.maybe_force_nop_documents(
frame_counter,
profile_counters,
|document_id| txns.iter().any(|txn| txn.document_id == document_id));
let mut built_frame = false;
for mut txn in txns.drain(..) {
let has_built_scene = txn.built_scene.is_some();
if let Some(timings) = txn.timings {
if has_built_scene {
profile_counters.scene_changed = true;
}
profile_counters.txn.set(
timings.builder_start_time_ns,
timings.builder_end_time_ns,
timings.send_time_ns,
timings.scene_build_start_time_ns,
timings.scene_build_end_time_ns,
timings.display_list_len,
);
}
if let Some(doc) = self.documents.get_mut(&txn.document_id) {
doc.removed_pipelines.append(&mut txn.removed_pipelines);
if let Some(built_scene) = txn.built_scene.take() {
doc.new_async_scene_ready(
built_scene,
&mut self.recycler,
);
}
// 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.
if let Some(updates) = txn.interner_updates.take() {
#[cfg(feature = "capture")]
{
if self.debug_flags.contains(DebugFlags::TILE_CACHE_LOGGING_DBG) {
self.tile_cache_logger.serialize_updates(&updates);
}
}
doc.data_stores.apply_updates(updates, profile_counters);
}
// 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();
}
if let Some(ref tx) = result_tx {
let (resume_tx, resume_rx) = 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();
}
} 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.
if let Some(ref tx) = result_tx {
tx.send(SceneSwapResult::Aborted).unwrap();
}
continue;
}
self.resource_cache.add_rasterized_blob_images(
txn.rasterized_blobs.take(),
&mut profile_counters.resources.texture_cache,
);
built_frame |= self.update_document(
txn.document_id,
txn.resource_updates.take(),
txn.frame_ops.take(),
txn.notifications.take(),
txn.render_frame,
txn.invalidate_rendered_frame,
frame_counter,
profile_counters,
has_built_scene,
);
}
built_frame
}
fn process_api_msg(
&mut self,
msg: ApiMsg,
@ -1218,12 +1272,28 @@ impl RenderBackend {
let output = self.save_capture(root, bits, profile_counters);
ResultMsg::DebugOutput(output)
},
#[cfg(feature = "capture")]
DebugCommand::StartCaptureSequence(root, bits) => {
self.start_capture_sequence(root, bits);
return RenderBackendStatus::Continue;
},
#[cfg(feature = "capture")]
DebugCommand::StopCaptureSequence => {
self.stop_capture_sequence();
return RenderBackendStatus::Continue;
},
#[cfg(feature = "replay")]
DebugCommand::LoadCapture(root, tx) => {
DebugCommand::LoadCapture(path, ids, tx) => {
NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed);
*frame_counter += 1;
self.load_capture(&root, profile_counters);
let mut config = CaptureConfig::new(path, CaptureBits::all());
if let Some((scene_id, frame_id)) = ids {
config.scene_id = scene_id;
config.frame_id = frame_id;
}
self.load_capture(config, profile_counters);
for (id, doc) in &self.documents {
let captured = CapturedDocument {
@ -1466,8 +1536,9 @@ impl RenderBackend {
profile_counters,
|document_id| txns.iter().any(|txn| txn.document_id == document_id));
let mut built_frame = false;
for mut txn in txns {
self.update_document(
built_frame |= self.update_document(
txn.document_id,
txn.resource_updates.take(),
txn.frame_ops.take(),
@ -1479,7 +1550,10 @@ impl RenderBackend {
false
);
}
if built_frame {
#[cfg(feature = "capture")]
self.save_capture_sequence();
}
self.bookkeep_after_frames();
return;
}
@ -1509,8 +1583,10 @@ impl RenderBackend {
.cloned()
.filter(|key| !document_already_present(*key))
.collect();
#[allow(unused_variables)]
let mut built_frame = false;
for &document_id in &nop_documents {
self.update_document(
built_frame |= self.update_document(
document_id,
Vec::default(),
Vec::default(),
@ -1521,6 +1597,11 @@ impl RenderBackend {
profile_counters,
false);
}
#[cfg(feature = "capture")]
match built_frame {
true => self.save_capture_sequence(),
_ => {},
}
}
}
@ -1535,7 +1616,7 @@ impl RenderBackend {
frame_counter: &mut u32,
profile_counters: &mut BackendProfileCounters,
has_built_scene: bool,
) {
) -> bool {
let requested_frame = render_frame;
let requires_frame_build = self.requires_frame_build();
@ -1657,6 +1738,26 @@ impl RenderBackend {
}
doc.prev_composite_descriptor = composite_descriptor;
#[cfg(feature = "capture")]
match self.capture_config {
Some(ref mut config) => {
// FIXME(aosmond): document splitting causes multiple prepare frames
config.prepare_frame();
if config.bits.contains(CaptureBits::FRAME) {
let file_name = format!("frame-{}-{}", document_id.namespace_id.0, document_id.id);
config.serialize_for_frame(&rendered_document.frame, file_name);
}
let data_stores_name = format!("data-stores-{}-{}", document_id.namespace_id.0, document_id.id);
config.serialize_for_frame(&doc.data_stores, data_stores_name);
let properties_name = format!("properties-{}-{}", document_id.namespace_id.0, document_id.id);
config.serialize_for_frame(&doc.dynamic_properties, properties_name);
},
None => {},
}
let msg = ResultMsg::PublishPipelineInfo(doc.updated_pipeline_info());
self.result_tx.send(msg).unwrap();
@ -1704,6 +1805,8 @@ impl RenderBackend {
if !doc.hit_tester_is_valid {
doc.rebuild_hit_tester();
}
build_frame
}
#[cfg(not(feature = "debugger"))]
@ -1751,6 +1854,29 @@ impl RenderBackend {
// thread waiting on the request.
self.scene_tx.send(SceneBuilderRequest::ReportMemory(report, tx)).unwrap();
}
#[cfg(feature = "capture")]
fn save_capture_sequence(&mut self) {
if let Some(ref mut config) = self.capture_config {
let deferred = self.resource_cache.save_capture_sequence(config);
let backend = PlainRenderBackend {
default_device_pixel_ratio: self.default_device_pixel_ratio,
frame_config: self.frame_config.clone(),
resource_sequence_id: config.resource_id,
documents: self.documents
.iter()
.map(|(id, doc)| (*id, doc.view.clone()))
.collect(),
};
config.serialize_for_frame(&backend, "backend");
if !deferred.is_empty() {
let msg = ResultMsg::DebugOutput(DebugOutput::SaveCapture(config.clone(), deferred));
self.result_tx.send(msg).unwrap();
}
}
}
}
fn get_blob_image_updates(updates: &[ResourceUpdate]) -> Vec<BlobImageKey> {
@ -1783,7 +1909,6 @@ impl RenderBackend {
profile_counters: &mut BackendProfileCounters,
) -> DebugOutput {
use std::fs;
use crate::capture::CaptureConfig;
use crate::render_task_graph::dump_render_tasks_as_svg;
debug!("capture: saving {:?}", root);
@ -1817,19 +1942,17 @@ impl RenderBackend {
// 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(&rendered_document.frame, file_name);
config.serialize_for_frame(&rendered_document.frame, file_name);
let file_name = format!("spatial-{}-{}", id.namespace_id.0, id.id);
config.serialize_tree(&doc.scene.spatial_tree, file_name);
config.serialize_tree_for_frame(&doc.scene.spatial_tree, file_name);
let file_name = format!("built-primitives-{}-{}", id.namespace_id.0, id.id);
config.serialize(&doc.scene.prim_store, file_name);
config.serialize_for_frame(&doc.scene.prim_store, file_name);
let file_name = format!("built-clips-{}-{}", id.namespace_id.0, id.id);
config.serialize(&doc.scene.clip_store, file_name);
config.serialize_for_frame(&doc.scene.clip_store, file_name);
let file_name = format!("scratch-{}-{}", id.namespace_id.0, id.id);
config.serialize(&doc.scratch, file_name);
let file_name = format!("properties-{}-{}", id.namespace_id.0, id.id);
config.serialize(&doc.dynamic_properties, file_name);
config.serialize_for_frame(&doc.scratch, file_name);
let file_name = format!("render-tasks-{}-{}.svg", id.namespace_id.0, id.id);
let mut svg_file = fs::File::create(&config.file_path(file_name, "svg"))
let mut svg_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,
@ -1839,7 +1962,10 @@ impl RenderBackend {
}
let data_stores_name = format!("data-stores-{}-{}", id.namespace_id.0, id.id);
config.serialize(&doc.data_stores, data_stores_name);
config.serialize_for_frame(&doc.data_stores, data_stores_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) {
@ -1865,14 +1991,15 @@ impl RenderBackend {
let backend = PlainRenderBackend {
default_device_pixel_ratio: self.default_device_pixel_ratio,
frame_config: self.frame_config.clone(),
resource_sequence_id: 0,
documents: self.documents
.iter()
.map(|(id, doc)| (*id, doc.view.clone()))
.collect(),
resources,
};
config.serialize(&backend, "backend");
config.serialize_for_frame(&backend, "backend");
config.serialize_for_frame(&resources, "plain-resources");
if config.bits.contains(CaptureBits::FRAME) {
let msg_update_resources = ResultMsg::UpdateResources {
@ -1883,47 +2010,86 @@ impl RenderBackend {
// Save the texture/glyph/image caches.
info!("\tresource cache");
let caches = self.resource_cache.save_caches(&config.root);
config.serialize(&caches, "resource_cache");
config.serialize_for_resource(&caches, "resource_cache");
info!("\tgpu cache");
config.serialize(&self.gpu_cache, "gpu_cache");
config.serialize_for_resource(&self.gpu_cache, "gpu_cache");
}
DebugOutput::SaveCapture(config, deferred)
}
#[cfg(feature = "capture")]
fn start_capture_sequence(
&mut self,
root: PathBuf,
bits: CaptureBits,
) {
self.scene_tx.send(SceneBuilderRequest::StartCaptureSequence(CaptureConfig::new(root, bits))).unwrap();
}
#[cfg(feature = "capture")]
fn stop_capture_sequence(
&mut self,
) {
self.scene_tx.send(SceneBuilderRequest::StopCaptureSequence).unwrap();
}
#[cfg(feature = "replay")]
fn load_capture(
&mut self,
root: &PathBuf,
mut config: CaptureConfig,
profile_counters: &mut BackendProfileCounters,
) {
use crate::capture::CaptureConfig;
debug!("capture: loading {:?}", root);
let backend = CaptureConfig::deserialize::<PlainRenderBackend, _>(root, "backend")
debug!("capture: loading {:?}", config.frame_root());
let backend = config.deserialize_for_frame::<PlainRenderBackend, _>("backend")
.expect("Unable to open backend.ron");
let caches_maybe = CaptureConfig::deserialize::<PlainCacheOwn, _>(root, "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:
// 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;
if self.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_externals = self.resource_cache.load_capture(
backend.resources,
caches_maybe,
root,
);
let msg_load = ResultMsg::DebugOutput(
DebugOutput::LoadCapture(root.clone(), plain_externals)
);
self.result_tx.send(msg_load).unwrap();
config.resource_id = backend.resource_sequence_id;
self.loaded_resource_sequence_id = backend.resource_sequence_id;
self.gpu_cache = match CaptureConfig::deserialize::<GpuCache, _>(root, "gpu_cache") {
Some(gpu_cache) => gpu_cache,
None => GpuCache::new(),
};
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.gpu_cache = match config.deserialize_for_resource::<GpuCache, _>("gpu_cache") {
Some(gpu_cache) => gpu_cache,
None => GpuCache::new(),
};
}
self.documents.clear();
self.default_device_pixel_ratio = backend.default_device_pixel_ratio;
self.frame_config = backend.frame_config;
@ -1932,41 +2098,64 @@ impl RenderBackend {
for (id, view) in backend.documents {
debug!("\tdocument {:?}", id);
let scene_name = format!("scene-{}-{}", id.namespace_id.0, id.id);
let scene = CaptureConfig::deserialize::<Scene, _>(root, &scene_name)
let scene = config.deserialize_for_scene::<Scene, _>(&scene_name)
.expect(&format!("Unable to open {}.ron", scene_name));
let interners_name = format!("interners-{}-{}", id.namespace_id.0, id.id);
let interners = CaptureConfig::deserialize::<Interners, _>(root, &interners_name)
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 = CaptureConfig::deserialize::<DataStores, _>(root, &data_stores_name)
let data_stores = config.deserialize_for_frame::<DataStores, _>(&data_stores_name)
.expect(&format!("Unable to open {}.ron", data_stores_name));
let doc = Document {
id,
scene: BuiltScene::empty(),
removed_pipelines: Vec::new(),
view: view.clone(),
stamp: FrameStamp::first(id),
frame_builder: FrameBuilder::new(),
output_pipelines: FastHashSet::default(),
dynamic_properties: SceneProperties::new(),
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: PrimitiveScratchBuffer::new(),
render_task_counters: RenderTaskGraphCounters::new(),
loaded_scene: scene.clone(),
prev_composite_descriptor: CompositeDescriptor::empty(),
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));
// 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.
match self.documents.entry(id) {
Occupied(entry) => {
let doc = entry.into_mut();
doc.view = view.clone();
doc.loaded_scene = scene.clone();
doc.data_stores = data_stores;
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: view.clone(),
stamp: FrameStamp::first(id),
frame_builder: FrameBuilder::new(),
output_pipelines: FastHashSet::default(),
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: PrimitiveScratchBuffer::new(),
render_task_counters: RenderTaskGraphCounters::new(),
loaded_scene: scene.clone(),
prev_composite_descriptor: CompositeDescriptor::empty(),
};
entry.insert(doc);
}
};
let frame_name = format!("frame-{}-{}", id.namespace_id.0, id.id);
let frame = CaptureConfig::deserialize::<Frame, _>(root, frame_name);
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());
@ -1997,13 +2186,11 @@ impl RenderBackend {
scene,
view: view.clone(),
config: self.frame_config.clone(),
output_pipelines: doc.output_pipelines.clone(),
output_pipelines: self.documents[&id].output_pipelines.clone(),
font_instances: self.resource_cache.get_font_instances(),
build_frame,
interners,
});
self.documents.insert(id, doc);
}
if !scenes_to_build.is_empty() {

Просмотреть файл

@ -2890,9 +2890,9 @@ impl Renderer {
self.save_capture(config, deferred);
}
#[cfg(feature = "replay")]
DebugOutput::LoadCapture(root, plain_externals) => {
DebugOutput::LoadCapture(config, plain_externals) => {
self.active_documents.clear();
self.load_capture(root, plain_externals);
self.load_capture(config, plain_externals);
}
},
ResultMsg::DebugCommand(command) => {
@ -3132,7 +3132,9 @@ impl Renderer {
self.debug_server.send(json);
}
DebugCommand::SaveCapture(..) |
DebugCommand::LoadCapture(..) => {
DebugCommand::LoadCapture(..) |
DebugCommand::StartCaptureSequence(..) |
DebugCommand::StopCaptureSequence => {
panic!("Capture commands are not welcome here! Did you build with 'capture' feature?")
}
DebugCommand::ClearCaches(_)
@ -6927,7 +6929,13 @@ struct PlainRenderer {
gpu_cache: PlainTexture,
gpu_cache_frame_id: FrameId,
textures: FastHashMap<CacheTextureId, PlainTexture>,
external_images: Vec<ExternalCaptureImage>
}
#[cfg(any(feature = "capture", feature = "replay"))]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
struct PlainExternalResources {
images: Vec<ExternalCaptureImage>
}
#[cfg(feature = "replay")]
@ -7075,11 +7083,13 @@ impl Renderer {
use std::io::Write;
use api::{CaptureBits, ExternalImageData};
let root = config.resource_root();
self.device.begin_frame();
let _gm = self.gpu_profile.start_marker("read GPU data");
self.device.bind_read_target_impl(self.read_fbo);
if !deferred_images.is_empty() {
if config.bits.contains(CaptureBits::EXTERNAL_RESOURCES) && !deferred_images.is_empty() {
info!("saving external images");
let mut arc_map = FastHashMap::<*const u8, String>::default();
let mut tex_map = FastHashMap::<u32, String>::default();
@ -7130,13 +7140,13 @@ impl Renderer {
}
};
if let Some(bytes) = data {
fs::File::create(config.root.join(&short_path))
fs::File::create(root.join(&short_path))
.expect(&format!("Unable to create {}", short_path))
.write_all(&bytes)
.unwrap();
#[cfg(feature = "png")]
CaptureConfig::save_png(
config.root.join(&short_path).with_extension("png"),
root.join(&short_path).with_extension("png"),
def.descriptor.size,
def.descriptor.format,
def.descriptor.stride,
@ -7148,15 +7158,19 @@ impl Renderer {
external: def.external,
uv: ext_image.uv,
};
config.serialize(&plain, &def.short_path);
config.serialize_for_resource(&plain, &def.short_path);
}
for def in &deferred_images {
handler.unlock(def.external.id, def.external.channel_index);
}
let plain_external = PlainExternalResources {
images: deferred_images,
};
config.serialize_for_resource(&plain_external, "external_resources");
}
if config.bits.contains(CaptureBits::FRAME) {
let path_textures = config.root.join("textures");
let path_textures = root.join("textures");
if !path_textures.is_dir() {
fs::create_dir(&path_textures).unwrap();
}
@ -7167,22 +7181,21 @@ impl Renderer {
device_size: self.device_size,
gpu_cache: Self::save_texture(
&self.gpu_cache_texture.texture.as_ref().unwrap(),
"gpu", &config.root, &mut self.device,
"gpu", &root, &mut self.device,
),
gpu_cache_frame_id: self.gpu_cache_frame_id,
textures: FastHashMap::default(),
external_images: deferred_images,
};
info!("saving cached textures");
for (id, texture) in &self.texture_resolver.texture_cache_map {
let file_name = format!("cache-{}", plain_self.textures.len() + 1);
info!("\t{}", file_name);
let plain = Self::save_texture(texture, &file_name, &config.root, &mut self.device);
let plain = Self::save_texture(texture, &file_name, &root, &mut self.device);
plain_self.textures.insert(*id, plain);
}
config.serialize(&plain_self, "renderer");
config.serialize_for_resource(&plain_self, "renderer");
}
self.device.reset_read_target();
@ -7192,7 +7205,9 @@ impl Renderer {
#[cfg(feature = "replay")]
fn load_capture(
&mut self, root: PathBuf, plain_externals: Vec<PlainExternalImage>
&mut self,
config: CaptureConfig,
plain_externals: Vec<PlainExternalImage>,
) {
use std::fs::File;
use std::io::Read;
@ -7205,6 +7220,8 @@ impl Renderer {
data: FastHashMap::default(),
};
let root = config.resource_root();
// Note: this is a `SCENE` level population of the external image handlers
// It would put both external buffers and texture into the map.
// But latter are going to be overwritten later in this function
@ -7226,7 +7243,49 @@ impl Renderer {
image_handler.data.insert((ext.id, ext.channel_index), value);
}
if let Some(renderer) = CaptureConfig::deserialize::<PlainRenderer, _>(&root, "renderer") {
if let Some(external_resources) = config.deserialize_for_resource::<PlainExternalResources, _>("external_resources") {
info!("loading external texture-backed images");
let mut native_map = FastHashMap::<String, gl::GLuint>::default();
for ExternalCaptureImage { short_path, external, descriptor } in external_resources.images {
let target = match external.image_type {
ExternalImageType::TextureHandle(target) => target,
ExternalImageType::Buffer => continue,
};
let plain_ext = config.deserialize_for_resource::<PlainExternalImage, _>(&short_path)
.expect(&format!("Unable to read {}.ron", short_path));
let key = (external.id, external.channel_index);
let tid = match native_map.entry(plain_ext.data) {
Entry::Occupied(e) => e.get().clone(),
Entry::Vacant(e) => {
//TODO: provide a way to query both the layer count and the filter from external images
let (layer_count, filter) = (1, TextureFilter::Linear);
let plain_tex = PlainTexture {
data: e.key().clone(),
size: (descriptor.size, layer_count),
format: descriptor.format,
filter,
has_depth: false,
};
let t = Self::load_texture(
target,
&plain_tex,
None,
&root,
&mut self.device
);
let extex = t.0.into_external();
self.owned_external_images.insert(key, extex.clone());
e.insert(extex.internal_id()).clone()
}
};
let value = (CapturedExternalImageData::NativeTexture(tid), plain_ext.uv);
image_handler.data.insert(key, value);
}
}
if let Some(renderer) = config.deserialize_for_resource::<PlainRenderer, _>("renderer") {
info!("loading cached textures");
self.device_size = renderer.device_size;
self.device.begin_frame();
@ -7280,46 +7339,18 @@ impl Renderer {
}
self.gpu_cache_frame_id = renderer.gpu_cache_frame_id;
info!("loading external texture-backed images");
let mut native_map = FastHashMap::<String, gl::GLuint>::default();
for ExternalCaptureImage { short_path, external, descriptor } in renderer.external_images {
let target = match external.image_type {
ExternalImageType::TextureHandle(target) => target,
ExternalImageType::Buffer => continue,
};
let plain_ext = CaptureConfig::deserialize::<PlainExternalImage, _>(&root, &short_path)
.expect(&format!("Unable to read {}.ron", short_path));
let key = (external.id, external.channel_index);
let tid = match native_map.entry(plain_ext.data) {
Entry::Occupied(e) => e.get().clone(),
Entry::Vacant(e) => {
//TODO: provide a way to query both the layer count and the filter from external images
let (layer_count, filter) = (1, TextureFilter::Linear);
let plain_tex = PlainTexture {
data: e.key().clone(),
size: (descriptor.size, layer_count),
format: descriptor.format,
filter,
has_depth: false,
};
let t = Self::load_texture(
target,
&plain_tex,
None,
&root,
&mut self.device
);
let extex = t.0.into_external();
self.owned_external_images.insert(key, extex.clone());
e.insert(extex.internal_id()).clone()
}
};
let value = (CapturedExternalImageData::NativeTexture(tid), plain_ext.uv);
image_handler.data.insert(key, value);
self.device.end_frame();
} else {
info!("loading cached textures");
self.device.begin_frame();
for (_id, texture) in self.texture_resolver.texture_cache_map.drain() {
self.device.delete_texture(texture);
}
info!("loading gpu cache");
if let Some(t) = self.gpu_cache_texture.texture.take() {
self.device.delete_texture(t);
}
self.device.end_frame();
}

Просмотреть файл

@ -15,7 +15,7 @@ use api::units::*;
use crate::capture::ExternalCaptureImage;
#[cfg(feature = "replay")]
use crate::capture::PlainExternalImage;
#[cfg(any(feature = "replay", feature = "png"))]
#[cfg(any(feature = "replay", feature = "png", feature="capture"))]
use crate::capture::CaptureConfig;
use crate::composite::{NativeSurfaceId, NativeSurfaceOperation, NativeTileId, NativeSurfaceOperationDetails};
use crate::device::TextureFilter;
@ -459,6 +459,12 @@ pub struct ResourceCache {
state: State,
current_frame_id: FrameId,
#[cfg(feature = "capture")]
/// Used for capture sequences. If the resource cache is updated, then we
/// mark it as dirty. When the next frame is captured in the sequence, we
/// dump the state of the resource cache.
capture_dirty: bool,
pub texture_cache: TextureCache,
/// TODO(gw): We should expire (parts of) this cache semi-regularly!
@ -506,6 +512,8 @@ impl ResourceCache {
// We want to keep three frames worth of delete blob keys
deleted_blob_keys: vec![Vec::new(), Vec::new(), Vec::new()].into(),
pending_native_surface_updates: Vec::new(),
#[cfg(feature = "capture")]
capture_dirty: true,
}
}
@ -568,6 +576,11 @@ impl ResourceCache {
// TODO, there is potential for optimization here, by processing updates in
// bulk rather than one by one (for example by sorting allocations by size or
// in a way that reduces fragmentation in the atlas).
#[cfg(feature = "capture")]
match updates.is_empty() {
false => self.capture_dirty = true,
_ => {},
}
for update in updates {
match update {
@ -633,6 +646,12 @@ impl ResourceCache {
updates: &mut Vec<ResourceUpdate>,
profile_counters: &mut ResourceProfileCounters,
) {
#[cfg(feature = "capture")]
match updates.is_empty() {
false => self.capture_dirty = true,
_ => {},
}
for update in updates.iter() {
match *update {
ResourceUpdate::AddBlobImage(ref img) => {
@ -1998,7 +2017,7 @@ impl ResourceCache {
&mut self,
resources: PlainResources,
caches: Option<PlainCacheOwn>,
root: &PathBuf,
config: &CaptureConfig,
) -> Vec<PlainExternalImage> {
use std::{fs, path::Path};
@ -2040,6 +2059,7 @@ impl ResourceCache {
res.image_templates.images.clear();
info!("\tfont templates...");
let root = config.resource_root();
let native_font_replacement = Arc::new(NATIVE_FONT.to_vec());
for (key, plain_template) in resources.font_templates {
let arc = match raw_map.entry(plain_template.data) {
@ -2071,7 +2091,7 @@ impl ResourceCache {
info!("\timage templates...");
let mut external_images = Vec::new();
for (key, template) in resources.image_templates {
let data = match CaptureConfig::deserialize::<PlainExternalImage, _>(root, &template.data) {
let data = match config.deserialize_for_resource::<PlainExternalImage, _>(&template.data) {
Some(plain) => {
let ext_data = plain.external;
external_images.push(plain);
@ -2104,6 +2124,19 @@ impl ResourceCache {
external_images
}
#[cfg(feature = "capture")]
pub fn save_capture_sequence(&mut self, config: &mut CaptureConfig) -> Vec<ExternalCaptureImage> {
if self.capture_dirty {
self.capture_dirty = false;
config.prepare_resource();
let (resources, deferred) = self.save_capture(&config.resource_root());
config.serialize_for_resource(&resources, "plain-resources.ron");
deferred
} else {
Vec::new()
}
}
}
/// For now the blob's coordinate space have the same pixel sizes as the

Просмотреть файл

@ -7,7 +7,7 @@ use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate, ExternalEven
use api::{BuiltDisplayList, ColorF, NotificationRequest, Checkpoint, IdNamespace};
use api::{ClipIntern, FilterDataIntern, MemoryReport, PrimitiveKeyKind};
use api::units::LayoutSize;
#[cfg(feature = "capture")]
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::capture::CaptureConfig;
use crate::frame_builder::FrameBuilderConfig;
use crate::scene_building::SceneBuilder;
@ -161,12 +161,18 @@ pub enum SceneBuilderRequest {
SaveScene(CaptureConfig),
#[cfg(feature = "replay")]
LoadScenes(Vec<LoadScene>),
#[cfg(feature = "capture")]
StartCaptureSequence(CaptureConfig),
#[cfg(feature = "capture")]
StopCaptureSequence,
DocumentsForDebugger
}
// Message from scene builder to render backend.
pub enum SceneBuilderResult {
Transactions(Vec<Box<BuiltTransaction>>, Option<Sender<SceneSwapResult>>),
#[cfg(feature = "capture")]
CapturedTransactions(Vec<Box<BuiltTransaction>>, CaptureConfig, Option<Sender<SceneSwapResult>>),
ExternalEvent(ExternalEvent),
FlushComplete(Sender<()>),
ClearNamespace(IdNamespace),
@ -267,7 +273,9 @@ pub struct SceneBuilderThread {
size_of_ops: Option<MallocSizeOfOps>,
hooks: Option<Box<dyn SceneBuilderHooks + Send>>,
simulate_slow_ms: u32,
removed_pipelines: FastHashSet<PipelineId>
removed_pipelines: FastHashSet<PipelineId>,
#[cfg(feature = "capture")]
capture_config: Option<CaptureConfig>,
}
pub struct SceneBuilderThreadChannels {
@ -313,6 +321,8 @@ impl SceneBuilderThread {
hooks,
simulate_slow_ms: 0,
removed_pipelines: FastHashSet::default(),
#[cfg(feature = "capture")]
capture_config: None,
}
}
@ -343,6 +353,11 @@ impl SceneBuilderThread {
let built_txns : Vec<Box<BuiltTransaction>> = txns.iter_mut()
.map(|txn| self.process_transaction(txn))
.collect();
#[cfg(feature = "capture")]
match built_txns.iter().any(|txn| txn.built_scene.is_some()) {
true => self.save_capture_sequence(),
_ => {},
}
self.forward_built_transactions(built_txns);
}
Ok(SceneBuilderRequest::DeleteDocument(document_id)) => {
@ -363,6 +378,16 @@ impl SceneBuilderThread {
Ok(SceneBuilderRequest::SaveScene(config)) => {
self.save_scene(config);
}
#[cfg(feature = "capture")]
Ok(SceneBuilderRequest::StartCaptureSequence(config)) => {
self.start_capture_sequence(config);
}
#[cfg(feature = "capture")]
Ok(SceneBuilderRequest::StopCaptureSequence) => {
// FIXME(aosmond): clear config for frames and resource cache without scene
// rebuild?
self.capture_config = None;
}
Ok(SceneBuilderRequest::DocumentsForDebugger) => {
let json = self.get_docs_for_debugger();
self.send(SceneBuilderResult::DocumentsForDebugger(json));
@ -406,11 +431,11 @@ impl SceneBuilderThread {
fn save_scene(&mut self, config: CaptureConfig) {
for (id, doc) in &self.documents {
let interners_name = format!("interners-{}-{}", id.namespace_id.0, id.id);
config.serialize(&doc.interners, interners_name);
config.serialize_for_scene(&doc.interners, interners_name);
if config.bits.contains(api::CaptureBits::SCENE) {
let file_name = format!("scene-{}-{}", id.namespace_id.0, id.id);
config.serialize(&doc.scene, file_name);
config.serialize_for_scene(&doc.scene, file_name);
}
}
}
@ -471,6 +496,33 @@ impl SceneBuilderThread {
}
}
#[cfg(feature = "capture")]
fn save_capture_sequence(
&mut self,
) {
if let Some(ref mut config) = self.capture_config {
config.prepare_scene();
for (id, doc) in &self.documents {
let interners_name = format!("interners-{}-{}", id.namespace_id.0, id.id);
config.serialize_for_scene(&doc.interners, interners_name);
if config.bits.contains(api::CaptureBits::SCENE) {
let file_name = format!("scene-{}-{}", id.namespace_id.0, id.id);
config.serialize_for_scene(&doc.scene, file_name);
}
}
}
}
#[cfg(feature = "capture")]
fn start_capture_sequence(
&mut self,
config: CaptureConfig,
) {
self.capture_config = Some(config);
self.save_capture_sequence();
}
#[cfg(feature = "debugger")]
fn traverse_items<'a>(
&self,
@ -688,6 +740,13 @@ impl SceneBuilderThread {
Vec::new()
};
#[cfg(feature = "capture")]
match self.capture_config {
Some(ref config) => self.tx.send(SceneBuilderResult::CapturedTransactions(txns, config.clone(), result_tx)).unwrap(),
None => self.tx.send(SceneBuilderResult::Transactions(txns, result_tx)).unwrap(),
}
#[cfg(not(feature = "capture"))]
self.tx.send(SceneBuilderResult::Transactions(txns, result_tx)).unwrap();
let _ = self.api_tx.send(ApiMsg::WakeUp);

Просмотреть файл

@ -913,6 +913,8 @@ bitflags!{
const FRAME = 0x2;
///
const TILE_CACHE = 0x4;
///
const EXTERNAL_RESOURCES = 0x8;
}
}
@ -967,7 +969,11 @@ pub enum DebugCommand {
SaveCapture(PathBuf, CaptureBits),
/// Load a capture of all the documents state.
#[serde(skip_serializing, skip_deserializing)]
LoadCapture(PathBuf, Sender<CapturedDocument>),
LoadCapture(PathBuf, Option<(u32, u32)>, Sender<CapturedDocument>),
/// Start capturing a sequence of scene/frame changes.
StartCaptureSequence(PathBuf, CaptureBits),
/// Stop capturing a sequence of scene/frame changes.
StopCaptureSequence,
/// Clear cached resources, forcing them to be re-uploaded from templates.
ClearCaches(ClearCache),
/// Enable/disable native compositor usage
@ -1732,13 +1738,13 @@ impl RenderApi {
}
/// Load a capture of the current frame state for debugging.
pub fn load_capture(&self, path: PathBuf) -> Vec<CapturedDocument> {
pub fn load_capture(&self, path: PathBuf, ids: Option<(u32, u32)>) -> Vec<CapturedDocument> {
// First flush the scene builder otherwise async scenes might clobber
// the capture we are about to load.
self.flush_scene_builder();
let (tx, rx) = channel();
let msg = ApiMsg::DebugCommand(DebugCommand::LoadCapture(path, tx));
let msg = ApiMsg::DebugCommand(DebugCommand::LoadCapture(path, ids, tx));
self.send_message(msg);
let mut documents = Vec::new();
@ -1748,6 +1754,18 @@ impl RenderApi {
documents
}
/// Start capturing a sequence of frames.
pub fn start_capture_sequence(&self, path: PathBuf, bits: CaptureBits) {
let msg = ApiMsg::DebugCommand(DebugCommand::StartCaptureSequence(path, bits));
self.send_message(msg);
}
/// Stop capturing sequences of frames.
pub fn stop_capture_sequence(&self) {
let msg = ApiMsg::DebugCommand(DebugCommand::StopCaptureSequence);
self.send_message(msg);
}
/// Update the state of builtin debugging facilities.
pub fn send_debug_cmd(&self, cmd: DebugCommand) {
let msg = ApiMsg::DebugCommand(cmd);

Просмотреть файл

@ -143,6 +143,16 @@ subcommands:
long: keyframes
takes_value: true
help: Provide a keyframes file, that can be used to animate the yaml input file
- scene-id:
short: s
long: scene-id
takes_value: true
help: Select a starting scene sequence ID (YAML capture sequence only).
- frame-id:
short: f
long: frame-id
takes_value: true
help: Select a starting frame sequence ID (YAML capture sequence only).
- INPUT:
help: The input YAML, binary recording, or capture directory
required: true

Просмотреть файл

@ -54,7 +54,7 @@ use webrender::api::*;
use webrender::api::units::*;
use winit::dpi::{LogicalPosition, LogicalSize};
use winit::VirtualKeyCode;
use crate::wrench::{Wrench, WrenchThing};
use crate::wrench::{CapturedSequence, Wrench, WrenchThing};
use crate::yaml_frame_reader::YamlFrameReader;
pub const PLATFORM_DEFAULT_FACE_NAME: &str = "Arial";
@ -764,13 +764,18 @@ fn render<'a>(
let input_path = subargs.value_of("INPUT").map(PathBuf::from).unwrap();
// If the input is a directory, we are looking at a capture.
let mut thing = if input_path.as_path().is_dir() {
let mut documents = wrench.api.load_capture(input_path);
let mut thing = if input_path.join("scenes").as_path().is_dir() {
let scene_id = subargs.value_of("scene-id").map(|z| z.parse::<u32>().unwrap());
let frame_id = subargs.value_of("frame-id").map(|z| z.parse::<u32>().unwrap());
Box::new(CapturedSequence::new(
input_path,
scene_id.unwrap_or(1),
frame_id.unwrap_or(1),
))
} else if input_path.as_path().is_dir() {
let mut documents = wrench.api.load_capture(input_path, None);
println!("loaded {:?}", documents.iter().map(|cd| cd.document_id).collect::<Vec<_>>());
let captured = documents.swap_remove(0);
if let Some(fb_size) = wrench.renderer.device_size() {
window.resize(fb_size);
}
wrench.document_id = captured.document_id;
Box::new(captured) as Box<dyn WrenchThing>
} else {
@ -794,6 +799,10 @@ fn render<'a>(
window.update(wrench);
thing.do_frame(wrench);
if let Some(fb_size) = wrench.renderer.device_size() {
window.resize(fb_size);
}
let mut debug_flags = DebugFlags::empty();
debug_flags.set(DebugFlags::DISABLE_BATCHING, no_batch);

Просмотреть файл

@ -1288,7 +1288,7 @@ impl<'a> RawtestHarness<'a> {
// 4. load the first one
let mut documents = self.wrench.api.load_capture(path.into());
let mut documents = self.wrench.api.load_capture(path.into(), None);
let captured = documents.swap_remove(0);
// 5. render the built frame and compare

Просмотреть файл

@ -140,6 +140,71 @@ impl WrenchThing for CapturedDocument {
}
}
pub struct CapturedSequence {
root: PathBuf,
frame: usize,
frame_set: Vec<(u32, u32)>,
}
impl CapturedSequence {
pub fn new(root: PathBuf, scene_start: u32, frame_start: u32) -> Self {
// Build set of a scene and frame IDs.
let mut scene = scene_start;
let mut frame = frame_start;
let mut frame_set = Vec::new();
while Self::scene_root(&root, scene).as_path().is_dir() {
while Self::frame_root(&root, scene, frame).as_path().is_dir() {
frame_set.push((scene, frame));
frame += 1;
}
scene += 1;
frame = 1;
}
assert!(!frame_set.is_empty());
Self {
root,
frame: 0,
frame_set,
}
}
fn scene_root(root: &PathBuf, scene: u32) -> PathBuf {
let path = format!("scenes/{:05}", scene);
root.join(path)
}
fn frame_root(root: &PathBuf, scene: u32, frame: u32) -> PathBuf {
let path = format!("scenes/{:05}/frames/{:05}", scene, frame);
root.join(path)
}
}
impl WrenchThing for CapturedSequence {
fn next_frame(&mut self) {
if self.frame + 1 < self.frame_set.len() {
self.frame += 1;
}
}
fn prev_frame(&mut self) {
if self.frame > 0 {
self.frame -= 1;
}
}
fn do_frame(&mut self, wrench: &mut Wrench) -> u32 {
let mut documents = wrench.api.load_capture(self.root.clone(), Some(self.frame_set[self.frame]));
println!("loaded {:?} from {:?}",
documents.iter().map(|cd| cd.document_id).collect::<Vec<_>>(),
self.frame_set[self.frame]);
let captured = documents.swap_remove(0);
wrench.document_id = captured.document_id;
self.frame as u32
}
}
pub struct Wrench {
window_size: DeviceIntSize,
pub device_pixel_ratio: f32,