Quelle mod.rs
Sprache: unbekannt
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* 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};
use api::{DocumentId, Epoch, ExternalImageHandler, RenderReasons};
#[cfg(feature = "replay")]
use api::ExternalImageId;
use api::{ExternalImageSource, ExternalImageType, ImageFormat, PremultipliedColorF};
use api::{PipelineId, ImageRendering, Checkpoint, NotificationRequest, ImageBufferKin d};
#[cfg(feature = "replay")]
use api::ExternalImage;
use api::FramePublishId;
use api::units::*;
use api::channel::{Sender, Receiver};
pub use api::DebugFlags;
use core::time::Duration;
use crate::pattern::PatternKind;
use crate::render_api::{DebugCommand, ApiMsg, MemoryReport};
use crate::batch::{AlphaBatchContainer, BatchKind, BatchFeatures, BatchTextures, BrushBatchKind, ClipBatchList};
use crate::batch::ClipMaskInstanceList;
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
use crate::composite::{CompositeState, CompositeTileSurface, CompositorInputLayer, CompositorSurfaceTransform, ResolvedExternalSurface};
use crate::composite::{CompositorKind, Compositor, NativeTileId, CompositeFeatures, CompositeSurfaceFormat, ResolvedExternalSurfaceColorData};
use crate::composite::{CompositorConfig, NativeSurfaceOperationDetails, NativeSurfaceId, NativeSurfaceOperation};
use crate::composite::TileKind;
use crate::{debug_colors, CompositorInputConfig, CompositorSurfaceUsage};
use crate::device::{DepthFunction, Device, DrawTarget, ExternalTexture, GpuFrameId, UploadPBOPool};
use crate::device::{ReadTarget, ShaderError, Texture, TextureFilter, TextureFlags, TextureSlot, Texel};
use crate::device::query::{GpuSampler, GpuTimer};
#[cfg(feature = "capture")]
use crate::device::FBOId;
use crate::debug_item::DebugItem;
use crate::frame_builder::Frame;
use glyph_rasterizer::GlyphFormat;
use crate::gpu_cache::{GpuCacheUpdate, GpuCacheUpdateList};
use crate::gpu_cache::{GpuCacheDebugChunk, GpuCacheDebugCmd};
use crate::gpu_types::{ScalingInstance, SvgFilterInstance, SVGFEFilterInstance, CopyInstance, PrimitiveInstanceData};
use crate::gpu_types::{BlurInstance, ClearInstance, CompositeInstance};
use crate::internal_types::{TextureSource, TextureSourceExternal, TextureCacheCategory, FrameId, FrameVec};
#[cfg(any(feature = "capture", feature = "replay"))]
use crate::internal_types::DebugOutput;
use crate::internal_types::{CacheTextureId, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
use crate::internal_types::{TextureCacheAllocInfo, TextureCacheAllocationKind, TextureUpdateList};
use crate::internal_types::{RenderTargetInfo, Swizzle, DeferredResolveIndex};
use crate::picture::ResolvedSurfaceTexture;
use crate::prim_store::DeferredResolve;
use crate::profiler::{self, GpuProfileTag, TransactionProfile};
use crate::profiler::{Profiler, add_event_marker, add_text_marker, thread_is_being_profiled};
use crate::device::query::GpuProfiler;
use crate::render_target::ResolveOp;
use crate::render_task_graph::RenderTaskGraph;
use crate::render_task::{RenderTask, RenderTaskKind, ReadbackTask};
use crate::screen_capture::AsyncScreenshotGrabber;
use crate::render_target::{RenderTarget, PictureCacheTarget, PictureCacheTargetKind};
use crate::render_target::{RenderTargetKind, BlitJob};
use crate::telemetry::Telemetry;
use crate::tile_cache::PictureCacheDebugInfo;
use crate::util::drain_filter;
use crate::rectangle_occlusion as occlusion;
use upload::{upload_to_texture_cache, UploadTexturePool};
use init::*;
use euclid::{rect, Transform3D, Scale, default};
use gleam::gl;
use malloc_size_of::MallocSizeOfOps;
#[cfg(feature = "replay")]
use std::sync::Arc;
use std::{
cell::RefCell,
collections::VecDeque,
f32,
ffi::c_void,
mem,
num::NonZeroUsize,
path::PathBuf,
rc::Rc,
};
#[cfg(any(feature = "capture", feature = "replay"))]
use std::collections::hash_map::Entry;
use time::precise_time_ns;
mod debug;
mod gpu_buffer;
mod gpu_cache;
mod shade;
mod vertex;
mod upload;
pub(crate) mod init;
pub use debug::DebugRenderer;
pub use shade::{Shaders, SharedShaders};
pub use vertex::{desc, VertexArrayKind, MAX_VERTEX_TEXTURE_WIDTH};
pub use gpu_buffer::{GpuBuffer, GpuBufferF, GpuBufferBuilderF, GpuBufferI, GpuBufferBuilderI, GpuBufferAddress, GpuBufferBuilder};
/// 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.
pub const VERTEX_DATA_TEXTURE_COUNT: usize = 3;
/// Number of GPU blocks per UV rectangle provided for an image.
pub const BLOCKS_PER_UV_RECT: usize = 2;
const GPU_TAG_BRUSH_OPACITY: GpuProfileTag = GpuProfileTag {
label: "B_Opacity",
color: debug_colors::DARKMAGENTA,
};
const GPU_TAG_BRUSH_LINEAR_GRADIENT: GpuProfileTag = GpuProfileTag {
label: "B_LinearGradient",
color: debug_colors::POWDERBLUE,
};
const GPU_TAG_BRUSH_YUV_IMAGE: GpuProfileTag = GpuProfileTag {
label: "B_YuvImage",
color: debug_colors::DARKGREEN,
};
const GPU_TAG_BRUSH_MIXBLEND: GpuProfileTag = GpuProfileTag {
label: "B_MixBlend",
color: debug_colors::MAGENTA,
};
const GPU_TAG_BRUSH_BLEND: GpuProfileTag = GpuProfileTag {
label: "B_Blend",
color: debug_colors::ORANGE,
};
const GPU_TAG_BRUSH_IMAGE: GpuProfileTag = GpuProfileTag {
label: "B_Image",
color: debug_colors::SPRINGGREEN,
};
const GPU_TAG_BRUSH_SOLID: GpuProfileTag = GpuProfileTag {
label: "B_Solid",
color: debug_colors::RED,
};
const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag {
label: "C_Clip",
color: debug_colors::PURPLE,
};
const GPU_TAG_CACHE_BORDER: GpuProfileTag = GpuProfileTag {
label: "C_Border",
color: debug_colors::CORNSILK,
};
const GPU_TAG_CACHE_LINE_DECORATION: GpuProfileTag = GpuProfileTag {
label: "C_LineDecoration",
color: debug_colors::YELLOWGREEN,
};
const GPU_TAG_CACHE_FAST_LINEAR_GRADIENT: GpuProfileTag = GpuProfileTag {
label: "C_FastLinearGradient",
color: debug_colors::BROWN,
};
const GPU_TAG_CACHE_LINEAR_GRADIENT: GpuProfileTag = GpuProfileTag {
label: "C_LinearGradient",
color: debug_colors::BROWN,
};
const GPU_TAG_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag {
label: "C_RadialGradient",
color: debug_colors::BROWN,
};
const GPU_TAG_CONIC_GRADIENT: GpuProfileTag = GpuProfileTag {
label: "C_ConicGradient",
color: debug_colors::BROWN,
};
const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag {
label: "target init",
color: debug_colors::SLATEGREY,
};
const GPU_TAG_SETUP_DATA: GpuProfileTag = GpuProfileTag {
label: "data init",
color: debug_colors::LIGHTGREY,
};
const GPU_TAG_PRIM_SPLIT_COMPOSITE: GpuProfileTag = GpuProfileTag {
label: "SplitComposite",
color: debug_colors::DARKBLUE,
};
const GPU_TAG_PRIM_TEXT_RUN: GpuProfileTag = GpuProfileTag {
label: "TextRun",
color: debug_colors::BLUE,
};
const GPU_TAG_PRIMITIVE: GpuProfileTag = GpuProfileTag {
label: "Primitive",
color: debug_colors::RED,
};
const GPU_TAG_INDIRECT_PRIM: GpuProfileTag = GpuProfileTag {
label: "Primitive (indirect)",
color: debug_colors::YELLOWGREEN,
};
const GPU_TAG_INDIRECT_MASK: GpuProfileTag = GpuProfileTag {
label: "Mask (indirect)",
color: debug_colors::IVORY,
};
const GPU_TAG_BLUR: GpuProfileTag = GpuProfileTag {
label: "Blur",
color: debug_colors::VIOLET,
};
const GPU_TAG_BLIT: GpuProfileTag = GpuProfileTag {
label: "Blit",
color: debug_colors::LIME,
};
const GPU_TAG_SCALE: GpuProfileTag = GpuProfileTag {
label: "Scale",
color: debug_colors::GHOSTWHITE,
};
const GPU_SAMPLER_TAG_ALPHA: GpuProfileTag = GpuProfileTag {
label: "Alpha targets",
color: debug_colors::BLACK,
};
const GPU_SAMPLER_TAG_OPAQUE: GpuProfileTag = GpuProfileTag {
label: "Opaque pass",
color: debug_colors::BLACK,
};
const GPU_SAMPLER_TAG_TRANSPARENT: GpuProfileTag = GpuProfileTag {
label: "Transparent pass",
color: debug_colors::BLACK,
};
const GPU_TAG_SVG_FILTER: GpuProfileTag = GpuProfileTag {
label: "SvgFilter",
color: debug_colors::LEMONCHIFFON,
};
const GPU_TAG_SVG_FILTER_NODES: GpuProfileTag = GpuProfileTag {
label: "SvgFilterNodes",
color: debug_colors::LEMONCHIFFON,
};
const GPU_TAG_COMPOSITE: GpuProfileTag = GpuProfileTag {
label: "Composite",
color: debug_colors::TOMATO,
};
#[derive(Debug)]
// Defines the content that we will draw to a given swapchain / layer, calculated
// after occlusion culling.
struct SwapChainLayer {
opaque_items: Vec<occlusion::Item<usize>>,
alpha_items: Vec<occlusion::Item<usize>>,
clear_tiles: Vec<occlusion::Item<usize>>,
}
/// 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.
pub const TEXTURE_CACHE_DBG_CLEAR_COLOR: [f32; 4] = [0.0, 0.0, 0.8, 1.0];
impl BatchKind {
fn sampler_tag(&self) -> GpuProfileTag {
match *self {
BatchKind::SplitComposite => GPU_TAG_PRIM_SPLIT_COMPOSITE,
BatchKind::Brush(kind) => {
match kind {
BrushBatchKind::Solid => GPU_TAG_BRUSH_SOLID,
BrushBatchKind::Image(..) => GPU_TAG_BRUSH_IMAGE,
BrushBatchKind::Blend => GPU_TAG_BRUSH_BLEND,
BrushBatchKind::MixBlend { .. } => GPU_TAG_BRUSH_MIXBLEND,
BrushBatchKind::YuvImage(..) => GPU_TAG_BRUSH_YUV_IMAGE,
BrushBatchKind::LinearGradient => GPU_TAG_BRUSH_LINEAR_GRADIENT,
BrushBatchKind::Opacity => GPU_TAG_BRUSH_OPACITY,
}
}
BatchKind::TextRun(_) => GPU_TAG_PRIM_TEXT_RUN,
BatchKind::Quad(PatternKind::ColorOrTexture) => GPU_TAG_PRIMITIVE,
BatchKind::Quad(PatternKind::RadialGradient) => GPU_TAG_RADIAL_GRADIENT,
BatchKind::Quad(PatternKind::ConicGradient) => GPU_TAG_CONIC_GRADIENT,
BatchKind::Quad(PatternKind::Mask) => GPU_TAG_INDIRECT_MASK,
}
}
}
fn flag_changed(before: DebugFlags, after: DebugFlags, select: DebugFlags) -> Option<bool> {
if before & select != after & select {
Some(after.contains(select))
} else {
None
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub enum ShaderColorMode {
Alpha = 0,
SubpixelDualSource = 1,
BitmapShadow = 2,
ColorBitmap = 3,
Image = 4,
MultiplyDualSource = 5,
}
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,
GpuCache,
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.");
}
}
}
}
impl Into<TextureSlot> for TextureSampler {
fn into(self) -> TextureSlot {
match self {
TextureSampler::Color0 => TextureSlot(0),
TextureSampler::Color1 => TextureSlot(1),
TextureSampler::Color2 => TextureSlot(2),
TextureSampler::GpuCache => TextureSlot(3),
TextureSampler::TransformPalette => TextureSlot(4),
TextureSampler::RenderTasks => TextureSlot(5),
TextureSampler::Dither => TextureSlot(6),
TextureSampler::PrimitiveHeadersF => TextureSlot(7),
TextureSampler::PrimitiveHeadersI => TextureSlot(8),
TextureSampler::ClipMask => TextureSlot(9),
TextureSampler::GpuBufferF => TextureSlot(10),
TextureSampler::GpuBufferI => TextureSlot(11),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum GraphicsApi {
OpenGL,
}
#[derive(Clone, Debug)]
pub struct GraphicsApiInfo {
pub kind: GraphicsApi,
pub renderer: String,
pub version: String,
}
#[derive(Debug)]
pub struct GpuProfile {
pub frame_id: GpuFrameId,
pub paint_time_ns: u64,
}
impl GpuProfile {
fn new(frame_id: GpuFrameId, timers: &[GpuTimer]) -> GpuProfile {
let mut paint_time_ns = 0;
for timer in timers {
paint_time_ns += timer.time_ns;
}
GpuProfile {
frame_id,
paint_time_ns,
}
}
}
#[derive(Debug)]
pub struct CpuProfile {
pub frame_id: GpuFrameId,
pub backend_time_ns: u64,
pub composite_time_ns: u64,
pub draw_calls: usize,
}
impl CpuProfile {
fn new(
frame_id: GpuFrameId,
backend_time_ns: u64,
composite_time_ns: u64,
draw_calls: usize,
) -> CpuProfile {
CpuProfile {
frame_id,
backend_time_ns,
composite_time_ns,
draw_calls,
}
}
}
/// The selected partial present mode for a given frame.
#[derive(Debug, Copy, Clone)]
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,
},
}
struct CacheTexture {
texture: Texture,
category: TextureCacheCategory,
}
/// 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,
}
impl TextureResolver {
fn new(device: &mut Device) -> TextureResolver {
let dummy_cache_texture = device
.create_texture(
ImageBufferKind::Texture2D,
ImageFormat::RGBA8,
1,
1,
TextureFilter::Linear,
None,
);
device.upload_texture_immediate(
&dummy_cache_texture,
&[0xff, 0xff, 0xff, 0xff],
);
TextureResolver {
texture_cache_map: FastHashMap::default(),
external_images: FastHashMap::default(),
dummy_cache_texture,
}
}
fn deinit(self, device: &mut Device) {
device.delete_texture(self.dummy_cache_texture);
for (_id, item) in self.texture_cache_map {
device.delete_texture(item.texture);
}
}
fn begin_frame(&mut self) {
}
fn end_pass(
&mut self,
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),
}
}
fn report_memory(&self) -> MemoryReport {
let mut report = MemoryReport::default();
// We're reporting GPU memory rather than heap-allocations, so we don't
// use size_of_op.
for item in self.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) {
let mut external_image_bytes = 0;
for img in self.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;
}
profile.set(profiler::EXTERNAL_IMAGE_BYTES, profiler::bytes_to_mb(external_image_bytes));
}
fn get_cache_texture_mut(&mut self, id: &CacheTextureId) -> &mut Texture {
&mut self.texture_cache_map
.get_mut(id)
.expect("bug: texture not allocated")
.texture
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub enum BlendMode {
None,
Alpha,
PremultipliedAlpha,
PremultipliedDestOut,
SubpixelDualSource,
Advanced(MixBlendMode),
MultiplyDualSource,
Screen,
Exclusion,
PlusLighter,
}
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.
pub fn 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>,
}
impl DebugOverlayState {
fn new() -> Self {
DebugOverlayState {
is_enabled: false,
current_size: None,
}
}
}
/// 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(&mut self, rect: &DeviceRect) {
self.damage_rects[self.current_offset] = rect.clone();
self.current_offset = match self.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.
pub struct 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_gpu_cache_updates: Vec<GpuCacheUpdateList>,
pending_gpu_cache_clear: bool,
pending_shader_updates: Vec<PathBuf>,
active_documents: FastHashMap<DocumentId, RenderedDocument>,
shaders: Rc<RefCell<Shaders>>,
max_recorded_profiles: usize,
clear_color: ColorF,
enable_clear_scissor: bool,
enable_advanced_blend_barriers: bool,
clear_caches_with_quads: bool,
clear_alpha_targets_with_quads: bool,
debug: debug::LazyInitializedDebugRenderer,
debug_flags: DebugFlags,
profile: TransactionProfile,
frame_counter: u64,
resource_upload_time: f64,
gpu_cache_upload_time: f64,
profiler: Profiler,
last_time: u64,
pub gpu_profiler: GpuProfiler,
vaos: vertex::RendererVAOs,
gpu_cache_texture: gpu_cache::GpuCacheTexture,
vertex_data_textures: Vec<vertex::VertexDataTextures>,
current_vertex_data_textures: usize,
/// When the GPU cache debugger is enabled, we keep track of the live blocks
/// in the GPU cache so that we can use them for the debug display. This
/// member stores those live blocks, indexed by row.
gpu_cache_debug_chunks: Vec<Vec<GpuCacheDebugChunk>>,
gpu_cache_frame_id: FrameId,
gpu_cache_overflow: bool,
pipeline_info: PipelineInfo,
// Manages and resolves source textures IDs to real texture IDs.
texture_resolver: TextureResolver,
texture_upload_pbo_pool: UploadPBOPool,
staging_texture_pool: UploadTexturePool,
dither_matrix_texture: Option<Texture>,
/// 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>,
pub renderer_errors: Vec<RendererError>,
pub(in crate) async_frame_recorder: Option<AsyncScreenshotGrabber>,
pub(in crate) async_screenshots: Option<AsyncScreenshotGrabber>,
/// 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>,
#[cfg(feature = "capture")]
read_fbo: FBOId,
#[cfg(feature = "replay")]
owned_external_images: FastHashMap<(ExternalImageId, u8), ExternalTexture>,
/// 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,
max_primitive_instance_count: usize,
enable_instancing: bool,
/// 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>,
}
#[derive(Debug)]
pub enum RendererError {
Shader(ShaderError),
Thread(std::io::Error),
MaxTextureSize,
SoftwareRasterizer,
OutOfMemory,
}
impl From<ShaderError> for RendererError {
fn from(err: ShaderError) -> Self {
RendererError::Shader(err)
}
}
impl From<std::io::Error> for RendererError {
fn from(err: std::io::Error) -> Self {
RendererError::Thread(err)
}
}
impl Renderer {
pub fn device_size(&self) -> Option<DeviceIntSize> {
self.device_size
}
/// Update the current position of the debug cursor.
pub fn set_cursor_position(
&mut self,
position: DeviceIntPoint,
) {
self.cursor_position = position;
}
pub fn get_max_texture_size(&self) -> i32 {
self.device.max_texture_size()
}
pub fn get_graphics_api_info(&self) -> GraphicsApiInfo {
GraphicsApiInfo {
kind: GraphicsApi::OpenGL,
version: self.device.gl().get_string(gl::VERSION),
renderer: self.device.gl().get_string(gl::RENDERER),
}
}
pub fn preferred_color_format(&self) -> ImageFormat {
self.device.preferred_color_formats().external
}
pub fn required_texture_stride_alignment(&self, format: ImageFormat) -> usize {
self.device.required_pbo_stride().num_bytes(format).get()
}
pub fn set_clear_color(&mut self, color: ColorF) {
self.clear_color = color;
}
pub fn flush_pipeline_info(&mut self) -> PipelineInfo {
mem::replace(&mut self.pipeline_info, PipelineInfo::default())
}
/// Returns the Epoch of the current frame in a pipeline.
pub fn current_epoch(&self, document_id: DocumentId, pipeline_id: PipelineId) -> Option<Epoch> {
self.pipeline_info.epochs.get(&(pipeline_id, document_id)).cloned()
}
fn get_next_result_msg(&mut self) -> Option<ResultMsg> {
if self.pending_result_msg.is_none() {
if let Ok(msg) = self.result_rx.try_recv() {
self.pending_result_msg = Some(msg);
}
}
match (&self.pending_result_msg, &self.target_frame_publish_id) {
(Some(ResultMsg::PublishDocument(frame_publish_id, _, _, _)), Some(target_id)) => {
if frame_publish_id > target_id {
return None;
}
}
_ => {}
}
self.pending_result_msg.take()
}
/// Processes the result queue.
///
/// Should be called before `render()`, as texture cache updates are done here.
pub fn update(&mut self) {
profile_scope!("update");
// Pull any pending results and return the most recent.
while let 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 = if let Some(mut prev_doc) = self.active_documents.remove(&document_id) {
doc.profile.merge(&mut prev_doc.profile);
if prev_doc.frame.must_be_drawn() {
prev_doc.render_reasons |= RenderReasons::TEXTURE_CACHE_FLUSH;
self.render_impl(
document_id,
&mut prev_doc,
None,
0,
).ok();
}
Some(prev_doc.frame.allocator_memory)
} else {
None
};
if let 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::UpdateGpuCache(mut list) => {
if list.clear {
self.pending_gpu_cache_clear = true;
}
if list.clear {
self.gpu_cache_debug_chunks = Vec::new();
}
for cmd in mem::replace(&mut list.debug_commands, Vec::new()) {
match cmd {
GpuCacheDebugCmd::Alloc(chunk) => {
let row = chunk.address.v as usize;
if row >= self.gpu_cache_debug_chunks.len() {
self.gpu_cache_debug_chunks.resize(row + 1, Vec::new());
}
self.gpu_cache_debug_chunks[row].push(chunk);
},
GpuCacheDebugCmd::Free(address) => {
let chunks = &mut self.gpu_cache_debug_chunks[address.v as usize];
let pos = chunks.iter()
.position(|x| x.address == address).unwrap();
chunks.remove(pos);
},
}
}
self.pending_gpu_cache_updates.push(list);
}
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(
&mut self.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();
}
}
}
self.pending_texture_cache_updates |= !resource_updates.texture_updates.updates.is_empty();
self.pending_texture_updates.push(resource_updates.texture_updates);
self.pending_native_surface_updates.extend(resource_updates.native_surface_updates);
self.device.begin_frame();
self.update_texture_cache();
self.update_native_surfaces();
// 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(&mut self.device);
self.staging_texture_pool.delete_textures(&mut self.device);
}
self.device.end_frame();
}
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.
pub fn set_target_frame_publish_id(&mut self, publish_id: FramePublishId) {
self.target_frame_publish_id = Some(publish_id);
}
fn handle_debug_command(&mut self, command: DebugCommand) {
match command {
DebugCommand::SetPictureTileSize(_) |
DebugCommand::SetMaximumSurfaceSize(_) => {
panic!("Should be handled by render backend");
}
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::InvalidateGpuCache => {
self.gpu_cache_texture.invalidate();
}
DebugCommand::SetFlags(flags) => {
self.set_debug_flags(flags);
}
}
}
/// Set a callback for handling external images.
pub fn set_external_image_handler(&mut self, handler: Box<dyn ExternalImageHandler>) {
self.external_image_handler = Some(handler);
}
/// Retrieve (and clear) the current list of recorded frame profiles.
pub fn get_frame_profiles(&mut self) -> (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.
pub fn force_redraw(&mut self) {
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.
pub fn render(
&mut self,
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
let mut doc = self.active_documents
.remove(&doc_id)
.unwrap();
let result = self.render_impl(
doc_id,
&mut doc,
Some(device_size),
buffer_age,
);
self.active_documents.insert(doc_id, doc);
result
}
None => {
self.last_time = precise_time_ns();
Ok(RenderResults::default())
}
};
drain_filter(
&mut self.notifications,
|n| { n.when() == Checkpoint::FrameRendered },
|n| { n.notify(); },
);
let mut oom = false;
if let 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();
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(
&mut self,
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::GPU_CACHE_DBG |
DebugFlags::PICTURE_CACHING_DBG |
DebugFlags::PRIMITIVE_DBG |
DebugFlags::ZOOM_DBG |
DebugFlags::WINDOW_VISIBILITY_DBG
);
// Update the debug overlay surface, if we are running in native compositor mode.
if let 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.
if let Some(current_size) = self.debug_overlay_state.current_size {
if !self.debug_overlay_state.is_enabled || current_size != framebuffer_size {
compositor.destroy_surface(&mut self.device, NativeSurfaceId::DEBUG_OVERLAY);
self.debug_overlay_state.current_size = None;
}
}
// Allocate a new surface, if we need it and there isn't one.
if self.debug_overlay_state.is_enabled && self.debug_overlay_state.current_size.is_none() {
compositor.create_surface(
&mut self.device,
NativeSurfaceId::DEBUG_OVERLAY,
DeviceIntPoint::zero(),
framebuffer_size,
false,
);
compositor.create_tile(
&mut self.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(&mut self, device_size: DeviceIntSize) -> Option<DrawTarget> {
// Debug overlay setup are only required in native compositing mode
if self.debug_overlay_state.is_enabled {
if let CompositorKind::Native { .. } = self.current_compositor_kind {
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(
&mut self.device,
NativeTileId::DEBUG_OVERLAY,
DeviceIntRect::from_size(surface_size),
);
// Bind the native surface
let surface_info = compositor.bind(
&mut self.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)
} else {
// 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(&mut self) {
// Debug overlay setup are only required in native compositing mode
if self.debug_overlay_state.is_enabled {
if let CompositorKind::Native { .. } = self.current_compositor_kind {
let compositor = self.compositor_config.compositor().unwrap();
// Unbind the draw target and add it to the visual tree to be composited
compositor.unbind(&mut self.device);
compositor.add_surface(
&mut self.device,
NativeSurfaceId::DEBUG_OVERLAY,
CompositorSurfaceTransform::identity(),
DeviceIntRect::from_size(
self.debug_overlay_state.current_size.unwrap(),
),
ImageRendering::Auto,
);
}
}
}
// 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(
&mut self,
doc_id: DocumentId,
active_doc: &mut RenderedDocument,
device_size: Option<DeviceIntSize>,
buffer_age: usize,
) -> Result<RenderResults, Vec<RendererError>> {
profile_scope!("render");
let mut results = RenderResults::default();
self.profile.end_time_if_started(profiler::FRAME_SEND_TIME);
self.profile.start_time(profiler::RENDERER_TIME);
self.staging_texture_pool.begin_frame();
let compositor_kind = active_doc.frame.composite_state.compositor_kind;
// CompositorKind is updated
if self.current_compositor_kind != compositor_kind {
let enable = match (self.current_compositor_kind, compositor_kind) {
(CompositorKind::Native { .. }, CompositorKind::Draw { .. }) => {
if self.debug_overlay_state.current_size.is_some() {
self.compositor_config
.compositor()
.unwrap()
.destroy_surface(&mut self.device, NativeSurfaceId::DEBUG_OVERLAY);
self.debug_overlay_state.current_size = None;
}
false
}
(CompositorKind::Draw { .. }, CompositorKind::Native { .. }) => {
true
}
(current_compositor_kind, active_doc_compositor_kind) => {
warn!("Compositor mismatch, assuming this is Wrench running. Current {:?}, active {:?}",
current_compositor_kind, active_doc_compositor_kind);
false
}
};
if let Some(config) = self.compositor_config.compositor() {
config.enable_native_compositor(&mut self.device, enable);
}
self.current_compositor_kind = compositor_kind;
}
// 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();
if let Some(device_size) = device_size {
self.update_gpu_profile(device_size);
}
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);
self.device.disable_scissor();
self.device.disable_depth();
self.set_blend(false, FramebufferKind::Main);
//self.update_shaders();
self.update_texture_cache();
self.update_native_surfaces();
frame_id
};
if let 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.
if let CompositorKind::Native { .. } = self.current_compositor_kind {
let compositor = self.compositor_config.compositor().unwrap();
compositor.begin_frame(&mut self.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);
if self.shared_texture_cache_cleared {
assert!(self.documents_seen.contains(&doc_id),
"Cleared texture cache without sending new document frame.");
}
match self.prepare_gpu_cache(&frame.deferred_resolves) {
Ok(..) => {
assert!(frame.gpu_cache_frame_id <= self.gpu_cache_frame_id,
"Received frame depends on a later GPU cache epoch ({:?}) than one we received last via `UpdateGpuCache` ({:?})",
frame.gpu_cache_frame_id, self.gpu_cache_frame_id);
{
profile_scope!("gl.flush");
self.device.gl().flush(); // early start on gpu cache updates
}
self.draw_frame(
frame,
device_size,
buffer_age,
&mut results,
);
// 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);
if let 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);
}
self.profile.merge(profile);
}
Err(e) => {
self.renderer_errors.push(e);
}
}
self.unlock_external_images(&frame.deferred_resolves);
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 = precise_time_ns();
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_gpu_cache_debug(device_size);
self.draw_zoom_debug(device_size);
self.draw_epoch_debug();
self.draw_window_visibility_debug();
draw_target
})
});
Telemetry::record_renderer_time(Duration::from_micros((t * 1000.00) as u64));
if self.profile.get(profiler::SHADER_BUILD_TIME).is_none() {
Telemetry::record_renderer_time_no_sc(Duration::from_micros((t * 1000.00) as u64));
}
if self.max_recorded_profiles > 0 {
while self.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);
}
let report = self.texture_resolver.report_memory();
self.profile.set(profiler::RENDER_TARGET_MEM, profiler::bytes_to_mb(report.render_target_textures));
self.profile.set(profiler::PICTURE_TILES_MEM, profiler::bytes_to_mb(report.picture_tile_textures));
self.profile.set(profiler::ATLAS_TEXTURES_MEM, profiler::bytes_to_mb(report.atlas_textures));
--> --------------------
--> maximum size reached
--> --------------------
[ Dauer der Verarbeitung: 0.44Diashow
(vorverarbeitet)
]
|
2026-04-04
|