/* 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 interfacing with the GPU. //! //! Much of WebRender's design is driven by separating work into different //! threads. To avoid the complexities of multi-threaded GPU access, we restrict //! all communication with the GPU to one thread, the render thread. But since //! issuing GPU commands is often a bottleneck, we move everything else (i.e. //! the computation of what commands to issue) to another thread, the //! RenderBackend thread. The RenderBackend, in turn, may delegate work to other //! thread (like the SceneBuilder threads or Rayon workers), but the //! Render-vs-RenderBackend distinction is the most important. //! //! The consumer is responsible for initializing the render thread before //! calling into WebRender, which means that this module also serves as the //! initial entry point into WebRender, and is responsible for spawning the //! various other threads discussed above. That said, WebRender initialization //! returns both the `Renderer` instance as well as a channel for communicating //! directly with the `RenderBackend`. Aside from a few high-level operations //! like 'render now', most of interesting commands from the consumer go over //! that channel and operate on the `RenderBackend`. //! //! ## Space conversion guidelines //! At this stage, we shuld be operating with `DevicePixel` and `FramebufferPixel` only. //! "Framebuffer" space represents the final destination of our rendeing, //! and it happens to be Y-flipped on OpenGL. The conversion is done as follows: //! - for rasterized primitives, the orthographics projection transforms //! the content rectangle to -1 to 1 //! - the viewport transformation is setup to map the whole range to //! the framebuffer rectangle provided by the document view, stored in `DrawTarget` //! - all the direct framebuffer operations, like blitting, reading pixels, and setting //! up the scissor, are accepting already transformed coordinates, which we can get by //! calling `DrawTarget::to_framebuffer_rect`
use api::{ColorF, ColorU, MixBlendMode, TextureCacheCategory}; use api::{DocumentId, Epoch, ExternalImageHandler, RenderReasons}; use api::{PipelineId, ImageRendering, Checkpoint, NotificationRequest, ImageBufferKind}; use api::{FramePublishId, ImageFormat}; #[cfg(any(feature = "capture", feature = "replay"))] use api::{ExternalImageSource, ExternalImageType}; #[cfg(feature = "replay")] use api::{ExternalImage, ExternalImageId}; use api::units::*; use api::channel::{Sender, Receiver}; pubuse api::DebugFlags; use core::time::Duration;
/// The size of the array of each type of vertex data texture that /// is round-robin-ed each frame during bind_frame_data. Doing this /// helps avoid driver stalls while updating the texture in some /// drivers. The size of these textures are typically very small /// (e.g. < 16 kB) so it's not a huge waste of memory. Despite that, /// this is a short-term solution - we want to find a better way /// to provide this frame data, which will likely involve some /// combination of UBO/SSBO usage. Although this only affects some /// platforms, it's enabled on all platforms to reduce testing /// differences between platforms. pubconst VERTEX_DATA_TEXTURE_COUNT: usize = 3;
/// Number of GPU blocks per UV rectangle provided for an image. pubconst BLOCKS_PER_UV_RECT: usize = 2;
/// The clear color used for the texture cache when the debug display is enabled. /// We use a shade of blue so that we can still identify completely blue items in /// the texture cache. pubconst TEXTURE_CACHE_DBG_CLEAR_COLOR: [f32; 4] = [0.0, 0.0, 0.8, 1.0];
impl From<GlyphFormat> for ShaderColorMode { fn from(format: GlyphFormat) -> ShaderColorMode { match format {
GlyphFormat::Alpha |
GlyphFormat::TransformedAlpha |
GlyphFormat::Bitmap => ShaderColorMode::Alpha,
GlyphFormat::Subpixel | GlyphFormat::TransformedSubpixel => {
panic!("Subpixel glyph formats must be handled separately.");
}
GlyphFormat::ColorBitmap => ShaderColorMode::ColorBitmap,
}
}
}
/// Enumeration of the texture samplers used across the various WebRender shaders. /// /// Each variant corresponds to a uniform declared in shader source. We only bind /// the variants we need for a given shader, so not every variant is bound for every /// batch. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) enum TextureSampler {
Color0,
Color1,
Color2,
TransformPalette,
RenderTasks,
Dither,
PrimitiveHeadersF,
PrimitiveHeadersI,
ClipMask,
GpuBufferF,
GpuBufferI,
}
impl TextureSampler { pub(crate) fn color(n: usize) -> TextureSampler { match n { 0 => TextureSampler::Color0, 1 => TextureSampler::Color1, 2 => TextureSampler::Color2,
_ => {
panic!("There are only 3 color samplers.");
}
}
}
}
/// The selected partial present mode for a given frame. #[derive(Debug, Copy, Clone)] pub(super) enum PartialPresentMode { /// The device supports fewer dirty rects than the number of dirty rects /// that WR produced. In this case, the WR dirty rects are union'ed into /// a single dirty rect, that is provided to the caller.
Single {
dirty_rect: DeviceRect,
},
}
/// Helper struct for resolving device Textures for use during rendering passes. /// /// Manages the mapping between the at-a-distance texture handles used by the /// `RenderBackend` (which does not directly interface with the GPU) and actual /// device texture handles. struct TextureResolver { /// A map to resolve texture cache IDs to native textures.
texture_cache_map: FastHashMap<CacheTextureId, CacheTexture>,
/// Map of external image IDs to native textures.
external_images: FastHashMap<DeferredResolveIndex, ExternalTexture>,
/// A special 1x1 dummy texture used for shaders that expect to work with /// the output of the previous pass but are actually running in the first /// pass.
dummy_cache_texture: Texture,
}
for (_id, item) inself.texture_cache_map {
device.delete_texture(item.texture);
}
}
fn begin_frame(&mutself) {
}
fn end_pass(
&mutself,
device: &mut Device,
textures_to_invalidate: &[CacheTextureId],
) { // For any texture that is no longer needed, immediately // invalidate it so that tiled GPUs don't need to resolve it // back to memory. for texture_id in textures_to_invalidate { let render_target = &self.texture_cache_map[texture_id].texture;
device.invalidate_render_target(render_target);
}
}
// Bind a source texture to the device. fn bind(&self, texture_id: &TextureSource, sampler: TextureSampler, device: &mut Device) -> Swizzle { match *texture_id {
TextureSource::Invalid => {
Swizzle::default()
}
TextureSource::Dummy => { let swizzle = Swizzle::default();
device.bind_texture(sampler, &self.dummy_cache_texture, swizzle);
swizzle
}
TextureSource::External(TextureSourceExternal { ref index, .. }) => { let texture = self.external_images
.get(index)
.expect("BUG: External image should be resolved by now");
device.bind_external_texture(sampler, texture);
Swizzle::default()
}
TextureSource::TextureCache(index, swizzle) => { let texture = &self.texture_cache_map[&index].texture;
device.bind_texture(sampler, texture, swizzle);
swizzle
}
}
}
// Get the real (OpenGL) texture ID for a given source texture. // For a texture cache texture, the IDs are stored in a vector // map for fast access. fn resolve(&self, texture_id: &TextureSource) -> Option<(&Texture, Swizzle)> { match *texture_id {
TextureSource::Invalid => None,
TextureSource::Dummy => {
Some((&self.dummy_cache_texture, Swizzle::default()))
}
TextureSource::External(..) => {
panic!("BUG: External textures cannot be resolved, they can only be bound.");
}
TextureSource::TextureCache(index, swizzle) => {
Some((&self.texture_cache_map[&index].texture, swizzle))
}
}
}
// Retrieve the deferred / resolved UV rect if an external texture, otherwise // return the default supplied UV rect. fn get_uv_rect(
&self,
source: &TextureSource,
default_value: TexelRect,
) -> TexelRect { match source {
TextureSource::External(TextureSourceExternal { ref index, .. }) => { let texture = self.external_images
.get(index)
.expect("BUG: External image should be resolved by now");
texture.get_uv_rect()
}
_ => {
default_value
}
}
}
/// Returns the size of the texture in pixels fn get_texture_size(&self, texture: &TextureSource) -> DeviceIntSize { match *texture {
TextureSource::Invalid => DeviceIntSize::zero(),
TextureSource::TextureCache(id, _) => { self.texture_cache_map[&id].texture.get_dimensions()
},
TextureSource::External(TextureSourceExternal { index, .. }) => { // If UV coords are normalized then this value will be incorrect. However, the // texture size is currently only used to set the uTextureSize uniform, so that // shaders without access to textureSize() can normalize unnormalized UVs. Which // means this is not a problem. let uv_rect = self.external_images[&index].get_uv_rect();
(uv_rect.uv1 - uv_rect.uv0).abs().to_size().to_i32()
},
TextureSource::Dummy => DeviceIntSize::new(1, 1),
}
}
// We're reporting GPU memory rather than heap-allocations, so we don't // use size_of_op. for item inself.texture_cache_map.values() { let counter = match item.category {
TextureCacheCategory::Atlas => &mut report.atlas_textures,
TextureCacheCategory::Standalone => &mut report.standalone_textures,
TextureCacheCategory::PictureTile => &mut report.picture_tile_textures,
TextureCacheCategory::RenderTarget => &mut report.render_target_textures,
};
*counter += item.texture.size_in_bytes();
}
report
}
fn update_profile(&self, profile: &mut TransactionProfile) { letmut external_image_bytes = 0; for img inself.external_images.values() { let uv_rect = img.get_uv_rect(); // If UV coords are normalized then this value will be incorrect. This is unfortunate // but doesn't impact end users at all. let size = (uv_rect.uv1 - uv_rect.uv0).abs().to_size().to_i32();
// Assume 4 bytes per pixels which is true most of the time but // not always. let bpp = 4;
external_image_bytes += size.area() as usize * bpp;
}
impl BlendMode { /// Decides when a given mix-blend-mode can be implemented in terms of /// simple blending, dual-source blending, advanced blending, or not at /// all based on available capabilities. pubfn from_mix_blend_mode(
mode: MixBlendMode,
advanced_blend: bool,
coherent: bool,
dual_source: bool,
) -> Option<BlendMode> { // If we emulate a mix-blend-mode via simple or dual-source blending, // care must be taken to output alpha As + Ad*(1-As) regardless of what // the RGB output is to comply with the mix-blend-mode spec.
Some(match mode { // If we have coherent advanced blend, just use that.
_ if advanced_blend && coherent => BlendMode::Advanced(mode), // Screen can be implemented as Cs + Cd - Cs*Cd => Cs + Cd*(1-Cs)
MixBlendMode::Screen => BlendMode::Screen, // Exclusion can be implemented as Cs + Cd - 2*Cs*Cd => Cs*(1-Cd) + Cd*(1-Cs)
MixBlendMode::Exclusion => BlendMode::Exclusion, // PlusLighter is basically a clamped add.
MixBlendMode::PlusLighter => BlendMode::PlusLighter, // Multiply can be implemented as Cs*Cd + Cs*(1-Ad) + Cd*(1-As) => Cs*(1-Ad) + Cd*(1 - SRC1=(As-Cs))
MixBlendMode::Multiply if dual_source => BlendMode::MultiplyDualSource, // Otherwise, use advanced blend without coherency if available.
_ if advanced_blend => BlendMode::Advanced(mode), // If advanced blend is not available, then we have to use brush_mix_blend.
_ => return None,
})
}
}
/// Information about the state of the debugging / profiler overlay in native compositing mode. struct DebugOverlayState { /// True if any of the current debug flags will result in drawing a debug overlay.
is_enabled: bool,
/// The current size of the debug overlay surface. None implies that the /// debug surface isn't currently allocated.
current_size: Option<DeviceIntSize>,
/// Tracks buffer damage rects over a series of frames. #[derive(Debug, Default)] pub(crate) struct BufferDamageTracker {
damage_rects: [DeviceRect; 4],
current_offset: usize,
}
impl BufferDamageTracker { /// Sets the damage rect for the current frame. Should only be called *after* /// get_damage_rect() has been called to get the current backbuffer's damage rect. fn push_dirty_rect(&mutself, rect: &DeviceRect) { self.damage_rects[self.current_offset] = rect.clone(); self.current_offset = matchself.current_offset { 0 => self.damage_rects.len() - 1,
n => n - 1,
}
}
/// Gets the damage rect for the current backbuffer, given the backbuffer's age. /// (The number of frames since it was previously the backbuffer.) /// Returns an empty rect if the buffer is valid, and None if the entire buffer is invalid. fn get_damage_rect(&self, buffer_age: usize) -> Option<DeviceRect> { match buffer_age { // 0 means this is a new buffer, so is completely invalid. 0 => None, // 1 means this backbuffer was also the previous frame's backbuffer // (so must have been copied to the frontbuffer). It is therefore entirely valid. 1 => Some(DeviceRect::zero()), // We must calculate the union of the damage rects since this buffer was previously // the backbuffer.
n if n <= self.damage_rects.len() + 1 => {
Some( self.damage_rects.iter()
.cycle()
.skip(self.current_offset + 1)
.take(n - 1)
.fold(DeviceRect::zero(), |acc, r| acc.union(r))
)
} // The backbuffer is older than the number of frames for which we track, // so we treat it as entirely invalid.
_ => None,
}
}
}
/// The renderer is responsible for submitting to the GPU the work prepared by the /// RenderBackend. /// /// We have a separate `Renderer` instance for each instance of WebRender (generally /// one per OS window), and all instances share the same thread. pubstruct Renderer {
result_rx: Receiver<ResultMsg>,
api_tx: Sender<ApiMsg>, pub device: Device,
pending_texture_updates: Vec<TextureUpdateList>, /// True if there are any TextureCacheUpdate pending.
pending_texture_cache_updates: bool,
pending_native_surface_updates: Vec<NativeSurfaceOperation>,
pending_shader_updates: Vec<PathBuf>,
active_documents: FastHashMap<DocumentId, RenderedDocument>,
/// Optional trait object that allows the client /// application to provide external buffers for image data.
external_image_handler: Option<Box<dyn ExternalImageHandler>>,
/// Optional function pointers for measuring memory used by a given /// heap-allocated pointer.
size_of_ops: Option<MallocSizeOfOps>,
/// List of profile results from previous frames. Can be retrieved /// via get_frame_profiles().
cpu_profiles: VecDeque<CpuProfile>,
gpu_profiles: VecDeque<GpuProfile>,
/// Notification requests to be fulfilled after rendering.
notifications: Vec<NotificationRequest>,
device_size: Option<DeviceIntSize>,
/// A lazily created texture for the zoom debugging widget.
zoom_debug_texture: Option<Texture>,
/// The current mouse position. This is used for debugging /// functionality only, such as the debug zoom widget.
cursor_position: DeviceIntPoint,
/// Guards to check if we might be rendering a frame with expired texture /// cache entries.
shared_texture_cache_cleared: bool,
/// The set of documents which we've seen a publish for since last render.
documents_seen: FastHashSet<DocumentId>,
/// The compositing config, affecting how WR composites into the final scene.
compositor_config: CompositorConfig,
current_compositor_kind: CompositorKind,
/// Maintains a set of allocated native composite surfaces. This allows any /// currently allocated surfaces to be cleaned up as soon as deinit() is /// called (the normal bookkeeping for native surfaces exists in the /// render backend thread).
allocated_native_surfaces: FastHashSet<NativeSurfaceId>,
/// If true, partial present state has been reset and everything needs to /// be drawn on the next render.
force_redraw: bool,
/// State related to the debug / profiling overlays
debug_overlay_state: DebugOverlayState,
/// Tracks the dirty rectangles from previous frames. Used on platforms /// that require keeping the front buffer fully correct when doing /// partial present (e.g. unix desktop with EGL_EXT_buffer_age).
buffer_damage_tracker: BufferDamageTracker,
/// Count consecutive oom frames to detectif we are stuck unable to render /// in a loop.
consecutive_oom_frames: u32,
/// update() defers processing of ResultMsg, if frame_publish_id of /// ResultMsg::PublishDocument exceeds target_frame_publish_id.
target_frame_publish_id: Option<FramePublishId>,
/// Hold a next ResultMsg that will be handled by update().
pending_result_msg: Option<ResultMsg>,
/// Hold previous frame compositing state with layer compositor.
layer_compositor_frame_state_in_prev_frame: Option<LayerCompositorFrameState>,
/// Hold DebugItems of DebugFlags::EXTERNAL_COMPOSITE_BORDERS for debug overlay
external_composite_debug_items: Vec<DebugItem>,
/// On-demand RenderDoc frame capture, driven from the debugger / wrshell. #[cfg(feature = "debugger")]
renderdoc: crate::renderdoc::RenderDocCapture, /// Pending reply channel for an in-flight RenderDoc capture request; sent the /// written .rdc path (or an error) once the next frame has been captured. #[cfg(feature = "debugger")]
renderdoc_capture_reply: Option<crate::api::channel::Sender<crate::api::debugger::RenderDocReply>>,
}
/// Update the current position of the debug cursor. pubfn set_cursor_position(
&mutself,
position: DeviceIntPoint,
) { self.cursor_position = position;
}
/// Returns the Epoch of the current frame in a pipeline. pubfn current_epoch(&self, document_id: DocumentId, pipeline_id: PipelineId) -> Option<Epoch> { self.pipeline_info.epochs.get(&(pipeline_id, document_id)).cloned()
}
/// Processes the result queue. /// /// Should be called before `render()`, as texture cache updates are done here. pubfn update(&mutself) {
profile_scope!("update");
// Pull any pending results and return the most recent. whilelet Some(msg) = self.get_next_result_msg() { match msg {
ResultMsg::PublishPipelineInfo(mut pipeline_info) => { for ((pipeline_id, document_id), epoch) in pipeline_info.epochs { self.pipeline_info.epochs.insert((pipeline_id, document_id), epoch);
} self.pipeline_info.removed_pipelines.extend(pipeline_info.removed_pipelines.drain(..));
}
ResultMsg::PublishDocument(
_,
document_id, mut doc,
resource_update_list,
) => { // Add a new document to the active set
// If the document we are replacing must be drawn (in order to // update the texture cache), issue a render just to // off-screen targets, ie pass None to render_impl. We do this // because a) we don't need to render to the main framebuffer // so it is cheaper not to, and b) doing so without a // subsequent present would break partial present. let prev_frame_memory = iflet Some(mut prev_doc) = self.active_documents.remove(&document_id) {
doc.profile.merge(&mut prev_doc.profile);
iflet Some(memory) = prev_frame_memory { // We just dropped the frame a few lives above. There should be no // live allocations left in the frame's memory.
memory.assert_memory_reusable();
}
self.active_documents.insert(document_id, doc);
// IMPORTANT: The pending texture cache updates must be applied // *after* the previous frame has been rendered above // (if neceessary for a texture cache update). For // an example of why this is required: // 1) Previous frame contains a render task that // targets Texture X. // 2) New frame contains a texture cache update which // frees Texture X. // 3) bad stuff happens.
//TODO: associate `document_id` with target window self.pending_texture_cache_updates |= !resource_update_list.texture_updates.updates.is_empty(); self.pending_texture_updates.push(resource_update_list.texture_updates); self.pending_native_surface_updates.extend(resource_update_list.native_surface_updates); self.documents_seen.insert(document_id);
}
ResultMsg::UpdateResources {
resource_updates,
memory_pressure,
} => { if memory_pressure { // If a memory pressure event arrives _after_ a new scene has // been published that writes persistent targets (i.e. cached // render tasks to the texture cache, or picture cache tiles) // but _before_ the next update/render loop, those targets // will not be updated due to the active_documents list being // cleared at the end of this message. To work around that, // if any of the existing documents have not rendered yet, and // have picture/texture cache targets, force a render so that // those targets are updated. let active_documents = mem::replace(
&mutself.active_documents,
FastHashMap::default(),
); for (doc_id, mut doc) in active_documents { if doc.frame.must_be_drawn() { // As this render will not be presented, we must pass None to // render_impl. This avoids interfering with partial present // logic, as well as being more efficient. self.render_impl(
doc_id,
&mut doc,
None, 0,
).ok();
}
}
}
// Flush the render target pool on memory pressure. // // This needs to be separate from the block below because // the device module asserts if we delete textures while // not in a frame. if memory_pressure { self.texture_upload_pbo_pool.on_memory_pressure(&mutself.device); self.staging_texture_pool.delete_textures(&mutself.device); iflet Some(texture) = self.gpu_buffer_texture_f.take() { self.device.delete_texture(texture);
} iflet Some(texture) = self.gpu_buffer_texture_i.take() { self.device.delete_texture(texture);
}
}
self.device.end_frame();
}
ResultMsg::RenderDocumentOffscreen(document_id, mut offscreen_doc, resources) => { // Flush pending operations if needed (See comment in the match arm for // PublishPipelineInfo).
self.render_impl(
document_id,
&mut offscreen_doc,
None, 0,
).unwrap();
}
ResultMsg::AppendNotificationRequests(mut notifications) => { // We need to know specifically if there are any pending // TextureCacheUpdate updates in any of the entries in // pending_texture_updates. They may simply be nops, which do not // need to prevent issuing the notification, and if so, may not // cause a timely frame render to occur to wake up any listeners. if !self.pending_texture_cache_updates {
drain_filter(
&mut notifications,
|n| { n.when() == Checkpoint::FrameTexturesUpdated },
|n| { n.notify(); },
);
} self.notifications.append(&mut notifications);
}
ResultMsg::ForceRedraw => { self.force_redraw = true;
}
ResultMsg::RefreshShader(path) => { self.pending_shader_updates.push(path);
}
ResultMsg::SetParameter(ref param) => { self.device.set_parameter(param); self.profiler.set_parameter(param);
}
ResultMsg::DebugOutput(output) => match output { #[cfg(feature = "capture")]
DebugOutput::SaveCapture(config, deferred) => { self.save_capture(config, deferred);
} #[cfg(feature = "replay")]
DebugOutput::LoadCapture(config, plain_externals) => { self.active_documents.clear(); self.load_capture(config, plain_externals);
}
},
ResultMsg::DebugCommand(command) => { self.handle_debug_command(command);
}
}
}
}
/// update() defers processing of ResultMsg, if frame_publish_id of /// ResultMsg::PublishDocument exceeds target_frame_publish_id. pubfn set_target_frame_publish_id(&mutself, publish_id: FramePublishId) { self.target_frame_publish_id = Some(publish_id);
}
fn handle_debug_command(&mutself, command: DebugCommand) { match command {
DebugCommand::SetPictureTileSize(_) |
DebugCommand::SetMaximumSurfaceSize(_) |
DebugCommand::GenerateFrame => {
panic!("Should be handled by render backend");
} #[cfg(feature = "debugger")]
DebugCommand::Query(ref query) => { match query.kind {
DebugQueryKind::SpatialTree { .. } => {
panic!("Should be handled by render backend");
}
DebugQueryKind::CompositorConfig { .. } => { let result = matchself.active_documents.iter().last() {
Some((_, doc)) => {
doc.frame.composite_state.print_to_string()
}
None => { "No active documents".into()
}
};
query.result.send(result).ok();
}
DebugQueryKind::CompositorView { .. } => { let result = matchself.active_documents.iter().last() {
Some((_, doc)) => { let info = CompositorDebugInfo::from(&doc.frame.composite_state);
serde_json::to_string(&info).unwrap()
}
None => { "No active documents".into()
}
};
query.result.send(result).ok();
}
DebugQueryKind::Textures { category } => { letmut texture_list = Vec::new();
for (id, item) in &self.texture_resolver.texture_cache_map { if category.is_some() && category != Some(item.category) { continue;
}
let size = item.texture.get_dimensions(); let format = item.texture.get_format(); let buffer_size = (size.area() * format.bytes_per_pixel()) as usize; letmut data = vec![0u8; buffer_size]; let rect = size.cast_unit().into(); self.device.attach_read_texture(&item.texture); self.device.read_pixels_into(rect, format, &mut data);
let category_str = match item.category {
TextureCacheCategory::Atlas => "atlas",
TextureCacheCategory::Standalone => "standalone",
TextureCacheCategory::PictureTile => "tile",
TextureCacheCategory::RenderTarget => "target",
};
let texture_msg = DebuggerTextureContent {
name: format!("{category_str}-{:02}", id.0),
category: item.category,
width: size.width as u32,
height: size.height as u32,
format,
data,
};
texture_list.push(texture_msg);
} self.device.reset_read_target(); self.device.end_frame();
query.result.send(serde_json::to_string(&texture_list).unwrap()).ok();
}
}
}
DebugCommand::SaveCapture(..) |
DebugCommand::LoadCapture(..) |
DebugCommand::StartCaptureSequence(..) |
DebugCommand::StopCaptureSequence => {
panic!("Capture commands are not welcome here! Did you build with 'capture' feature?")
}
DebugCommand::ClearCaches(_)
| DebugCommand::SimulateLongSceneBuild(_)
| DebugCommand::EnableNativeCompositor(_)
| DebugCommand::SetBatchingLookback(_) => {}
DebugCommand::SetFlags(flags) => { self.set_debug_flags(flags);
}
DebugCommand::GetDebugFlags(tx) => {
tx.send(self.debug_flags).unwrap();
}
DebugCommand::SetRenderCommandLog(enabled) => { if enabled && self.command_log.is_none() { self.command_log = Some(RenderCommandLog::new());
} elseif !enabled { self.command_log = None;
}
} #[cfg(feature = "debugger")]
DebugCommand::AddDebugClient(client) => { self.debugger.add_client(
client, self.debug_flags,
&self.profiler,
);
} #[cfg(feature = "debugger")]
DebugCommand::CaptureRenderDoc(reply) => { ifself.renderdoc.is_available() { self.renderdoc.arm(); self.renderdoc_capture_reply = Some(reply);
} else { let _ = reply.send(crate::api::debugger::RenderDocReply::Error( "RenderDoc not available (launch the host with \
LD_PRELOAD=librenderdoc.so)".to_string(),
));
}
}
}
}
/// Set a callback for handling external images. pubfn set_external_image_handler(&mutself, handler: Box<dyn ExternalImageHandler>) { self.external_image_handler = Some(handler);
}
/// Retrieve (and clear) the current list of recorded frame profiles. pubfn get_frame_profiles(&mutself) -> (Vec<CpuProfile>, Vec<GpuProfile>) { let cpu_profiles = self.cpu_profiles.drain(..).collect(); let gpu_profiles = self.gpu_profiles.drain(..).collect();
(cpu_profiles, gpu_profiles)
}
/// Reset the current partial present state. This forces the entire framebuffer /// to be refreshed next time `render` is called. pubfn force_redraw(&mutself) { self.force_redraw = true;
}
/// Renders the current frame. /// /// A Frame is supplied by calling [`generate_frame()`][webrender_api::Transaction::generate_frame]. /// buffer_age is the age of the current backbuffer. It is only relevant if partial present /// is active, otherwise 0 should be passed here. pubfn render(
&mutself,
device_size: DeviceIntSize,
buffer_age: usize,
) -> Result<RenderResults, Vec<RendererError>> { self.device_size = Some(device_size);
// TODO(gw): We want to make the active document that is // being rendered configurable via the public // API in future. For now, just select the last // added document as the active one to render // (Gecko only ever creates a single document // per renderer right now). let doc_id = self.active_documents.keys().last().cloned();
let result = match doc_id {
Some(doc_id) => { // Remove the doc from the map to appease the borrow checker letmut doc = self.active_documents
.remove(&doc_id)
.unwrap();
let size = if !device_size.is_empty() {
Some(device_size)
} else {
None
};
#[cfg(feature = "debugger")] let capture = self.renderdoc.take_request(); #[cfg(feature = "debugger")] if capture { self.renderdoc.start();
}
let result = self.render_impl(
doc_id,
&mut doc,
size,
buffer_age,
);
#[cfg(feature = "debugger")] if capture { let path = self.renderdoc.end(); iflet Some(reply) = self.renderdoc_capture_reply.take() { let result = match path {
Some(p) => crate::api::debugger::RenderDocReply::Path(
p.to_string_lossy().into_owned()
),
None => crate::api::debugger::RenderDocReply::Error( "RenderDoc capture failed (launch the host with \
LD_PRELOAD=librenderdoc.so)".to_string()
),
}; let _ = reply.send(result);
}
}
letmut oom = false; iflet Err(ref errors) = result { for error in errors { if matches!(error, &RendererError::OutOfMemory) {
oom = true; break;
}
}
}
if oom { let _ = self.api_tx.send(ApiMsg::MemoryPressure); // Ensure we don't get stuck in a loop. self.consecutive_oom_frames += 1;
assert!(self.consecutive_oom_frames < 5, "Renderer out of memory");
} else { self.consecutive_oom_frames = 0;
}
// This is the end of the rendering pipeline. If some notifications are is still there, // just clear them and they will autimatically fire the Checkpoint::TransactionDropped // event. Otherwise they would just pile up in this vector forever. self.notifications.clear();
self.external_composite_debug_items = Vec::new();
tracy_frame_marker!();
result
}
/// Update the state of any debug / profiler overlays. This is currently only needed /// when running with the native compositor enabled. fn update_debug_overlay(
&mutself,
framebuffer_size: DeviceIntSize,
has_debug_items: bool,
) { // If any of the following debug flags are set, something will be drawn on the debug overlay. self.debug_overlay_state.is_enabled = has_debug_items || self.debug_flags.intersects(
DebugFlags::PROFILER_DBG |
DebugFlags::RENDER_TARGET_DBG |
DebugFlags::TEXTURE_CACHE_DBG |
DebugFlags::EPOCHS |
DebugFlags::PICTURE_CACHING_DBG |
DebugFlags::PICTURE_BORDERS |
DebugFlags::ZOOM_DBG |
DebugFlags::WINDOW_VISIBILITY_DBG |
DebugFlags::EXTERNAL_COMPOSITE_BORDERS
);
// Update the debug overlay surface, if we are running in native compositor mode. iflet CompositorKind::Native { .. } = self.current_compositor_kind { let compositor = self.compositor_config.compositor().unwrap();
// If there is a current surface, destroy it if we don't need it for this frame, or if // the size has changed. iflet Some(current_size) = self.debug_overlay_state.current_size { if !self.debug_overlay_state.is_enabled || current_size != framebuffer_size {
compositor.destroy_surface(&mutself.device, NativeSurfaceId::DEBUG_OVERLAY); self.debug_overlay_state.current_size = None;
}
}
// Allocate a new surface, if we need it and there isn't one. ifself.debug_overlay_state.is_enabled && self.debug_overlay_state.current_size.is_none() {
compositor.create_surface(
&mutself.device,
NativeSurfaceId::DEBUG_OVERLAY,
DeviceIntPoint::zero(),
framebuffer_size, false,
);
compositor.create_tile(
&mutself.device,
NativeTileId::DEBUG_OVERLAY,
); self.debug_overlay_state.current_size = Some(framebuffer_size);
}
}
}
/// Bind a draw target for the debug / profiler overlays, if required. fn bind_debug_overlay(&mutself, device_size: DeviceIntSize) -> Option<DrawTarget> { // Debug overlay setup are only required in native compositing mode ifself.debug_overlay_state.is_enabled { matchself.current_compositor_kind {
CompositorKind::Native { .. } => { let compositor = self.compositor_config.compositor().unwrap(); let surface_size = self.debug_overlay_state.current_size.unwrap();
// Ensure old surface is invalidated before binding
compositor.invalidate_tile(
&mutself.device,
NativeTileId::DEBUG_OVERLAY,
DeviceIntRect::from_size(surface_size),
); // Bind the native surface let surface_info = compositor.bind(
&mutself.device,
NativeTileId::DEBUG_OVERLAY,
DeviceIntRect::from_size(surface_size),
DeviceIntRect::from_size(surface_size),
);
// Bind the native surface to current FBO target let draw_target = DrawTarget::NativeSurface {
offset: surface_info.origin,
external_fbo_id: surface_info.fbo_id,
dimensions: surface_size,
}; self.device.bind_draw_target(draw_target);
// When native compositing, clear the debug overlay each frame. self.device.clear_target(
Some([0.0, 0.0, 0.0, 0.0]),
None, // debug renderer does not use depth
None,
);
Some(draw_target)
}
CompositorKind::Layer { .. } => { let compositor = self.compositor_config.layer_compositor().unwrap();
compositor.bind_layer(self.debug_overlay_state.layer_index, &[]);
self.device.clear_target(
Some([0.0, 0.0, 0.0, 0.0]),
None, // debug renderer does not use depth
None,
);
Some(DrawTarget::new_default(device_size, self.device.surface_origin_is_top_left()))
}
CompositorKind::Draw { .. } => { // If we're not using the native compositor, then the default // frame buffer is already bound. Create a DrawTarget for it and // return it.
Some(DrawTarget::new_default(device_size, self.device.surface_origin_is_top_left()))
}
}
} else {
None
}
}
/// Unbind the draw target for debug / profiler overlays, if required. fn unbind_debug_overlay(&mutself) { // Debug overlay setup are only required in native compositing mode ifself.debug_overlay_state.is_enabled { matchself.current_compositor_kind {
CompositorKind::Native { .. } => { let compositor = self.compositor_config.compositor().unwrap(); // Unbind the draw target and add it to the visual tree to be composited
compositor.unbind(&mutself.device);
let clip_rect = DeviceIntRect::from_size( self.debug_overlay_state.current_size.unwrap(),
);
// If device_size is None, don't render to the main frame buffer. This is useful to // update texture cache render tasks but avoid doing a full frame render. If the // render is not going to be presented, then this must be set to None, as performing a // composite without a present will confuse partial present. fn render_impl(
&mutself,
doc_id: DocumentId,
active_doc: &mut RenderedDocument, mut device_size: Option<DeviceIntSize>,
buffer_age: usize,
) -> Result<RenderResults, Vec<RendererError>> {
profile_scope!("render"); letmut results = RenderResults::default(); self.profile.end_time_if_started(profiler::FRAME_SEND_TIME); self.profile.start_time(profiler::RENDERER_TIME);
// The texture resolver scope should be outside of any rendering, including // debug rendering. This ensures that when we return render targets to the // pool via glInvalidateFramebuffer, we don't do any debug rendering after // that point. Otherwise, the bind / invalidate / bind logic trips up the // render pass logic in tiled / mobile GPUs, resulting in an extra copy / // resolve step when the debug overlay is enabled. self.texture_resolver.begin_frame();
let cpu_frame_id = { let _gm = self.gpu_profiler.start_marker("begin frame"); let frame_id = self.device.begin_frame(); self.gpu_profiler.begin_frame(frame_id);
if !active_doc.frame.present { // Setting device_size to None is what ensures compositing/presenting // the frame is skipped in the rest of this module.
device_size = None;
}
iflet Some(device_size) = device_size { // Inform the client that we are starting a composition transaction if native // compositing is enabled. This needs to be done early in the frame, so that // we can create debug overlays after drawing the main surfaces. iflet CompositorKind::Native { .. } = self.current_compositor_kind { let compositor = self.compositor_config.compositor().unwrap();
compositor.begin_frame(&mutself.device);
}
// Update the state of the debug overlay surface, ensuring that // the compositor mode has a suitable surface to draw to, if required. self.update_debug_overlay(device_size, !active_doc.frame.debug_items.is_empty());
}
let frame = &mut active_doc.frame; let profile = &mut active_doc.profile;
assert!(self.current_compositor_kind == frame.composite_state.compositor_kind);
ifself.shared_texture_cache_cleared {
assert!(self.documents_seen.contains(&doc_id), "Cleared texture cache without sending new document frame.");
}
// Now that external images are resolved, copy their (potentially Y-flipped) uv // rects into the quad segment blocks that reference them.
frame.gpu_buffer_f.apply_deferred_uv_copies();
// TODO(nical): do this automatically by selecting counters in the wr profiler // Profile marker for the number of invalidated picture cache if thread_is_being_profiled() { let duration = Duration::new(0,0); iflet Some(n) = self.profile.get(profiler::RENDERED_PICTURE_TILES) { let message = (n as usize).to_string();
add_text_marker("NumPictureCacheInvalidated", &message, duration);
}
}
if device_size.is_some() { self.draw_frame_debug_items(&frame.debug_items);
}
let _gm = self.gpu_profiler.start_marker("end frame"); self.gpu_profiler.end_frame();
let t = self.profile.end_time(profiler::RENDERER_TIME); self.profile.end_time_if_started(profiler::TOTAL_FRAME_CPU_TIME);
let current_time = zeitstempel::now(); if device_size.is_some() { let time = profiler::ns_to_ms(current_time - self.last_time); self.profile.set(profiler::FRAME_TIME, time);
}
let debug_overlay = device_size.and_then(|device_size| { // Bind a surface to draw the debug / profiler information to. self.bind_debug_overlay(device_size).map(|draw_target| { self.draw_render_target_debug(&draw_target); self.draw_texture_cache_debug(&draw_target); self.draw_zoom_debug(device_size); self.draw_epoch_debug(); self.draw_window_visibility_debug(); self.draw_external_composite_borders_debug();
draw_target
})
});
Telemetry::record_renderer_time(Duration::from_micros((t * 1000.00) as u64)); ifself.profile.get(profiler::SHADER_BUILD_TIME).is_none() {
Telemetry::record_renderer_time_no_sc(Duration::from_micros((t * 1000.00) as u64));
}
ifself.max_recorded_profiles > 0 { whileself.cpu_profiles.len() >= self.max_recorded_profiles { self.cpu_profiles.pop_front();
} let cpu_profile = CpuProfile::new(
cpu_frame_id,
(self.profile.get_or(profiler::FRAME_BUILDING_TIME, 0.0) * 1000000.0) as u64,
(self.profile.get_or(profiler::RENDERER_TIME, 0.0) * 1000000.0) as u64, self.profile.get_or(profiler::DRAW_CALLS, 0.0) as usize,
); self.cpu_profiles.push_back(cpu_profile);
}
if thread_is_being_profiled() { let duration = Duration::new(0,0); let message = (self.profile.get_or(profiler::DRAW_CALLS, 0.0) as usize).to_string();
add_text_marker("NumDrawCalls", &message, duration);
}
iflet Some(stats) = active_doc.frame_stats.take() { // Copy the full frame stats to RendererStats
results.stats.merge(&stats);
self.profiler.update_frame_stats(stats);
}
// Turn the render reasons bitflags into something we can see in the profiler. // For now this is just a binary yes/no for each bit, which means that when looking // at "Render reasons" in the profiler HUD the average view indicates the proportion // of frames that had the bit set over a half second window whereas max shows whether // the bit as been set at least once during that time window. // We could implement better ways to visualize this information. let add_markers = thread_is_being_profiled(); for i in0..RenderReasons::NUM_BITS { let counter = profiler::RENDER_REASON_FIRST + i as usize; letmut val = 0.0; let reason_bit = RenderReasons::from_bits_truncate(1 << i); if active_doc.render_reasons.contains(reason_bit) {
val = 1.0; if add_markers { let event_str = format!("Render reason {:?}", reason_bit);
add_event_marker(&event_str);
}
} self.profile.set(counter, val);
}
active_doc.render_reasons = RenderReasons::empty();
// Note: this clears the values in self.profile. self.profiler.set_counters(&mutself.profile);
// If debugger is enabled, collect any profiler updates before value is overwritten // during update below. #[cfg(feature = "debugger")] self.debugger.update( self.debug_flags,
&self.profiler,
&self.command_log,
);
// Note: profile counters must be set before this or they will count for next frame. self.profiler.update();
ifself.debug_flags.intersects(DebugFlags::PROFILER_DBG | DebugFlags::PROFILER_CAPTURE) { iflet Some(device_size) = device_size { //TODO: take device/pixel ratio into equation? iflet Some(debug_renderer) = self.debug.get_mut(&mutself.device) { self.profiler.draw_profile( self.frame_counter,
debug_renderer,
device_size,
);
}
}
}
iflet Some(debug_renderer) = self.debug.try_get_mut() { let small_screen = self.debug_flags.contains(DebugFlags::SMALL_SCREEN); let scale = if small_screen { 1.6 } else { 1.0 }; // TODO(gw): Tidy this up so that compositor config integrates better // with the (non-compositor) surface y-flip options. let surface_origin_is_top_left = matchself.current_compositor_kind {
CompositorKind::Native { .. } => true,
CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => self.device.surface_origin_is_top_left(),
}; // If there is a debug overlay, render it. Otherwise, just clear // the debug renderer.
debug_renderer.render(
&mutself.device,
debug_overlay.and(device_size),
scale,
surface_origin_is_top_left,
);
}
if debug_overlay.is_some() { self.last_time = current_time;
// Unbind the target for the debug overlay. No debug or profiler drawing // can occur afer this point. self.unbind_debug_overlay();
}
if device_size.is_some() { // Inform the client that we are finished this composition transaction if native // compositing is enabled. This must be called after any debug / profiling compositor // surfaces have been drawn and added to the visual tree. matchself.current_compositor_kind {
CompositorKind::Layer { .. } => { let compositor = self.compositor_config.layer_compositor().unwrap();
compositor.end_frame();
}
CompositorKind::Native { .. } => {
profile_scope!("compositor.end_frame"); let compositor = self.compositor_config.compositor().unwrap();
compositor.end_frame(&mutself.device);
}
CompositorKind::Draw { .. } => {}
}
}
fn update_gpu_profile(&mutself, device_size: DeviceIntSize) { let _gm = self.gpu_profiler.start_marker("build samples"); // Block CPU waiting for last frame's GPU profiles to arrive. // In general this shouldn't block unless heavily GPU limited. let (gpu_frame_id, timers, samplers) = self.gpu_profiler.build_samples();
for update_list in pending_texture_updates.drain(..) { // Handle copies from one texture to another. for ((src_tex, dst_tex), copies) in &update_list.copies {
let dest_texture = &self.texture_resolver.texture_cache_map[&dst_tex].texture; let dst_texture_size = dest_texture.get_dimensions().to_f32();
letmut copy_instances = Vec::new(); for copy in copies {
copy_instances.push(CopyInstance {
src_rect: copy.src_rect.to_f32(),
dst_rect: copy.dst_rect.to_f32(),
dst_texture_size,
});
}
let draw_target = DrawTarget::from_texture(dest_texture, false); self.device.bind_draw_target(draw_target);
// Find any textures that will need to be deleted in this group of allocations. letmut pending_deletes = Vec::new(); for allocation in &update_list.allocations { let old = self.texture_resolver.texture_cache_map.remove(&allocation.id); match allocation.kind {
TextureCacheAllocationKind::Alloc(_) => {
assert!(old.is_none(), "Renderer and backend disagree!");
}
TextureCacheAllocationKind::Reset(_) |
TextureCacheAllocationKind::Free => {
assert!(old.is_some(), "Renderer and backend disagree!");
}
} iflet Some(old) = old {
// Regenerate the cache allocation info so we can search through deletes for reuse. let size = old.texture.get_dimensions(); let info = TextureCacheAllocInfo {
width: size.width,
height: size.height,
format: old.texture.get_format(),
filter: old.texture.get_filter(),
target: old.texture.get_target(),
is_shared_cache: old.texture.flags().contains(TextureFlags::IS_SHARED_TEXTURE_CACHE),
has_depth: old.texture.supports_depth(),
category: old.category,
};
pending_deletes.push((old.texture, info));
}
} // Look for any alloc or reset that has matching alloc info and save it from being deleted. letmut reused_textures = VecDeque::with_capacity(pending_deletes.len()); for allocation in &update_list.allocations { match allocation.kind {
TextureCacheAllocationKind::Alloc(ref info) |
TextureCacheAllocationKind::Reset(ref info) => {
reused_textures.push_back(
pending_deletes.iter()
.position(|(_, old_info)| *old_info == *info)
.map(|index| pending_deletes.swap_remove(index).0)
);
}
TextureCacheAllocationKind::Free => {}
}
}
// Now that we've saved as many deletions for reuse as we can, actually delete whatever is left. if !pending_deletes.is_empty() { let delete_texture_start = zeitstempel::now(); for (texture, _) in pending_deletes {
add_event_marker("TextureCacheFree"); self.device.delete_texture(texture);
}
delete_cache_texture_time += zeitstempel::now() - delete_texture_start;
}
for allocation in update_list.allocations { match allocation.kind {
TextureCacheAllocationKind::Alloc(_) => add_event_marker("TextureCacheAlloc"),
TextureCacheAllocationKind::Reset(_) => add_event_marker("TextureCacheReset"),
TextureCacheAllocationKind::Free => {}
}; match allocation.kind {
TextureCacheAllocationKind::Alloc(ref info) |
TextureCacheAllocationKind::Reset(ref info) => { let create_cache_texture_start = zeitstempel::now(); // Create a new native texture, as requested by the texture cache. // If we managed to reuse a deleted texture, then prefer that instead. // // Ensure no PBO is bound when creating the texture storage, // or GL will attempt to read data from there. letmut texture = reused_textures.pop_front().unwrap_or(None).unwrap_or_else(|| { self.device.create_texture(
info.target,
info.format,
info.width,
info.height,
info.filter, // This needs to be a render target because some render // tasks get rendered into the texture cache.
Some(RenderTargetInfo { has_depth: info.has_depth }),
)
});
if info.is_shared_cache {
texture.flags_mut()
.insert(TextureFlags::IS_SHARED_TEXTURE_CACHE);
// On Mali-Gxx devices we use batched texture uploads as it performs much better. // However, due to another driver bug we must ensure the textures are fully cleared, // otherwise we get visual artefacts when blitting to the texture cache. ifself.device.use_batched_texture_uploads() &&
!self.device.get_capabilities().supports_render_target_partial_update
{ self.clear_texture(&texture, [0.0; 4]);
}
// Textures in the cache generally don't need to be cleared, // but we do so if the debug display is active to make it // easier to identify unallocated regions. ifself.debug_flags.contains(DebugFlags::TEXTURE_CACHE_DBG) { self.clear_texture(&texture, TEXTURE_CACHE_DBG_CLEAR_COLOR);
}
}
if create_cache_texture_time > 0 { self.profile.set(
profiler::CREATE_CACHE_TEXTURE_TIME,
profiler::ns_to_ms(create_cache_texture_time)
);
} if delete_cache_texture_time > 0 { self.profile.set(
profiler::DELETE_CACHE_TEXTURE_TIME,
profiler::ns_to_ms(delete_cache_texture_time)
)
}
let t = self.profile.end_time(profiler::TEXTURE_CACHE_UPDATE_TIME); self.resource_upload_time += t;
Telemetry::record_texture_cache_update_time(Duration::from_micros((t * 1000.00) as u64));
// TODO: this probably isn't the best place for this. iflet Some(ref texture) = self.dither_matrix_texture { self.device.bind_texture(TextureSampler::Dither, texture, Swizzle::default());
}
}
// If we end up with an empty draw call here, that means we have // probably introduced unnecessary batch breaks during frame // building - so we should be catching this earlier and removing // the batch.
debug_assert!(!data.is_empty());
let vao = &self.vaos[vertex_array_kind]; self.device.bind_vao(vao);
fn handle_readback_composite(
&mutself,
draw_target: DrawTarget,
uses_scissor: bool,
backdrop: &RenderTask,
readback: &RenderTask,
) { // Extract the rectangle in the backdrop surface's device space of where // we need to read from. let readback_origin = match readback.kind {
RenderTaskKind::Readback(ReadbackTask { readback_origin: Some(o), .. }) => o,
RenderTaskKind::Readback(ReadbackTask { readback_origin: None, .. }) => { // If this is a dummy readback, just early out. We know that the // clear of the target will ensure the task rect is already zero alpha, // so it won't affect the rendering output. return;
}
_ => unreachable!(),
};
if uses_scissor { self.device.disable_scissor();
}
let texture_source = TextureSource::TextureCache(
readback.get_target_texture(),
Swizzle::default(),
); let (cache_texture, _) = self.texture_resolver
.resolve(&texture_source).expect("bug: no source texture");
// Before submitting the composite batch, do the // framebuffer readbacks that are needed for each // composite operation in this batch. let readback_rect = readback.get_target_rect(); let backdrop_rect = backdrop.get_target_rect(); let (backdrop_screen_origin, _) = match backdrop.kind {
RenderTaskKind::Picture(ref task_info) => (task_info.content_origin, task_info.device_pixel_scale),
_ => panic!("bug: composite on non-picture?"),
};
// Bind the FBO to blit the backdrop to. // Called per-instance in case the FBO changes. The device will skip // the GL call if the requested target is already bound. let cache_draw_target = DrawTarget::from_texture(
cache_texture, false,
);
// Get the rect that we ideally want, in space of the parent surface let wanted_rect = DeviceRect::from_origin_and_size(
readback_origin,
readback_rect.size().to_f32(),
);
// Get the rect that is available on the parent surface. It may be smaller // than desired because this is a picture cache tile covering only part of // the wanted rect and/or because the parent surface was clipped. let avail_rect = DeviceRect::from_origin_and_size(
backdrop_screen_origin,
backdrop_rect.size().to_f32(),
);
iflet Some(int_rect) = wanted_rect.intersection(&avail_rect) { // If there is a valid intersection, work out the correct origins and // sizes of the copy rects, and do the blit. let copy_size = int_rect.size().to_i32();
let src_origin = backdrop_rect.min.to_f32() +
int_rect.min.to_vector() -
backdrop_screen_origin.to_vector();
let src = DeviceIntRect::from_origin_and_size(
src_origin.to_i32(),
copy_size,
);
let dest_origin = readback_rect.min.to_f32() +
int_rect.min.to_vector() -
readback_origin.to_vector();
let dest = DeviceIntRect::from_origin_and_size(
dest_origin.to_i32(),
copy_size,
);
// Should always be drawing to picture cache tiles or off-screen surface!
debug_assert!(!draw_target.is_default()); let device_to_framebuffer = Scale::new(1i32);
// Restore draw target to current pass render target, and reset // the read target. self.device.bind_draw_target(draw_target); self.device.reset_read_target();
if uses_scissor { self.device.enable_scissor();
}
}
let has_prim_instances = prim_instances.iter().any(|map| !map.is_empty()); if has_prim_instances || !prim_instances_with_scissor.is_empty() { let _timer = self.gpu_profiler.start_timer(GPU_TAG_INDIRECT_PRIM);
self.set_blend(false, FramebufferKind::Other);
for (pattern_idx, prim_instances_map) in prim_instances.iter().enumerate() { if prim_instances_map.is_empty() { continue;
} let pattern = PatternKind::from_u32(pattern_idx as u32);
for (scissor_rect, instances) in &masks.mask_instances_fast_with_scissor { self.device.set_scissor_rect(draw_target.to_framebuffer_rect(*scissor_rect));
for ((scissor_rect, texture), prim_instances) in &masks.image_mask_instances_with_scissor { self.device.set_scissor_rect(draw_target.to_framebuffer_rect(*scissor_rect));
for (scissor_rect, instances) in &masks.mask_instances_slow_with_scissor { self.device.set_scissor_rect(draw_target.to_framebuffer_rect(*scissor_rect));
let _timer = self.gpu_profiler.start_timer(GPU_TAG_BLIT);
// TODO(gw): For now, we don't bother batching these by source texture. // If if ever shows up as an issue, we can easily batch them. for blit in blits { let (source, source_rect) = { // A blit from the child render task into this target. // TODO(gw): Support R8 format here once we start // creating mips for alpha masks. let task = &render_tasks[blit.source]; let source_rect = blit.source_rect.translate(task.get_target_rect().min.to_vector()); let source_texture = task.get_texture_source();
(source_texture, source_rect)
};
let (texture, swizzle) = self.texture_resolver
.resolve(&source)
.expect("BUG: invalid source texture");
if swizzle != Swizzle::default() {
error!("Swizzle {:?} can't be handled by a blit", swizzle);
}
let read_target = DrawTarget::from_texture(
texture, false,
);
let _timer = self.gpu_profiler.start_timer(GPU_TAG_SCALE); for (source, instances) in scalings { let buffer_kind = source.image_buffer_kind();
// When the source texture is an external texture, the UV rect is not known // when the external surface descriptor is created, because external textures // are not resolved until the lock() callback is invoked at the start of the // frame render. We must therefore override the source rects now. let uv_override_instances; let instances = match source {
TextureSource::External(..) => {
uv_override_instances = instances.iter().map(|instance| { letmut new_instance = instance.clone(); let texel_rect: TexelRect = self.texture_resolver.get_uv_rect(
&source,
instance.source_rect.cast().into()
).into();
new_instance.source_rect = DeviceRect::new(texel_rect.uv0, texel_rect.uv1);
new_instance
}).collect::<Vec<_>>();
uv_override_instances.as_slice()
}
_ => instances.as_slice()
};
fn handle_resolve(
&mutself,
resolve_op: &ResolveOp,
render_tasks: &RenderTaskGraph,
draw_target: DrawTarget,
) { for src_task_id in &resolve_op.src_task_ids { let src_task = &render_tasks[*src_task_id]; let src_info = match src_task.kind {
RenderTaskKind::Picture(ref info) => info,
_ => panic!("bug: not a picture"),
}; let src_task_rect = src_task.get_target_rect().to_f32();
let dest_task = &render_tasks[resolve_op.dest_task_id]; let dest_info = match dest_task.kind {
RenderTaskKind::Picture(ref info) => info,
_ => panic!("bug: not a picture"),
}; let dest_task_rect = dest_task.get_target_rect().to_f32();
// If the dest picture is going to a blur target, it may have been // expanded in size so that the downsampling passes don't introduce // sampling error. In this case, we need to ensure we use the // content size rather than the render task size to work out // the intersecting rect to use for the resolve copy. let dest_task_rect = DeviceRect::from_origin_and_size(
dest_task_rect.min,
dest_info.content_size.to_f32(),
);
// Get the rect that we ideally want, in space of the parent surface let wanted_rect = DeviceRect::from_origin_and_size(
dest_info.content_origin,
dest_task_rect.size().to_f32(),
).cast_unit() * dest_info.device_pixel_scale.inverse();
// Get the rect that is available on the parent surface. It may be smaller // than desired because this is a picture cache tile covering only part of // the wanted rect and/or because the parent surface was clipped. let avail_rect = DeviceRect::from_origin_and_size(
src_info.content_origin,
src_task_rect.size().to_f32(),
).cast_unit() * src_info.device_pixel_scale.inverse();
iflet Some(device_int_rect) = wanted_rect.intersection(&avail_rect) { let src_int_rect = (device_int_rect * src_info.device_pixel_scale).cast_unit(); let dest_int_rect = (device_int_rect * dest_info.device_pixel_scale).cast_unit();
// If there is a valid intersection, work out the correct origins and // sizes of the copy rects, and do the blit.
let src_origin = src_task_rect.min.to_f32() +
src_int_rect.min.to_vector() -
src_info.content_origin.to_vector();
let src = DeviceIntRect::from_origin_and_size(
src_origin.to_i32(),
src_int_rect.size().round().to_i32(),
);
let dest_origin = dest_task_rect.min.to_f32() +
dest_int_rect.min.to_vector() -
dest_info.content_origin.to_vector();
let dest = DeviceIntRect::from_origin_and_size(
dest_origin.to_i32(),
dest_int_rect.size().round().to_i32(),
);
let texture_source = TextureSource::TextureCache(
src_task.get_target_texture(),
Swizzle::default(),
); let (cache_texture, _) = self.texture_resolver
.resolve(&texture_source).expect("bug: no source texture");
let read_target = ReadTarget::from_texture(cache_texture);
// Should always be drawing to picture cache tiles or off-screen surface!
debug_assert!(!draw_target.is_default()); let device_to_framebuffer = Scale::new(1i32);
self.profile.inc(profiler::RENDERED_PICTURE_TILES); let _gm = self.gpu_profiler.start_marker("picture cache target"); let framebuffer_kind = FramebufferKind::Other;
{ let _timer = self.gpu_profiler.start_timer(GPU_TAG_SETUP_TARGET); self.device.bind_draw_target(draw_target);
ifself.device.get_capabilities().supports_qcom_tiled_rendering { self.device.gl().start_tiling_qcom(
target.dirty_rect.min.x.max(0) as _,
target.dirty_rect.min.y.max(0) as _,
target.dirty_rect.width() as _,
target.dirty_rect.height() as _, 0,
);
}
let clear_color = target.clear_color.map(|c| c.to_array()); let scissor_rect = ifself.device.get_capabilities().supports_render_target_partial_update
&& (target.dirty_rect != target.valid_rect
|| self.device.get_capabilities().prefers_clear_scissor)
{
Some(target.dirty_rect)
} else {
None
}; match scissor_rect { // If updating only a dirty rect within a picture cache target, the // clear must also be scissored to that dirty region.
Some(r) ifself.clear_caches_with_quads => { self.device.enable_depth(DepthFunction::Always); // Save the draw call count so that our reftests don't get confused... let old_draw_call_count = stats.total_draw_calls; if clear_color.is_none() { self.device.disable_color_write();
} let instance = ClearInstance {
rect: [
r.min.x as f32, r.min.y as f32,
r.max.x as f32, r.max.y as f32,
],
color: clear_color.unwrap_or([0.0; 4]),
}; self.shaders.borrow_mut().ps_clear().bind(
&mutself.device,
&projection,
None,
&mutself.renderer_errors,
&mutself.profile,
&mutself.command_log,
); self.draw_instanced_batch(
&[instance],
VertexArrayKind::Clear,
&BatchTextures::empty(),
stats,
); if clear_color.is_none() { self.device.enable_color_write();
}
stats.total_draw_calls = old_draw_call_count; self.device.disable_depth();
}
other => { let scissor_rect = other.map(|rect| {
draw_target.build_scissor_rect(Some(rect))
}); self.device.clear_target(clear_color, Some(1.0), scissor_rect);
}
}; self.device.disable_depth_write();
}
let p0 = src_task_rect.min + sub_rect_offset; let p1 = p0 + target.dirty_rect.size(); let src_rect = DeviceIntRect::new(p0, p1);
// TODO(gw): In future, it'd be tidier to have the draw target offset // for DC surfaces handled by `blit_render_target`. However, // for now they are only ever written to here. let target_rect = target
.dirty_rect
.translate(draw_target.offset().to_vector())
.cast_unit();
/// Draw an alpha batch container into a given draw target. This is used /// by both color and picture cache target kinds. fn draw_alpha_batch_container(
&mutself,
alpha_batch_container: &AlphaBatchContainer,
draw_target: DrawTarget,
framebuffer_kind: FramebufferKind,
projection: &default::Transform3D<f32>,
render_tasks: &RenderTaskGraph,
stats: &mut RendererStats,
) { let uses_scissor = alpha_batch_container.task_scissor_rect.is_some();
if uses_scissor { self.device.enable_scissor(); let scissor_rect = draw_target.build_scissor_rect(
alpha_batch_container.task_scissor_rect,
); self.device.set_scissor_rect(scissor_rect)
}
if !alpha_batch_container.opaque_batches.is_empty()
&& !self.debug_flags.contains(DebugFlags::DISABLE_OPAQUE_PASS) { let _gl = self.gpu_profiler.start_marker("opaque batches"); let opaque_sampler = self.gpu_profiler.start_sampler(GPU_SAMPLER_TAG_OPAQUE); self.set_blend(false, framebuffer_kind); //Note: depth equality is needed for split planes self.device.enable_depth(DepthFunction::LessEqual); self.device.enable_depth_write();
// Draw opaque batches front-to-back for maximum // z-buffer efficiency! for batch in alpha_batch_container
.opaque_batches
.iter()
.rev()
{ if should_skip_batch(&batch.key.kind, self.debug_flags) { continue;
}
// Handle special case readback for composites. iflet BatchKind::Brush(BrushBatchKind::MixBlend { task_id, backdrop_id }) = batch.key.kind { // composites can't be grouped together because // they may overlap and affect each other.
debug_assert_eq!(batch.instances.len(), 1); self.handle_readback_composite(
draw_target,
uses_scissor,
&render_tasks[task_id],
&render_tasks[backdrop_id],
);
}
let is_alpha = target.target_kind == RenderTargetKind::Alpha; let require_precise_clear = target.cached;
// On some Mali-T devices we have observed crashes in subsequent draw calls // immediately after clearing the alpha render target regions with glClear(). // Using the shader to clear the regions avoids the crash. See bug 1638593. let clear_with_quads = (target.cached && self.clear_caches_with_quads)
|| (is_alpha && self.clear_alpha_targets_with_quads);
let favor_partial_updates = self.device.get_capabilities().supports_render_target_partial_update
&& self.enable_clear_scissor;
// On some Adreno 4xx devices we have seen render tasks to alpha targets have no // effect unless the target is fully cleared prior to rendering. See bug 1714227. let full_clears_on_adreno = is_alpha && self.device.get_capabilities().requires_alpha_target_full_clear; let require_full_clear = !require_precise_clear
&& (full_clears_on_adreno || !favor_partial_updates);
let clear_color = target
.clear_color
.map(|color| color.to_array());
letmut cleared_depth = false; if clear_with_quads { // Will be handled last. Only specific rects will be cleared.
} elseif require_precise_clear { // Only clear specific rects for (rect, color) in &target.clears { self.device.clear_target(
Some(color.to_array()),
None,
Some(draw_target.to_framebuffer_rect(*rect)),
);
}
} else { // At this point we know we don't require precise clears for correctness. // We may still attempt to restruct the clear rect as an optimization on // some configurations. let clear_rect = if require_full_clear {
None
} else { match draw_target {
DrawTarget::Default { rect, total_size, .. } => { if rect.min == FramebufferIntPoint::zero() && rect.size() == total_size { // Whole screen is covered, no need for scissor
None
} else {
Some(rect)
}
}
DrawTarget::Texture { .. } => { // TODO(gw): Applying a scissor rect and minimal clear here // is a very large performance win on the Intel and nVidia // GPUs that I have tested with. It's possible it may be a // performance penalty on other GPU types - we should test this // and consider different code paths. // // Note: The above measurements were taken when render // target slices were minimum 2048x2048. Now that we size // them adaptively, this may be less of a win (except perhaps // on a mostly-unused last slice of a large texture array).
target.used_rect.map(|rect| draw_target.to_framebuffer_rect(rect))
} // Full clear.
_ => None,
}
};
// Make sure to clear the depth buffer if it is used. if needs_depth && !cleared_depth { // TODO: We could also clear the depth buffer via ps_clear. This // is done by picture cache targets in some cases. self.device.clear_target(None, clear_depth, None);
}
// Finally, if we decided to clear with quads or if we need to clear // some areas with specific colors that don't match the global clear // color, clear more areas using a draw call.
letmut clear_instances = Vec::with_capacity(target.clears.len()); for (rect, color) in &target.clears { if clear_with_quads || (!require_precise_clear && target.clear_color != Some(*color)) { let rect = rect.to_f32();
clear_instances.push(ClearInstance {
rect: [
rect.min.x, rect.min.y,
rect.max.x, rect.max.y,
],
color: color.to_array(),
})
}
}
let draw_target = DrawTarget::from_texture(
texture,
needs_depth,
);
let projection = Transform3D::ortho( 0.0,
draw_target.dimensions().width as f32, 0.0,
draw_target.dimensions().height as f32,
self.device.ortho_near_plane(),
self.device.ortho_far_plane(),
);
profile_scope!("draw_render_target");
let _gm = self.gpu_profiler.start_marker("render target");
let counter = match target.target_kind {
RenderTargetKind::Color => profiler::COLOR_PASSES,
RenderTargetKind::Alpha => profiler::ALPHA_PASSES,
};
self.profile.inc(counter);
let sampler_query = match target.target_kind {
RenderTargetKind::Color => None,
RenderTargetKind::Alpha => Some(self.gpu_profiler.start_sampler(GPU_SAMPLER_TAG_ALPHA)),
};
// sanity check for the depth buffer if let DrawTarget::Texture { with_depth, .. } = draw_target {
assert!(with_depth >= target.needs_depth());
}
let framebuffer_kind = if draw_target.is_default() {
FramebufferKind::Main
} else {
FramebufferKind::Other
};
self.device.bind_draw_target(draw_target);
if self.device.get_capabilities().supports_qcom_tiled_rendering {
let preserve_mask = match target.clear_color {
Some(_) => 0,
None => gl::COLOR_BUFFER_BIT0_QCOM,
}; if let Some(used_rect) = target.used_rect {
self.device.gl().start_tiling_qcom(
used_rect.min.x.max(0) as _,
used_rect.min.y.max(0) as _,
used_rect.width() as _,
used_rect.height() as _,
preserve_mask,
);
}
}
if needs_depth {
self.device.enable_depth_write();
} else {
self.device.disable_depth_write();
}
if needs_depth {
self.device.disable_depth_write();
}
// Handle any resolves from parent pictures to this target
self.handle_resolves(
&target.resolve_ops,
render_tasks,
draw_target,
);
// Handle any blits from the texture cache to this target.
self.handle_blits(
&target.blits,
render_tasks,
draw_target,
);
// Draw any borders for this target. if !target.border_segments_solid.is_empty() ||
!target.border_segments_complex.is_empty()
{
let _timer = self.gpu_profiler.start_timer(GPU_TAG_CACHE_BORDER);
// Draw any line decorations for this target. if !target.line_decorations.is_empty() {
let _timer = self.gpu_profiler.start_timer(GPU_TAG_CACHE_LINE_DECORATION);
// Draw any blurs for this target. // Blurs are rendered as a standard 2-pass // separable implementation. // TODO(gw): In the future, consider having // fast path blur shaders for common // blur radii with fixed weights. if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
let _timer = self.gpu_profiler.start_timer(GPU_TAG_BLUR);
// Draw the clip items into the tiled alpha mask.
let has_primary_clips = !target.clip_batcher.primary_clips.is_empty();
let has_secondary_clips = !target.clip_batcher.secondary_clips.is_empty();
let has_clip_masks = !target.clip_masks.is_empty(); if has_primary_clips | has_secondary_clips | has_clip_masks {
let _timer = self.gpu_profiler.start_timer(GPU_TAG_CACHE_CLIP);
// TODO(gw): Consider grouping multiple clip masks per shader // invocation here to reduce memory bandwith further?
if has_primary_clips { // Draw the primary clip mask - since this is the first mask // for the task, we can disable blending, knowing that it will // overwrite every pixel in the mask area.
self.set_blend(false, FramebufferKind::Other);
self.draw_clip_batch_list(
&target.clip_batcher.primary_clips,
&projection,
stats,
);
}
if has_secondary_clips { // switch to multiplicative blending for secondary masks, using // multiplicative blending to accumulate clips into the mask.
self.set_blend(true, FramebufferKind::Other);
self.set_blend_mode_multiply(FramebufferKind::Other);
self.draw_clip_batch_list(
&target.clip_batcher.secondary_clips,
&projection,
stats,
);
}
if needs_depth {
self.device.invalidate_depth_target();
} if self.device.get_capabilities().supports_qcom_tiled_rendering {
self.device.gl().end_tiling_qcom(gl::COLOR_BUFFER_BIT0_QCOM);
}
if let Some(sampler) = sampler_query {
self.gpu_profiler.finish_sampler(sampler);
}
}
fn draw_blurs(
&mut self,
blurs: &FastHashMap<TextureSource, FrameVec<BlurInstance>>,
stats: &mut RendererStats,
) { for (texture, blurs) in blurs {
let textures = BatchTextures::composite_rgb(
*texture,
);
/// Draw all the instances in a clip batcher list to the current target.
fn draw_clip_batch_list(
&mut self,
list: &ClipBatchList,
projection: &default::Transform3D<f32>,
stats: &mut RendererStats,
) { if self.debug_flags.contains(DebugFlags::DISABLE_CLIP_MASKS) { return;
}
// Delete the texture if it has been too large for 10 frames // or more. if *texture_too_large > 10 {
device.delete_texture(texture.take().unwrap());
*texture_too_large = 0;
}
}
// These markers seem to crash a lot on Android, see bug 1559834 #[cfg(not(target_os = "android"))]
let _gm = self.gpu_profiler.start_marker("draw frame");
if frame.passes.is_empty() {
frame.has_been_rendered = true; return;
}
{
let _gm = self.gpu_profiler.start_marker("gpu buffer update");
let bytes_to_mb = 1.0 / 1000000.0;
let gpu_buffer_bytes_f = frame.gpu_buffer_f.size.to_f32().area() * 16.0;
let gpu_buffer_bytes_i = frame.gpu_buffer_i.size.to_f32().area() * 16.0;
let gpu_buffer_mb = (gpu_buffer_bytes_f + gpu_buffer_bytes_i) as f32 * bytes_to_mb;
self.profile.set(profiler::GPU_BUFFER_MEM, gpu_buffer_mb);
// Determine the present mode and dirty rects, if device_size // is Some(..). If it's None, no composite will occur and only // picture cache and texture cache targets will be updated. // TODO(gw): Split Frame so that it's clearer when a composite // is occurring.
let present_mode = device_size.and_then(|device_size| {
self.calculate_dirty_rects(
buffer_age,
&frame.composite_state,
device_size,
results,
)
});
// If we have a native OS compositor, then make use of that interface to // specify how to composite each of the picture cache surfaces. First, we // need to find each tile that may be bound and updated later in the frame // and invalidate it so that the native render compositor knows that these // tiles can't be composited early. Next, after all such tiles have been // invalidated, then we queue surfaces for native composition by the render // compositor before we actually update the tiles. This allows the render // compositor to start early composition while the tiles are updating. if let CompositorKind::Native { .. } = self.current_compositor_kind {
let compositor = self.compositor_config.compositor().unwrap(); // Invalidate any native surface tiles that might be updated by passes. if !frame.has_been_rendered { for tile in &frame.composite_state.tiles { if !tile.local_dirty_rect.is_empty() { if let CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::Native { id, .. } } = tile.surface {
let valid_rect = frame.composite_state.get_surface_rect(
&tile.local_valid_rect,
&tile.local_rect,
tile.transform_index,
).to_i32();
compositor.invalidate_tile(&mut self.device, id, valid_rect);
}
}
}
} // Ensure any external surfaces that might be used during early composition // are invalidated first so that the native compositor can properly schedule // composition to happen only when the external surface is updated. // See update_external_native_surfaces for more details. for surface in &frame.composite_state.external_surfaces { if let Some((native_surface_id, size)) = surface.update_params {
let surface_rect = size.into();
compositor.invalidate_tile(&mut self.device, NativeTileId { surface_id: native_surface_id, x: 0, y: 0 }, surface_rect);
}
} // Finally queue native surfaces for early composition, if applicable. By now, // we have already invalidated any tiles that such surfaces may depend upon, so // the native render compositor can keep track of when to actually schedule // composition as surfaces are updated. if device_size.is_some() {
frame.composite_state.composite_native(
self.clear_color,
&results.dirty_rects,
&mut self.device,
&mut **compositor,
);
}
}
for (_pass_index, pass) in frame.passes.iter_mut().enumerate() { #[cfg(not(target_os = "android"))]
let _gm = self.gpu_profiler.start_marker(&format!("pass {}", _pass_index));
profile_scope!("offscreen target");
// If this frame has already been drawn, then any texture // cache targets have already been updated and can be // skipped this time. if !frame.has_been_rendered { for (&texture_id, target) in &pass.texture_cache {
self.draw_render_target(
texture_id,
target,
&frame.render_tasks,
&mut results.stats,
);
}
if !pass.picture_cache.is_empty() {
self.profile.inc(profiler::COLOR_PASSES);
}
// Draw picture caching tiles for this pass. for picture_target in &pass.picture_cache {
results.stats.color_target_count += 1;
let draw_target = match picture_target.surface {
ResolvedSurfaceTexture::TextureCache { ref texture } => {
let (texture, _) = self.texture_resolver
.resolve(texture)
.expect("bug");
let projection = Transform3D::ortho( 0.0,
draw_target.dimensions().width as f32, 0.0,
draw_target.dimensions().height as f32,
self.device.ortho_near_plane(),
self.device.ortho_far_plane(),
);
// Native OS surfaces must be unbound at the end of drawing to them if let ResolvedSurfaceTexture::Native { .. } = picture_target.surface {
match self.current_compositor_kind {
CompositorKind::Native { .. } => {
let compositor = self.compositor_config.compositor().unwrap();
compositor.unbind(&mut self.device);
}
CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
unreachable!();
}
}
}
}
}
for target in &pass.alpha.targets {
results.stats.alpha_target_count += 1;
self.draw_render_target(
target.texture_id(),
target,
&frame.render_tasks,
&mut results.stats,
);
}
for target in &pass.color.targets {
results.stats.color_target_count += 1;
self.draw_render_target(
target.texture_id(),
target,
&frame.render_tasks,
&mut results.stats,
);
}
// Only end the pass here and invalidate previous textures for // off-screen targets. Deferring return of the inputs to the // frame buffer until the implicit end_pass in end_frame allows // debug draw overlays to be added without triggering a copy // resolve stage in mobile / tiled GPUs.
self.texture_resolver.end_pass(
&mut self.device,
&pass.textures_to_invalidate,
);
}
let debug_renderer = match self.debug.get_mut(&mut self.device) {
Some(render) => render,
None => return,
};
for item in items {
match item {
DebugItem::Rect { rect, outer_color, inner_color, thickness } => { if inner_color.a > 0.001 {
let rect = rect.inflate(-thickness as f32, -thickness as f32);
debug_renderer.add_quad(
rect.min.x,
rect.min.y,
rect.max.x,
rect.max.y,
(*inner_color).into(),
(*inner_color).into(),
);
}
if self.zoom_debug_texture.is_none() {
let texture = self.device.create_texture(
ImageBufferKind::Texture2D,
ImageFormat::BGRA8,
source_rect.width(),
source_rect.height(),
TextureFilter::Nearest,
Some(RenderTargetInfo { has_depth: false }),
);
self.zoom_debug_texture = Some(texture);
}
// Copy frame buffer into the zoom texture
let read_target = DrawTarget::new_default(device_size, self.device.surface_origin_is_top_left());
self.device.blit_render_target(
read_target.into(),
read_target.to_framebuffer_rect(source_rect),
DrawTarget::from_texture(
self.zoom_debug_texture.as_ref().unwrap(), false,
),
texture_rect,
TextureFilter::Nearest,
);
// Draw the zoom texture back to the framebuffer
self.device.blit_render_target(
ReadTarget::from_texture(
self.zoom_debug_texture.as_ref().unwrap(),
),
texture_rect,
read_target,
read_target.to_framebuffer_rect(target_rect),
TextureFilter::Nearest,
);
}
let device_size = draw_target.dimensions();
let fb_width = device_size.width;
let fb_height = device_size.height;
let surface_origin_is_top_left = draw_target.surface_origin_is_top_left();
let num_textures = textures.len() as i32;
if num_textures * (size + spacing) > fb_width {
let factor = fb_width as f32 / (num_textures * (size + spacing)) as f32;
size = (size as f32 * factor) as i32;
spacing = (spacing as f32 * factor) as i32;
}
let text_height = 14; // Visually approximated.
let text_margin = 1;
let tag_height = text_height + text_margin * 2;
let tag_y = fb_height - (bottom + spacing + tag_height);
let image_y = tag_y - size;
// Sort the display by size (in bytes), so that left-to-right is // largest-to-smallest. // // Note that the vec here is in increasing order, because the elements // get drawn right-to-left.
textures.sort_by_key(|t| t.size_in_bytes());
let mut i = 0; for texture in textures.iter() {
let dimensions = texture.get_dimensions();
let src_rect = FramebufferIntRect::from_size(
FramebufferIntSize::new(dimensions.width as i32, dimensions.height as i32),
);
let x = fb_width - (spacing + size) * (i as i32 + 1);
// If we have more targets than fit on one row in screen, just early exit. if x > fb_width { return;
}
// Draw the info tag.
let tag_rect = rect(x, tag_y, size, tag_height).to_box2d();
let tag_color = select_color(texture);
device.clear_target(
Some(tag_color),
None,
Some(draw_target.to_framebuffer_rect(tag_rect)),
);
// Draw the dimensions onto the tag.
let dim = texture.get_dimensions();
let text_rect = tag_rect.inflate(-text_margin, -text_margin);
debug_renderer.add_text(
text_rect.min.x as f32,
text_rect.max.y as f32, // Top-relative.
&format!("{}x{}", dim.width, dim.height),
ColorU::new(0, 0, 0, 255),
Some(tag_rect.to_f32())
);
// Blit the contents of the texture.
let dest_rect = draw_target.to_framebuffer_rect(rect(x, image_y, size, size).to_box2d());
let read_target = ReadTarget::from_texture(texture);
if surface_origin_is_top_left {
device.blit_render_target(
read_target,
src_rect,
*draw_target,
dest_rect,
TextureFilter::Linear,
);
} else { // Invert y.
device.blit_render_target_invert_y(
read_target,
src_rect,
*draw_target,
dest_rect,
);
}
i += 1;
}
}
fn draw_epoch_debug(&mut self) { if !self.debug_flags.contains(DebugFlags::EPOCHS) { return;
}
let debug_renderer = match self.debug.get_mut(&mut self.device) {
Some(render) => render,
None => return,
};
let dy = debug_renderer.line_height();
let x0: f32 = 30.0;
let y0: f32 = 30.0;
let mut y = y0;
let mut text_width = 0.0; for ((pipeline, document_id), epoch) in &self.pipeline_info.epochs {
y += dy;
let w = debug_renderer.add_text(
x0, y,
&format!("({:?}, {:?}): {:?}", pipeline, document_id, epoch),
ColorU::new(255, 255, 0, 255),
None,
).size.width;
text_width = f32::max(text_width, w);
}
fn draw_window_visibility_debug(&mut self) { if !self.debug_flags.contains(DebugFlags::WINDOW_VISIBILITY_DBG) { return;
}
let debug_renderer = match self.debug.get_mut(&mut self.device) {
Some(render) => render,
None => return,
};
let x: f32 = 30.0;
let y: f32 = 40.0;
if let CompositorConfig::Native { ref mut compositor, .. } = self.compositor_config {
let visibility = compositor.get_window_visibility(&mut self.device);
let color = if visibility.is_fully_occluded {
ColorU::new(255, 0, 0, 255)
} else {
ColorU::new(0, 0, 255, 255)
};
debug_renderer.add_text(
x, y,
&format!("{:?}", visibility),
color,
None,
);
}
}
fn draw_external_composite_borders_debug(&mut self) { if !self.debug_flags.contains(DebugFlags::EXTERNAL_COMPOSITE_BORDERS) { return;
}
let debug_renderer = match self.debug.get_mut(&mut self.device) {
Some(render) => render,
None => return,
};
for item in &self.external_composite_debug_items {
match item {
DebugItem::Rect { rect, outer_color, inner_color: _, thickness } => { if outer_color.a > 0.001 {
debug_renderer.add_rect(
&rect.to_i32(),
*thickness,
(*outer_color).into(),
);
}
}
DebugItem::Text { .. } => {}
}
}
}
/// Pass-through to `Device::read_pixels_into`, used by Gecko's WR bindings.
pub fn read_pixels_into(&mut self, rect: FramebufferIntRect, format: ImageFormat, output: &mut [u8]) {
self.device.read_pixels_into(rect, format, output);
}
// De-initialize the Renderer safely, assuming the GL is still alive and active.
pub fn deinit(mut self) { //Note: this is a fake frame, only needed because texture deletion is require to happen inside a frame
self.device.begin_frame(); // If we are using a native compositor, ensure that any remaining native // surfaces are freed. if let CompositorConfig::Native { mut compositor, .. } = self.compositor_config { for id in self.allocated_native_surfaces.drain() {
compositor.destroy_surface(&mut self.device, id);
} // Destroy the debug overlay surface, if currently allocated. if self.debug_overlay_state.current_size.is_some() {
compositor.destroy_surface(&mut self.device, NativeSurfaceId::DEBUG_OVERLAY);
}
compositor.deinit(&mut self.device);
} if let Some(dither_matrix_texture) = self.dither_matrix_texture {
self.device.delete_texture(dither_matrix_texture);
} if let Some(zoom_debug_texture) = self.zoom_debug_texture {
self.device.delete_texture(zoom_debug_texture);
} if let Some(texture) = self.gpu_buffer_texture_f {
self.device.delete_texture(texture);
} if let Some(texture) = self.gpu_buffer_texture_i {
self.device.delete_texture(texture);
} for textures in self.vertex_data_textures.drain(..) {
textures.deinit(&mut self.device);
}
self.texture_upload_pbo_pool.deinit(&mut self.device);
self.staging_texture_pool.delete_textures(&mut self.device);
self.texture_resolver.deinit(&mut self.device);
self.vaos.deinit(&mut self.device);
self.debug.deinit(&mut self.device);
if let Ok(shaders) = Rc::try_unwrap(self.shaders) {
shaders.into_inner().deinit(&mut self.device);
}
if let Some(async_screenshots) = self.async_screenshots.take() {
async_screenshots.deinit(&mut self.device);
}
if let Some(async_frame_recorder) = self.async_frame_recorder.take() {
async_frame_recorder.deinit(&mut self.device);
}
#[cfg(feature = "capture")]
self.device.delete_fbo(self.read_fbo); #[cfg(feature = "replay")] for (_, ext) in self.owned_external_images {
self.device.delete_external_texture(ext);
}
self.device.end_frame();
}
/// Collects a memory report.
pub fn report_memory(&self, swgl: *mut c_void) -> MemoryReport {
let mut report = MemoryReport::default();
/// Clears the texture with a given color.
fn clear_texture(&mut self, texture: &Texture, color: [f32; 4]) {
self.device.bind_draw_target(DrawTarget::from_texture(
&texture, false,
));
self.device.clear_target(Some(color), None, None);
}
}
bitflags! { /// Flags that control how shaders are pre-cached, if at all. #[derive(Default, Debug, Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
pub struct ShaderPrecacheFlags: u32 { /// Needed for const initialization const EMPTY = 0;
/// Some basic statistics about the rendered scene, used in Gecko, as /// well as in wrench reftests to ensure that tests are batching and/or /// allocating on render targets as we expect them to. #[repr(C)] #[derive(Debug, Default)]
pub struct RendererStats {
pub total_draw_calls: usize,
pub alpha_target_count: usize,
pub color_target_count: usize,
pub texture_upload_mb: f64,
pub resource_upload_time: f64,
pub gecko_display_list_time: f64,
pub wr_display_list_time: f64,
pub scene_build_time: f64,
pub frame_build_time: f64,
pub full_display_list: bool,
pub full_paint: bool,
}
/// Return type from render(), which contains some repr(C) statistics as well as /// some non-repr(C) data. #[derive(Debug, Default)]
pub struct RenderResults { /// Statistics about the frame that was rendered.
pub stats: RendererStats,
/// A list of the device dirty rects that were updated /// this frame. /// TODO(gw): This is an initial interface, likely to change in future. /// TODO(gw): The dirty rects here are currently only useful when scrolling /// is not occurring. They are still correct in the case of /// scrolling, but will be very large (until we expose proper /// OS compositor support where the dirty rects apply to a /// specific picture cache slice / OS compositor surface).
pub dirty_rects: Vec<DeviceIntRect>,
/// Information about the state of picture cache tiles. This is only /// allocated and stored if config.testing is true (such as wrench)
pub picture_cache_debug: PictureCacheDebugInfo,
/// Whether any tile was rasterized (had is_valid = false)
pub did_rasterize_any_tile: bool,
}
let short_path = format!("textures/{}.raw", name);
let bytes_per_pixel = texture.get_format().bytes_per_pixel();
let read_format = texture.get_format();
let rect_size = texture.get_dimensions();
let mut file = fs::File::create(root.join(&short_path))
.expect(&format!("Unable to create {}", short_path));
let bytes_per_texture = (rect_size.width * rect_size.height * bytes_per_pixel) as usize;
let mut data = vec![0; bytes_per_texture];
//TODO: instead of reading from an FBO with `read_pixels*`, we could // read from textures directly with `get_tex_image*`.
let rect = device_size_as_framebuffer_size(rect_size).into();
device.attach_read_texture(texture); #[cfg(feature = "png")]
{
let mut png_data;
let (data_ref, format) = match texture.get_format() {
ImageFormat::RGBAF32 => {
png_data = vec![0; (rect_size.width * rect_size.height * 4) as usize];
device.read_pixels_into(rect, ImageFormat::RGBA8, &mut png_data);
(&png_data, ImageFormat::RGBA8)
}
fm => (&data, fm),
};
CaptureConfig::save_png(
root.join(format!("textures/{}-{}.png", name, 0)),
rect_size, format,
None,
data_ref,
);
}
device.read_pixels_into(rect, read_format, &mut data);
file.write_all(&data)
.unwrap();
let mut texels = Vec::new();
File::open(root.join(&plain.data))
.expect(&format!("Unable to open texture at {}", plain.data))
.read_to_end(&mut texels)
.unwrap();
#[cfg(feature = "capture")]
fn save_capture(
&mut self,
config: CaptureConfig,
deferred_images: Vec<ExternalCaptureImage>,
) {
use std::fs;
use std::io::Write;
use api::ExternalImageData;
use crate::render_api::CaptureBits;
let root = config.resource_root();
self.device.begin_frame();
let _gm = self.gpu_profiler.start_marker("read GPU data");
self.device.bind_read_target_impl(self.read_fbo, DeviceIntPoint::zero());
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();
let handler = self.external_image_handler
.as_mut()
.expect("Unable to lock the external image handler!"); for def in &deferred_images {
info!("\t{}", def.short_path);
let ExternalImageData { id, channel_index, image_type, .. } = def.external; // The image rendering parameter is irrelevant because no filtering happens during capturing.
let ext_image = handler.lock(id, channel_index, false);
let (data, short_path) = match ext_image.source {
ExternalImageSource::RawData(data) => {
let arc_id = arc_map.len() + 1;
match arc_map.entry(data.as_ptr()) {
Entry::Occupied(e) => {
(None, e.get().clone())
}
Entry::Vacant(e) => {
let short_path = format!("externals/d{}.raw", arc_id);
(Some(data.to_vec()), e.insert(short_path).clone())
}
}
}
ExternalImageSource::NativeTexture(gl_id) => {
let tex_id = tex_map.len() + 1;
match tex_map.entry(gl_id) {
Entry::Occupied(e) => {
(None, e.get().clone())
}
Entry::Vacant(e) => {
let target = match image_type {
ExternalImageType::TextureHandle(target) => target,
ExternalImageType::Buffer => unreachable!(),
};
info!("\t\tnative texture of target {:?}", target);
self.device.attach_read_texture_external(gl_id, target);
let data = self.device.read_pixels(&def.descriptor);
let short_path = format!("externals/t{}.raw", tex_id);
(Some(data), e.insert(short_path).clone())
}
}
}
ExternalImageSource::Invalid => {
info!("\t\tinvalid source!");
(None, String::new())
}
}; if let Some(bytes) = data {
fs::File::create(root.join(&short_path))
.expect(&format!("Unable to create {}", short_path))
.write_all(&bytes)
.unwrap(); #[cfg(feature = "png")]
CaptureConfig::save_png(
root.join(&short_path).with_extension("png"),
def.descriptor.size,
def.descriptor.format,
def.descriptor.stride,
&bytes,
);
}
let plain = PlainExternalImage {
data: short_path,
external: def.external,
uv: ext_image.uv,
};
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 = root.join("textures"); if !path_textures.is_dir() {
fs::create_dir(&path_textures).unwrap();
}
let mut stats_file = fs::File::create(config.root.join("profiler-stats.txt"))
.expect(&format!("Unable to create profiler-stats.txt")); if self.debug_flags.intersects(DebugFlags::PROFILER_DBG | DebugFlags::PROFILER_CAPTURE) {
self.profiler.dump_stats(&mut stats_file).unwrap();
} else {
writeln!(stats_file, "Turn on PROFILER_DBG or PROFILER_CAPTURE to get stats here!").unwrap();
}
info!("loading external buffer-backed images");
assert!(self.texture_resolver.external_images.is_empty());
let mut raw_map = FastHashMap::<String, Arc<Vec<u8>>>::default();
let mut image_handler = DummyExternalImageHandler {
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 // if we are in the `FRAME` level. for plain_ext in plain_externals {
let data = match raw_map.entry(plain_ext.data) {
Entry::Occupied(e) => e.get().clone(),
Entry::Vacant(e) => {
let mut buffer = Vec::new();
File::open(root.join(e.key()))
.expect(&format!("Unable to open {}", e.key()))
.read_to_end(&mut buffer)
.unwrap();
e.insert(Arc::new(buffer)).clone()
}
};
let ext = plain_ext.external;
let value = (CapturedExternalImageData::Buffer(data), plain_ext.uv);
image_handler.data.insert((ext.id, ext.channel_index), value);
}
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) => {
let plain_tex = PlainTexture {
data: e.key().clone(),
size: descriptor.size,
format: descriptor.format,
filter: TextureFilter::Linear,
has_depth: false,
category: None,
};
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.begin_frame();
if let Some(renderer) = config.deserialize_for_resource::<PlainRenderer, _>("renderer") {
info!("loading cached textures");
self.device_size = renderer.device_size;
for (_id, item) in self.texture_resolver.texture_cache_map.drain() {
self.device.delete_texture(item.texture);
} for (id, texture) in renderer.textures {
info!("\t{}", texture.data);
let target = ImageBufferKind::Texture2D;
let t = Self::load_texture(
target,
&texture,
Some(RenderTargetInfo { has_depth: texture.has_depth }),
&root,
&mut self.device
);
self.texture_resolver.texture_cache_map.insert(id, CacheTexture {
texture: t.0,
category: texture.category.unwrap_or(TextureCacheCategory::Standalone),
});
}
} else {
info!("loading cached textures");
self.device.begin_frame(); for (_id, item) in self.texture_resolver.texture_cache_map.drain() {
self.device.delete_texture(item.texture);
}
}
self.device.end_frame();
self.external_image_handler = Some(Box::new(image_handler) as Box<_>);
info!("done.");
}
}
impl CompositeState { /// Use the client provided native compositor interface to add all picture /// cache tiles to the OS compositor
fn composite_native(
&self,
clear_color: ColorF,
dirty_rects: &[DeviceIntRect],
device: &mut Device,
compositor: &mut dyn Compositor,
) { // Add each surface to the visual tree. z-order is implicit based on // order added. Offset and clip rect apply to all tiles within this // surface. for surface in &self.descriptor.surfaces {
compositor.add_surface(
device,
surface.surface_id.expect("bug: no native surface allocated"),
surface.transform,
surface.clip_rect.to_i32(),
surface.image_rendering,
surface.rounded_clip_rect.to_i32(),
surface.rounded_clip_radii,
);
}
compositor.start_compositing(device, clear_color, dirty_rects, &[]);
}
}
mod tests { #[test]
fn test_buffer_damage_tracker() {
use super::BufferDamageTracker;
use api::units::{DevicePoint, DeviceRect, DeviceSize};
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.