use api::{AsyncBlobImageRasterizer, BlobImageResult, DebugFlags, Parameter};
use api::{DocumentId, PipelineId, ExternalEvent, BlobImageRequest};
use api::{NotificationRequest, Checkpoint, IdNamespace, QualitySettings};
use api::{PrimitiveKeyKind, GlyphDimensionRequest, GlyphIndexRequest};
use api::channel::{unbounded_channel, single_msg_channel, Receiver, Sender};
use api::units::*;
use crate::render_api::{ApiMsg, FrameMsg, SceneMsg, ResourceUpdate, TransactionMsg, MemoryReport};
use crate::box_shadow::BoxShadow;
#[cfg(feature = "capture")]
use crate::capture::CaptureConfig;
use crate::frame_builder::FrameBuilderConfig;
use crate::scene_building::{SceneBuilder, SceneRecycler};
use crate::clip::{ClipIntern, PolygonIntern};
use crate::filterdata::FilterDataIntern;
use glyph_rasterizer::SharedFontResources;
use crate::intern::{Internable, Interner, UpdateList};
use crate::internal_types::{FastHashMap, FastHashSet};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use crate::prim_store::backdrop::{BackdropCapture, BackdropRender};
use crate::prim_store::borders::{ImageBorder, NormalBorderPrim};
use crate::prim_store::gradient::{LinearGradient, RadialGradient, ConicGradient};
use crate::prim_store::image::{Image, YuvImage};
use crate::prim_store::line_dec::LineDecoration;
use crate::prim_store::picture::Picture;
use crate::prim_store::text_run::TextRun;
use crate::profiler::{self, TransactionProfile};
use crate::render_backend::SceneView;
use crate::renderer::{FullFrameStats, PipelineInfo};
use crate::scene::{BuiltScene, Scene, SceneStats};
use crate::spatial_tree::{SceneSpatialTree, SpatialTreeUpdates};
use crate::telemetry::Telemetry;
use crate::SceneBuilderHooks;
use std::iter;
use time::precise_time_ns;
use crate::util::drain_filter;
use std::thread;
use std::time::Duration;
fn rasterize_blobs(txn: &mut TransactionMsg, is_low_priority: bool) {
profile_scope!("rasterize_blobs");
if let Some(ref mut rasterizer) = txn.blob_rasterizer {
let mut rasterized_blobs = rasterizer.rasterize(&txn.blob_requests, is_low_priority);
if txn.rasterized_blobs.is_empty() {
txn.rasterized_blobs = rasterized_blobs;
} else {
txn.rasterized_blobs.append(&mut rasterized_blobs);
}
}
}
pub struct BuiltTransaction {
pub document_id: DocumentId,
pub built_scene: Option<BuiltScene>,
pub view: SceneView,
pub resource_updates: Vec<ResourceUpdate>,
pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
pub blob_rasterizer: Option<Box<dyn AsyncBlobImageRasterizer>>,
pub frame_ops: Vec<FrameMsg>,
pub removed_pipelines: Vec<(PipelineId, DocumentId)>,
pub notifications: Vec<NotificationRequest>,
pub interner_updates: Option<InternerUpdates>,
pub spatial_tree_updates: Option<SpatialTreeUpdates>,
pub render_frame: bool,
pub invalidate_rendered_frame: bool,
pub profile: TransactionProfile,
pub frame_stats: FullFrameStats,
}
#[cfg(feature = "replay")]
pub struct LoadScene {
pub document_id: DocumentId,
pub scene: Scene,
pub fonts: SharedFontResources,
pub view: SceneView,
pub config: FrameBuilderConfig,
pub build_frame: bool,
pub interners: Interners,
pub spatial_tree: SceneSpatialTree,
}
pub enum SceneBuilderRequest {
Transactions(Vec<Box<TransactionMsg>>),
AddDocument(DocumentId, DeviceIntSize),
DeleteDocument(DocumentId),
GetGlyphDimensions(GlyphDimensionRequest),
GetGlyphIndices(GlyphIndexRequest),
ClearNamespace(IdNamespace),
SimulateLongSceneBuild(u32),
ExternalEvent(ExternalEvent),
WakeUp,
StopRenderBackend,
ShutDown(Option<Sender<()>>),
Flush(Sender<()>),
SetFlags(DebugFlags),
SetFrameBuilderConfig(FrameBuilderConfig),
SetParameter(Parameter),
ReportMemory(Box<MemoryReport>, Sender<Box<MemoryReport>>),
#[cfg(feature = "capture")]
SaveScene(CaptureConfig),
#[cfg(feature = "replay")]
LoadScenes(Vec<LoadScene>),
#[cfg(feature = "capture")]
StartCaptureSequence(CaptureConfig),
#[cfg(feature = "capture")]
StopCaptureSequence,
}
pub enum SceneBuilderResult {
Transactions(Vec<Box<BuiltTransaction>>, Option<Sender<SceneSwapResult>>),
ExternalEvent(ExternalEvent),
FlushComplete(Sender<()>),
DeleteDocument(DocumentId),
ClearNamespace(IdNamespace),
GetGlyphDimensions(GlyphDimensionRequest),
GetGlyphIndices(GlyphIndexRequest),
SetParameter(Parameter),
StopRenderBackend,
ShutDown(Option<Sender<()>>),
#[cfg(feature = "capture")]
CapturedTransactions(Vec<Box<BuiltTransaction>>, CaptureConfig, Option<Sender<SceneSwapResult>>),
#[cfg(feature = "capture")]
StopCaptureSequence,
}
pub enum SceneSwapResult {
Complete(Sender<()>),
Aborted,
}
macro_rules! declare_interners {
( $( $name:ident : $ty:ident, )+ ) => {
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Default)]
pub struct Interners {
$(
pub $name: Interner<$ty>,
)+
}
$(
impl AsMut<Interner<$ty>> for Interners {
fn as_mut(&mut self) -> &mut Interner<$ty> {
&mut self.$name
}
}
)+
pub struct InternerUpdates {
$(
pub $name: UpdateList<<$ty as Internable>::Key>,
)+
}
impl Interners {
fn report_memory(
&self,
ops: &mut MallocSizeOfOps,
r: &mut MemoryReport,
) {
$(
r.interning.interners.$name += self.$name.size_of(ops);
)+
}
fn end_frame_and_get_pending_updates(&mut self) -> InternerUpdates {
InternerUpdates {
$(
$name: self.$name.end_frame_and_get_pending_updates(),
)+
}
}
}
}
}
crate::enumerate_interners!(declare_interners);
struct Document {
scene: Scene,
interners: Interners,
stats: SceneStats,
view: SceneView,
spatial_tree: SceneSpatialTree,
}
impl Document {
fn new(device_rect: DeviceIntRect) -> Self {
Document {
scene: Scene::new(),
interners: Interners::default(),
stats: SceneStats::empty(),
spatial_tree: SceneSpatialTree::new(),
view: SceneView {
device_rect,
quality_settings: QualitySettings::default(),
},
}
}
}
pub struct SceneBuilderThread {
documents: FastHashMap<DocumentId, Document>,
rx: Receiver<SceneBuilderRequest>,
tx: Sender<ApiMsg>,
config: FrameBuilderConfig,
fonts: SharedFontResources,
size_of_ops: Option<MallocSizeOfOps>,
hooks: Option<Box<dyn SceneBuilderHooks + Send>>,
simulate_slow_ms: u32,
removed_pipelines: FastHashSet<PipelineId>,
#[cfg(feature = "capture")]
capture_config: Option<CaptureConfig>,
debug_flags: DebugFlags,
recycler: SceneRecycler,
}
pub struct SceneBuilderThreadChannels {
rx: Receiver<SceneBuilderRequest>,
tx: Sender<ApiMsg>,
}
impl SceneBuilderThreadChannels {
pub fn new(
tx: Sender<ApiMsg>
) -> (Self, Sender<SceneBuilderRequest>) {
let (in_tx, in_rx) = unbounded_channel();
(
Self {
rx: in_rx,
tx,
},
in_tx,
)
}
}
impl SceneBuilderThread {
pub fn new(
config: FrameBuilderConfig,
fonts: SharedFontResources,
size_of_ops: Option<MallocSizeOfOps>,
hooks: Option<Box<dyn SceneBuilderHooks + Send>>,
channels: SceneBuilderThreadChannels,
) -> Self {
let SceneBuilderThreadChannels { rx, tx } = channels;
Self {
documents: Default::default(),
rx,
tx,
config,
fonts,
size_of_ops,
hooks,
simulate_slow_ms: 0,
removed_pipelines: FastHashSet::default(),
#[cfg(feature = "capture")]
capture_config: None,
debug_flags: DebugFlags::default(),
recycler: SceneRecycler::new(),
}
}
pub fn send(&self, msg: SceneBuilderResult) {
self.tx.send(ApiMsg::SceneBuilderResult(msg)).unwrap();
}
pub fn run(&mut self) {
if let Some(ref hooks) = self.hooks {
hooks.register();
}
loop {
tracy_begin_frame!("scene_builder_thread");
match self.rx.recv() {
Ok(SceneBuilderRequest::WakeUp) => {}
Ok(SceneBuilderRequest::Flush(tx)) => {
self.send(SceneBuilderResult::FlushComplete(tx));
}
Ok(SceneBuilderRequest::SetFlags(debug_flags)) => {
self.debug_flags = debug_flags;
}
Ok(SceneBuilderRequest::Transactions(txns)) => {
let built_txns : Vec<Box<BuiltTransaction>> = txns.into_iter()
.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);
self.recycler.recycle_built_scene();
}
Ok(SceneBuilderRequest::AddDocument(document_id, initial_size)) => {
let old = self.documents.insert(document_id, Document::new(
initial_size.into(),
));
debug_assert!(old.is_none());
}
Ok(SceneBuilderRequest::DeleteDocument(document_id)) => {
self.documents.remove(&document_id);
self.send(SceneBuilderResult::DeleteDocument(document_id));
}
Ok(SceneBuilderRequest::ClearNamespace(id)) => {
self.documents.retain(|doc_id, _doc| doc_id.namespace_id != id);
self.send(SceneBuilderResult::ClearNamespace(id));
}
Ok(SceneBuilderRequest::ExternalEvent(evt)) => {
self.send(SceneBuilderResult::ExternalEvent(evt));
}
Ok(SceneBuilderRequest::GetGlyphDimensions(request)) => {
self.send(SceneBuilderResult::GetGlyphDimensions(request));
}
Ok(SceneBuilderRequest::GetGlyphIndices(request)) => {
self.send(SceneBuilderResult::GetGlyphIndices(request));
}
Ok(SceneBuilderRequest::StopRenderBackend) => {
self.send(SceneBuilderResult::StopRenderBackend);
}
Ok(SceneBuilderRequest::ShutDown(sync)) => {
self.send(SceneBuilderResult::ShutDown(sync));
break;
}
Ok(SceneBuilderRequest::SimulateLongSceneBuild(time_ms)) => {
self.simulate_slow_ms = time_ms
}
Ok(SceneBuilderRequest::ReportMemory(mut report, tx)) => {
(*report) += self.report_memory();
tx.send(report).unwrap();
}
Ok(SceneBuilderRequest::SetFrameBuilderConfig(cfg)) => {
self.config = cfg;
}
Ok(SceneBuilderRequest::SetParameter(prop)) => {
self.send(SceneBuilderResult::SetParameter(prop));
}
#[cfg(feature = "replay")]
Ok(SceneBuilderRequest::LoadScenes(msg)) => {
self.load_scenes(msg);
}
#[cfg(feature = "capture")]
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) => {
self.capture_config = None;
self.send(SceneBuilderResult::StopCaptureSequence);
}
Err(_) => {
break;
}
}
if let Some(ref hooks) = self.hooks {
hooks.poke();
}
tracy_end_frame!("scene_builder_thread");
}
if let Some(ref hooks) = self.hooks {
hooks.deregister();
}
}
#[cfg(feature = "capture")]
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_for_scene(&doc.interners, interners_name);
let scene_spatial_tree_name = format!("scene-spatial-tree-{}-{}", id.namespace_id.0, id.id);
config.serialize_for_scene(&doc.spatial_tree, scene_spatial_tree_name);
use crate::render_api::CaptureBits;
if config.bits.contains(CaptureBits::SCENE) {
let file_name = format!("scene-{}-{}", id.namespace_id.0, id.id);
config.serialize_for_scene(&doc.scene, file_name);
}
}
}
#[cfg(feature = "replay")]
fn load_scenes(&mut self, scenes: Vec<LoadScene>) {
for mut item in scenes {
self.config = item.config;
let mut built_scene = None;
let mut interner_updates = None;
let mut spatial_tree_updates = None;
if item.scene.has_root_pipeline() {
built_scene = Some(SceneBuilder::build(
&item.scene,
item.fonts,
&item.view,
&self.config,
&mut item.interners,
&mut item.spatial_tree,
&mut self.recycler,
&SceneStats::empty(),
self.debug_flags,
));
interner_updates = Some(
item.interners.end_frame_and_get_pending_updates()
);
spatial_tree_updates = Some(
item.spatial_tree.end_frame_and_get_pending_updates()
);
}
self.documents.insert(
item.document_id,
Document {
scene: item.scene,
interners: item.interners,
stats: SceneStats::empty(),
view: item.view.clone(),
spatial_tree: item.spatial_tree,
},
);
let txns = vec![Box::new(BuiltTransaction {
document_id: item.document_id,
render_frame: item.build_frame,
invalidate_rendered_frame: false,
built_scene,
view: item.view,
resource_updates: Vec::new(),
rasterized_blobs: Vec::new(),
blob_rasterizer: None,
frame_ops: Vec::new(),
removed_pipelines: Vec::new(),
notifications: Vec::new(),
interner_updates,
spatial_tree_updates,
profile: TransactionProfile::new(),
frame_stats: FullFrameStats::default(),
})];
self.forward_built_transactions(txns);
}
}
#[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);
use crate::render_api::CaptureBits;
if config.bits.contains(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();
}
fn process_transaction(&mut self, mut txn: TransactionMsg) -> Box<BuiltTransaction> {
profile_scope!("process_transaction");
if let Some(ref hooks) = self.hooks {
hooks.pre_scene_build();
}
let doc = self.documents.get_mut(&txn.document_id).unwrap();
let scene = &mut doc.scene;
let mut profile = txn.profile.take();
let scene_build_start = precise_time_ns();
let mut removed_pipelines = Vec::new();
let mut rebuild_scene = false;
let mut frame_stats = FullFrameStats::default();
for message in txn.scene_ops.drain(..) {
match message {
SceneMsg::UpdateEpoch(pipeline_id, epoch) => {
scene.update_epoch(pipeline_id, epoch);
}
SceneMsg::SetQualitySettings { settings } => {
doc.view.quality_settings = settings;
}
SceneMsg::SetDocumentView { device_rect } => {
doc.view.device_rect = device_rect;
}
SceneMsg::SetDisplayList {
epoch,
pipeline_id,
display_list,
} => {
let (builder_start_time_ns, builder_end_time_ns, send_time_ns) =
display_list.times();
let content_send_time = profiler::ns_to_ms(precise_time_ns() - send_time_ns);
let dl_build_time = profiler::ns_to_ms(builder_end_time_ns - builder_start_time_ns);
profile.set(profiler::CONTENT_SEND_TIME, content_send_time);
profile.set(profiler::DISPLAY_LIST_BUILD_TIME, dl_build_time);
profile.set(profiler::DISPLAY_LIST_MEM, profiler::bytes_to_mb(display_list.size_in_bytes()));
let (gecko_display_list_time, full_display_list) = display_list.gecko_display_list_stats();
frame_stats.full_display_list = full_display_list;
frame_stats.gecko_display_list_time = gecko_display_list_time;
frame_stats.wr_display_list_time += dl_build_time;
if self.removed_pipelines.contains(&pipeline_id) {
continue;
}
rebuild_scene = true;
scene.set_display_list(
pipeline_id,
epoch,
display_list,
);
}
SceneMsg::SetRootPipeline(pipeline_id) => {
if scene.root_pipeline_id != Some(pipeline_id) {
rebuild_scene = true;
scene.set_root_pipeline_id(pipeline_id);
}
}
SceneMsg::RemovePipeline(pipeline_id) => {
scene.remove_pipeline(pipeline_id);
self.removed_pipelines.insert(pipeline_id);
removed_pipelines.push((pipeline_id, txn.document_id));
}
}
}
self.removed_pipelines.clear();
let mut built_scene = None;
let mut interner_updates = None;
let mut spatial_tree_updates = None;
if scene.has_root_pipeline() && rebuild_scene {
let built = SceneBuilder::build(
&scene,
self.fonts.clone(),
&doc.view,
&self.config,
&mut doc.interners,
&mut doc.spatial_tree,
&mut self.recycler,
&doc.stats,
self.debug_flags,
);
doc.stats = built.get_stats();
interner_updates = Some(
doc.interners.end_frame_and_get_pending_updates()
);
spatial_tree_updates = Some(
doc.spatial_tree.end_frame_and_get_pending_updates()
);
built_scene = Some(built);
}
let scene_build_time_ms =
profiler::ns_to_ms(precise_time_ns() - scene_build_start);
profile.set(profiler::SCENE_BUILD_TIME, scene_build_time_ms);
frame_stats.scene_build_time += scene_build_time_ms;
if !txn.blob_requests.is_empty() {
profile.start_time(profiler::BLOB_RASTERIZATION_TIME);
let is_low_priority = false;
rasterize_blobs(&mut txn, is_low_priority);
profile.end_time(profiler::BLOB_RASTERIZATION_TIME);
Telemetry::record_rasterize_blobs_time(Duration::from_micros((profile.get(profiler::BLOB_RASTERIZATION_TIME).unwrap() * 1000.00) as u64));
}
drain_filter(
&mut txn.notifications,
|n| { n.when() == Checkpoint::SceneBuilt },
|n| { n.notify(); },
);
if self.simulate_slow_ms > 0 {
thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64));
}
Box::new(BuiltTransaction {
document_id: txn.document_id,
render_frame: txn.generate_frame.as_bool(),
invalidate_rendered_frame: txn.invalidate_rendered_frame,
built_scene,
view: doc.view,
rasterized_blobs: txn.rasterized_blobs,
resource_updates: txn.resource_updates,
blob_rasterizer: txn.blob_rasterizer,
frame_ops: txn.frame_ops,
removed_pipelines,
notifications: txn.notifications,
interner_updates,
spatial_tree_updates,
profile,
frame_stats,
})
}
fn forward_built_transactions(&mut self, txns: Vec<Box<BuiltTransaction>>) {
let (pipeline_info, result_tx, result_rx) = match self.hooks {
Some(ref hooks) => {
if txns.iter().any(|txn| txn.built_scene.is_some()) {
let info = PipelineInfo {
epochs: txns.iter()
.filter(|txn| txn.built_scene.is_some())
.map(|txn| {
txn.built_scene.as_ref().unwrap()
.pipeline_epochs.iter()
.zip(iter::repeat(txn.document_id))
.map(|((&pipeline_id, &epoch), document_id)| ((pipeline_id, document_id), epoch))
}).flatten().collect(),
removed_pipelines: txns.iter()
.map(|txn| txn.removed_pipelines.clone())
.flatten().collect(),
};
let (tx, rx) = single_msg_channel();
let txn = txns.iter().find(|txn| txn.built_scene.is_some()).unwrap();
Telemetry::record_scenebuild_time(Duration::from_millis(txn.profile.get(profiler::SCENE_BUILD_TIME).unwrap() as u64));
hooks.pre_scene_swap();
(Some(info), Some(tx), Some(rx))
} else {
(None, None, None)
}
}
_ => (None, None, None)
};
let timer_id = Telemetry::start_sceneswap_time();
let document_ids = txns.iter().map(|txn| txn.document_id).collect();
let have_resources_updates : Vec<DocumentId> = if pipeline_info.is_none() {
txns.iter()
.filter(|txn| !txn.resource_updates.is_empty() || txn.invalidate_rendered_frame)
.map(|txn| txn.document_id)
.collect()
} else {
Vec::new()
};
#[cfg(feature = "capture")]
match self.capture_config {
Some(ref config) => self.send(SceneBuilderResult::CapturedTransactions(txns, config.clone(), result_tx)),
None => self.send(SceneBuilderResult::Transactions(txns, result_tx)),
};
#[cfg(not(feature = "capture"))]
self.send(SceneBuilderResult::Transactions(txns, result_tx));
if let Some(pipeline_info) = pipeline_info {
let swap_result = result_rx.unwrap().recv();
Telemetry::stop_and_accumulate_sceneswap_time(timer_id);
self.hooks.as_ref().unwrap().post_scene_swap(&document_ids,
pipeline_info);
if let Ok(SceneSwapResult::Complete(resume_tx)) = swap_result {
resume_tx.send(()).ok();
}
} else {
Telemetry::cancel_sceneswap_time(timer_id);
if !have_resources_updates.is_empty() {
if let Some(ref hooks) = self.hooks {
hooks.post_resource_update(&have_resources_updates);
}
} else if let Some(ref hooks) = self.hooks {
hooks.post_empty_scene_build();
}
}
}
fn report_memory(&mut self) -> MemoryReport {
let ops = self.size_of_ops.as_mut().unwrap();
let mut report = MemoryReport::default();
for doc in self.documents.values() {
doc.interners.report_memory(ops, &mut report);
doc.scene.report_memory(ops, &mut report);
}
report
}
}
pub struct LowPrioritySceneBuilderThread {
pub rx: Receiver<SceneBuilderRequest>,
pub tx: Sender<SceneBuilderRequest>,
}
impl LowPrioritySceneBuilderThread {
pub fn run(&mut self) {
loop {
match self.rx.recv() {
Ok(SceneBuilderRequest::Transactions(mut txns)) => {
let txns : Vec<Box<TransactionMsg>> = txns.drain(..)
.map(|txn| self.process_transaction(txn))
.collect();
self.tx.send(SceneBuilderRequest::Transactions(txns)).unwrap();
}
Ok(SceneBuilderRequest::ShutDown(sync)) => {
self.tx.send(SceneBuilderRequest::ShutDown(sync)).unwrap();
break;
}
Ok(other) => {
self.tx.send(other).unwrap();
}
Err(_) => {
break;
}
}
}
}
fn process_transaction(&mut self, mut txn: Box<TransactionMsg>) -> Box<TransactionMsg> {
let is_low_priority = true;
txn.profile.start_time(profiler::BLOB_RASTERIZATION_TIME);
rasterize_blobs(&mut txn, is_low_priority);
txn.profile.end_time(profiler::BLOB_RASTERIZATION_TIME);
Telemetry::record_rasterize_blobs_time(Duration::from_micros((txn.profile.get(profiler::BLOB_RASTERIZATION_TIME).unwrap() * 1000.00) as u64));
txn.blob_requests = Vec::new();
txn
}
}