зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1676559 - Pt 8 - Move render target pool from renderer to frame building. r=nical,jnicol
This is an incremental but important step to implementing render tasks as a proper graph. By moving the render target management to the frame building step, we know the texture_id of all sub-passes before the batching is done for any passes that use these as inputs. This means that we can directly reference the texture_id during batch, rather that the old `RenderTaskCache` and `PrevPassAlpha` / `PrevPassColor` enum fields (although removal of all these will be done in the next patch). Another advantage of this is that we have much better knowledge of which targets are required for rendering a given frame, so these can be allocated up front at the start of a frame. This may be a better allocation pattern for some drivers. We also have better knowledge available on when a texture can be invalidated, and the render target pool management is simpler since it is the same as the way other texture cache textures are handled. Differential Revision: https://phabricator.services.mozilla.com/D98547
This commit is contained in:
Родитель
19e9e7b349
Коммит
01cde5084e
|
@ -16,7 +16,7 @@ use crate::gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance}
|
|||
use crate::gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
|
||||
use crate::gpu_types::{ImageBrushData, get_shader_opacity, BoxShadowData};
|
||||
use crate::gpu_types::{ClipMaskInstanceCommon, ClipMaskInstanceImage, ClipMaskInstanceRect, ClipMaskInstanceBoxShadow};
|
||||
use crate::internal_types::{FastHashMap, SavedTargetIndex, Swizzle, TextureSource, Filter, DeferredResolveIndex};
|
||||
use crate::internal_types::{FastHashMap, Swizzle, TextureSource, Filter, DeferredResolveIndex};
|
||||
use crate::picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, ClusterFlags};
|
||||
use crate::prim_store::{DeferredResolve, PrimitiveInstanceKind, ClipData};
|
||||
use crate::prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex};
|
||||
|
@ -1627,9 +1627,12 @@ impl BatchBuilder {
|
|||
let secondary_id = picture.secondary_render_task_id.expect("no secondary!?");
|
||||
let content_source = {
|
||||
let secondary_task = &render_tasks[secondary_id];
|
||||
let saved_index = secondary_task.saved_index.expect("no saved index!?");
|
||||
debug_assert_ne!(saved_index, SavedTargetIndex::PENDING);
|
||||
TextureSource::RenderTaskCache(saved_index, Swizzle::default())
|
||||
let texture_id = secondary_task.get_target_texture();
|
||||
TextureSource::TextureCache(
|
||||
texture_id,
|
||||
ImageBufferKind::Texture2DArray,
|
||||
Swizzle::default(),
|
||||
)
|
||||
};
|
||||
|
||||
// Build BatchTextures for shadow/content
|
||||
|
@ -1966,14 +1969,21 @@ impl BatchBuilder {
|
|||
let uv_rect_address = render_tasks[cache_task_id]
|
||||
.get_texture_address(gpu_cache)
|
||||
.as_int();
|
||||
let textures = match render_tasks[cache_task_id].saved_index {
|
||||
Some(saved_index) => BatchTextures::new(
|
||||
TextureSource::RenderTaskCache(saved_index, Swizzle::default()),
|
||||
let cache_render_task = &render_tasks[cache_task_id];
|
||||
let textures = if cache_render_task.save_target {
|
||||
let texture_id = cache_render_task.get_target_texture();
|
||||
BatchTextures::new(
|
||||
TextureSource::TextureCache(
|
||||
texture_id,
|
||||
ImageBufferKind::Texture2DArray,
|
||||
Swizzle::default(),
|
||||
),
|
||||
TextureSource::PrevPassAlpha,
|
||||
TextureSource::Invalid,
|
||||
clip_mask_texture_id,
|
||||
),
|
||||
None => BatchTextures::render_target_cache(clip_mask_texture_id),
|
||||
)
|
||||
} else {
|
||||
BatchTextures::render_target_cache(clip_mask_texture_id)
|
||||
};
|
||||
let batch_params = BrushBatchParameters::shared(
|
||||
BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
|
||||
|
|
|
@ -486,6 +486,10 @@ impl Texture {
|
|||
self.last_frame_used == frame_id
|
||||
}
|
||||
|
||||
pub fn is_render_target(&self) -> bool {
|
||||
!self.fbos.is_empty()
|
||||
}
|
||||
|
||||
/// Returns true if this texture was used within `threshold` frames of
|
||||
/// the current frame.
|
||||
pub fn used_recently(&self, current_frame_id: GpuFrameId, threshold: usize) -> bool {
|
||||
|
|
|
@ -12,7 +12,7 @@ use crate::debug_render::DebugItem;
|
|||
use crate::gpu_cache::{GpuCache, GpuCacheHandle};
|
||||
use crate::gpu_types::{PrimitiveHeaders, TransformPalette, ZBufferIdGenerator};
|
||||
use crate::gpu_types::TransformData;
|
||||
use crate::internal_types::{FastHashMap, PlaneSplitter, SavedTargetIndex};
|
||||
use crate::internal_types::{CacheTextureId, FastHashMap, PlaneSplitter};
|
||||
use crate::picture::{DirtyRegion, PictureUpdateState, SliceId, TileCacheInstance};
|
||||
use crate::picture::{SurfaceInfo, SurfaceIndex, ROOT_SURFACE_INDEX};
|
||||
use crate::picture::{BackdropKind, SubpixelMode, TileCacheLogger, RasterConfig, PictureCompositeMode};
|
||||
|
@ -549,6 +549,15 @@ impl FrameBuilder {
|
|||
let use_dual_source_blending = scene.config.dual_source_blending_is_enabled &&
|
||||
scene.config.dual_source_blending_is_supported;
|
||||
|
||||
// As an incremental approach to moving to a proper DAG for render tasks, we
|
||||
// implement the existing saved texture functionality here.
|
||||
|
||||
// Textures that are marked for saving until the end of the frame
|
||||
let mut saved_texture_ids = Vec::new();
|
||||
// Textures that are written to on this pass and should be returned / invalidated
|
||||
// on the following pass.
|
||||
let mut active_texture_ids = Vec::new();
|
||||
|
||||
for pass in &mut passes {
|
||||
let mut ctx = RenderTargetContext {
|
||||
global_device_pixel_scale,
|
||||
|
@ -583,6 +592,60 @@ impl FrameBuilder {
|
|||
|
||||
has_texture_cache_tasks |= !pass.texture_cache.is_empty();
|
||||
has_texture_cache_tasks |= !pass.picture_cache.is_empty();
|
||||
|
||||
// Check which textures in this pass need to be saved until the end of
|
||||
// the frame. This will all disappear once render task graph is a full DAG.
|
||||
let mut save_color = false;
|
||||
let mut save_alpha = false;
|
||||
|
||||
for task_id in &pass.tasks {
|
||||
let task = &render_tasks[*task_id];
|
||||
match task.target_kind() {
|
||||
RenderTargetKind::Color => {
|
||||
save_color |= task.save_target;
|
||||
}
|
||||
RenderTargetKind::Alpha => {
|
||||
save_alpha |= task.save_target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return previous frame's textures to the target pool, so they are available
|
||||
// for use on subsequent passes. Also include these in the list for the renderer
|
||||
// to immediately invalidate once they are no longer used.
|
||||
for texture_id in active_texture_ids.drain(..) {
|
||||
resource_cache.return_render_target_to_pool(texture_id);
|
||||
pass.textures_to_invalidate.push(texture_id);
|
||||
}
|
||||
|
||||
if let Some(texture_id) = pass.color.texture_id {
|
||||
if save_color {
|
||||
saved_texture_ids.push(texture_id);
|
||||
} else {
|
||||
active_texture_ids.push(texture_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(texture_id) = pass.alpha.texture_id {
|
||||
if save_alpha {
|
||||
saved_texture_ids.push(texture_id);
|
||||
} else {
|
||||
active_texture_ids.push(texture_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(active_texture_ids.is_empty());
|
||||
|
||||
// At the end of the pass loop, return any saved textures to the pool. Also
|
||||
// add them to the final pass to invalidate those textures (which will be the
|
||||
// pass that writes to picture cache tiles or texture cache targets). This is
|
||||
// mostly implicit in the current scheme, but will become a lot clearer and
|
||||
// more explicit once we implement the full render task DAG.
|
||||
|
||||
for texture_id in saved_texture_ids.drain(..) {
|
||||
resource_cache.return_render_target_to_pool(texture_id);
|
||||
passes.last_mut().unwrap().textures_to_invalidate.push(texture_id);
|
||||
}
|
||||
|
||||
let mut ctx = RenderTargetContext {
|
||||
|
@ -726,23 +789,6 @@ pub fn build_render_pass(
|
|||
) {
|
||||
profile_scope!("build_render_pass");
|
||||
|
||||
let saved_color = if pass.tasks.iter().any(|&task_id| {
|
||||
let t = &render_tasks[task_id];
|
||||
t.target_kind() == RenderTargetKind::Color && t.saved_index.is_some()
|
||||
}) {
|
||||
Some(render_tasks.save_target())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let saved_alpha = if pass.tasks.iter().any(|&task_id| {
|
||||
let t = &render_tasks[task_id];
|
||||
t.target_kind() == RenderTargetKind::Alpha && t.saved_index.is_some()
|
||||
}) {
|
||||
Some(render_tasks.save_target())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Collect a list of picture cache tasks, keyed by picture index.
|
||||
// This allows us to only walk that picture root once, adding the
|
||||
// primitives to all relevant batches at the same time.
|
||||
|
@ -765,7 +811,7 @@ pub fn build_render_pass(
|
|||
RenderTargetKind::Color => pass.color.allocate(size),
|
||||
RenderTargetKind::Alpha => pass.alpha.allocate(size),
|
||||
};
|
||||
*origin = Some((alloc_origin, target_index));
|
||||
*origin = Some((alloc_origin, CacheTextureId::INVALID, target_index));
|
||||
(None, target_index.0)
|
||||
}
|
||||
RenderTaskLocation::PictureCache { .. } => {
|
||||
|
@ -789,15 +835,6 @@ pub fn build_render_pass(
|
|||
}
|
||||
};
|
||||
|
||||
// Replace the pending saved index with a real one
|
||||
if let Some(index) = task.saved_index {
|
||||
assert_eq!(index, SavedTargetIndex::PENDING);
|
||||
task.saved_index = match target_kind {
|
||||
RenderTargetKind::Color => saved_color,
|
||||
RenderTargetKind::Alpha => saved_alpha,
|
||||
};
|
||||
}
|
||||
|
||||
// Give the render task an opportunity to add any
|
||||
// information to the GPU cache, if appropriate.
|
||||
let (target_rect, target_index) = task.get_target_rect();
|
||||
|
@ -988,7 +1025,6 @@ pub fn build_render_pass(
|
|||
gpu_cache,
|
||||
render_tasks,
|
||||
deferred_resolves,
|
||||
saved_color,
|
||||
prim_headers,
|
||||
transforms,
|
||||
z_generator,
|
||||
|
@ -999,12 +1035,52 @@ pub fn build_render_pass(
|
|||
gpu_cache,
|
||||
render_tasks,
|
||||
deferred_resolves,
|
||||
saved_alpha,
|
||||
prim_headers,
|
||||
transforms,
|
||||
z_generator,
|
||||
composite_state,
|
||||
);
|
||||
|
||||
// Now that the passes have been built, we know what the texture_id is for this surface
|
||||
// (since we know the layer count and texture size). Step through the tasks on this
|
||||
// texture and store that in the task location. This is used so that tasks that get added
|
||||
// on following passes can directly access and reference the texture_id, rather than
|
||||
// referring to PrevPassAlpha/Color. Again, this will become a lot simpler once we
|
||||
// have the full DAG in place (and once sub-passes are individual textures rather than
|
||||
// a single texture array).
|
||||
|
||||
for &task_id in &pass.tasks {
|
||||
let task = &mut render_tasks[task_id];
|
||||
let target_kind = task.target_kind();
|
||||
|
||||
match task.location {
|
||||
RenderTaskLocation::TextureCache { .. } |
|
||||
RenderTaskLocation::PictureCache { .. } => {}
|
||||
|
||||
RenderTaskLocation::Dynamic(None, _) => {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
RenderTaskLocation::Dynamic(Some((_, ref mut texture_id, _)), _) => {
|
||||
assert_eq!(*texture_id, CacheTextureId::INVALID);
|
||||
|
||||
match target_kind {
|
||||
RenderTargetKind::Color => {
|
||||
*texture_id = pass
|
||||
.color
|
||||
.texture_id
|
||||
.expect("bug: color texture must be allocated by now");
|
||||
}
|
||||
RenderTargetKind::Alpha => {
|
||||
*texture_id = pass
|
||||
.alpha
|
||||
.texture_id
|
||||
.expect("bug: alpha texture must be allocated by now");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A rendering-oriented representation of the frame built by the render backend
|
||||
|
|
|
@ -235,6 +235,10 @@ pub struct SwizzleSettings {
|
|||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct CacheTextureId(pub u32);
|
||||
|
||||
impl CacheTextureId {
|
||||
pub const INVALID: CacheTextureId = CacheTextureId(!0);
|
||||
}
|
||||
|
||||
/// Canonical type for texture layer indices.
|
||||
///
|
||||
/// WebRender is currently not very consistent about layer index types. Some
|
||||
|
@ -248,21 +252,6 @@ pub struct CacheTextureId(pub u32);
|
|||
/// the device module when making calls into the platform layer.
|
||||
pub type LayerIndex = usize;
|
||||
|
||||
/// Identifies a render pass target that is persisted until the end of the frame.
|
||||
///
|
||||
/// By default, only the targets of the immediately-preceding pass are bound as
|
||||
/// inputs to the next pass. However, tasks can opt into having their target
|
||||
/// preserved in a list until the end of the frame, and this type specifies the
|
||||
/// index in that list.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct SavedTargetIndex(pub u32);
|
||||
|
||||
impl SavedTargetIndex {
|
||||
pub const PENDING: Self = SavedTargetIndex(!0);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
|
@ -276,17 +265,13 @@ pub enum TextureSource {
|
|||
/// Equivalent to `None`, allowing us to avoid using `Option`s everywhere.
|
||||
Invalid,
|
||||
/// An entry in the texture cache.
|
||||
TextureCache(CacheTextureId, Swizzle),
|
||||
TextureCache(CacheTextureId, ImageBufferKind, Swizzle),
|
||||
/// An external image texture, mananged by the embedding.
|
||||
External(DeferredResolveIndex, ImageBufferKind),
|
||||
/// The alpha target of the immediately-preceding pass.
|
||||
PrevPassAlpha,
|
||||
/// The color target of the immediately-preceding pass.
|
||||
PrevPassColor,
|
||||
/// A render target from an earlier pass. Unlike the immediately-preceding
|
||||
/// passes, these are not made available automatically, but are instead
|
||||
/// opt-in by the `RenderTask` (see `mark_for_saving()`).
|
||||
RenderTaskCache(SavedTargetIndex, Swizzle),
|
||||
/// Select a dummy 1x1 white texture. This can be used by image
|
||||
/// shaders that want to draw a solid color.
|
||||
Dummy,
|
||||
|
@ -295,14 +280,13 @@ pub enum TextureSource {
|
|||
impl TextureSource {
|
||||
pub fn image_buffer_kind(&self) -> ImageBufferKind {
|
||||
match *self {
|
||||
TextureSource::TextureCache(..) => ImageBufferKind::Texture2D,
|
||||
TextureSource::TextureCache(_, image_buffer_kind, _) => image_buffer_kind,
|
||||
|
||||
TextureSource::External(_, image_buffer_kind) => image_buffer_kind,
|
||||
|
||||
// Render tasks use texture arrays for now.
|
||||
TextureSource::PrevPassAlpha
|
||||
| TextureSource::PrevPassColor
|
||||
| TextureSource::RenderTaskCache(..)
|
||||
| TextureSource::Dummy => ImageBufferKind::Texture2DArray,
|
||||
|
||||
|
||||
|
|
|
@ -851,6 +851,8 @@ bitflags!{
|
|||
const RENDER_TASKS = 0b0001;
|
||||
///
|
||||
const TEXTURE_CACHE = 0b00001;
|
||||
/// Clear render target pool
|
||||
const RENDER_TARGETS = 0b000001;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
|
||||
use api::units::*;
|
||||
use api::{ColorF, PremultipliedColorF, ImageFormat, LineOrientation, BorderStyle};
|
||||
use api::{ColorF, PremultipliedColorF, ImageFormat, LineOrientation, BorderStyle, ImageBufferKind};
|
||||
use crate::batch::{AlphaBatchBuilder, AlphaBatchContainer, BatchTextures, resolve_image};
|
||||
use crate::batch::{ClipBatcher, BatchBuilder};
|
||||
use crate::spatial_tree::{SpatialTree, ROOT_SPATIAL_NODE_INDEX};
|
||||
|
@ -15,7 +15,7 @@ use crate::frame_builder::{FrameGlobalResources};
|
|||
use crate::gpu_cache::{GpuCache, GpuCacheAddress};
|
||||
use crate::gpu_types::{BorderInstance, SvgFilterInstance, BlurDirection, BlurInstance, PrimitiveHeaders, ScalingInstance};
|
||||
use crate::gpu_types::{TransformPalette, ZBufferIdGenerator};
|
||||
use crate::internal_types::{FastHashMap, TextureSource, LayerIndex, Swizzle, SavedTargetIndex};
|
||||
use crate::internal_types::{FastHashMap, TextureSource, LayerIndex, Swizzle, CacheTextureId};
|
||||
use crate::picture::{SliceId, SurfaceInfo, ResolvedSurfaceTexture, TileCacheInstance};
|
||||
use crate::prim_store::{PrimitiveStore, DeferredResolve, PrimitiveScratchBuffer};
|
||||
use crate::prim_store::gradient::GRADIENT_FP_STOPS;
|
||||
|
@ -158,7 +158,7 @@ pub trait RenderTarget {
|
|||
/// previous pass it depends on.
|
||||
///
|
||||
/// Note that in some cases (like drop-shadows), we can depend on the output of
|
||||
/// a pass earlier than the immediately-preceding pass. See `SavedTargetIndex`.
|
||||
/// a pass earlier than the immediately-preceding pass.
|
||||
#[cfg_attr(feature = "capture", derive(Serialize))]
|
||||
#[cfg_attr(feature = "replay", derive(Deserialize))]
|
||||
pub struct RenderTargetList<T> {
|
||||
|
@ -173,8 +173,8 @@ pub struct RenderTargetList<T> {
|
|||
/// allocator for the next slice to be just large enough to accomodate it.
|
||||
pub max_dynamic_size: DeviceIntSize,
|
||||
pub targets: Vec<T>,
|
||||
pub saved_index: Option<SavedTargetIndex>,
|
||||
pub alloc_tracker: GuillotineAllocator,
|
||||
pub texture_id: Option<CacheTextureId>,
|
||||
gpu_supports_fast_clears: bool,
|
||||
}
|
||||
|
||||
|
@ -189,8 +189,8 @@ impl<T: RenderTarget> RenderTargetList<T> {
|
|||
format,
|
||||
max_dynamic_size: DeviceIntSize::new(0, 0),
|
||||
targets: Vec::new(),
|
||||
saved_index: None,
|
||||
alloc_tracker: GuillotineAllocator::new(None),
|
||||
texture_id: None,
|
||||
gpu_supports_fast_clears,
|
||||
}
|
||||
}
|
||||
|
@ -201,14 +201,20 @@ impl<T: RenderTarget> RenderTargetList<T> {
|
|||
gpu_cache: &mut GpuCache,
|
||||
render_tasks: &mut RenderTaskGraph,
|
||||
deferred_resolves: &mut Vec<DeferredResolve>,
|
||||
saved_index: Option<SavedTargetIndex>,
|
||||
prim_headers: &mut PrimitiveHeaders,
|
||||
transforms: &mut TransformPalette,
|
||||
z_generator: &mut ZBufferIdGenerator,
|
||||
composite_state: &mut CompositeState,
|
||||
) {
|
||||
debug_assert_eq!(None, self.saved_index);
|
||||
self.saved_index = saved_index;
|
||||
if self.targets.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a bounding rect of all the layers, and round it up to a multiple
|
||||
// of 256. This improves render target reuse when resizing the window,
|
||||
// since we don't need to create a new render target for each slightly-
|
||||
// larger frame.
|
||||
let mut bounding_rect = DeviceIntRect::zero();
|
||||
|
||||
for target in &mut self.targets {
|
||||
target.build(
|
||||
|
@ -221,7 +227,24 @@ impl<T: RenderTarget> RenderTargetList<T> {
|
|||
z_generator,
|
||||
composite_state,
|
||||
);
|
||||
|
||||
bounding_rect = target.used_rect().union(&bounding_rect);
|
||||
}
|
||||
|
||||
debug_assert_eq!(bounding_rect.origin, DeviceIntPoint::zero());
|
||||
let dimensions = DeviceIntSize::new(
|
||||
(bounding_rect.size.width + 255) & !255,
|
||||
(bounding_rect.size.height + 255) & !255,
|
||||
);
|
||||
|
||||
let texture_id = ctx.resource_cache.get_or_create_render_target_from_pool(
|
||||
dimensions,
|
||||
self.targets.len(),
|
||||
self.format,
|
||||
);
|
||||
|
||||
assert!(self.texture_id.is_none());
|
||||
self.texture_id = Some(texture_id);
|
||||
}
|
||||
|
||||
pub fn allocate(
|
||||
|
@ -938,17 +961,31 @@ fn add_svg_filter_instances(
|
|||
) {
|
||||
let mut textures = BatchTextures::empty();
|
||||
|
||||
if let Some(saved_index) = input_1_task.map(|id| &render_tasks[id].saved_index) {
|
||||
textures.colors[0] = match saved_index {
|
||||
Some(saved_index) => TextureSource::RenderTaskCache(*saved_index, Swizzle::default()),
|
||||
None => TextureSource::PrevPassColor,
|
||||
if let Some(id) = input_1_task {
|
||||
let task = &render_tasks[id];
|
||||
|
||||
textures.colors[0] = if task.save_target {
|
||||
TextureSource::TextureCache(
|
||||
task.get_target_texture(),
|
||||
ImageBufferKind::Texture2DArray,
|
||||
Swizzle::default(),
|
||||
)
|
||||
} else {
|
||||
TextureSource::PrevPassColor
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(saved_index) = input_2_task.map(|id| &render_tasks[id].saved_index) {
|
||||
textures.colors[1] = match saved_index {
|
||||
Some(saved_index) => TextureSource::RenderTaskCache(*saved_index, Swizzle::default()),
|
||||
None => TextureSource::PrevPassColor,
|
||||
if let Some(id) = input_2_task {
|
||||
let task = &render_tasks[id];
|
||||
|
||||
textures.colors[1] = if task.save_target {
|
||||
TextureSource::TextureCache(
|
||||
task.get_target_texture(),
|
||||
ImageBufferKind::Texture2DArray,
|
||||
Swizzle::default(),
|
||||
)
|
||||
} else {
|
||||
TextureSource::PrevPassColor
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::filterdata::SFilterData;
|
|||
use crate::frame_builder::FrameBuilderConfig;
|
||||
use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
|
||||
use crate::gpu_types::{BorderInstance, ImageSource, UvRectKind};
|
||||
use crate::internal_types::{CacheTextureId, FastHashMap, LayerIndex, SavedTargetIndex};
|
||||
use crate::internal_types::{CacheTextureId, FastHashMap, LayerIndex};
|
||||
use crate::picture::ResolvedSurfaceTexture;
|
||||
use crate::prim_store::{ClipData, PictureIndex};
|
||||
use crate::prim_store::image::ImageCacheKey;
|
||||
|
@ -64,7 +64,7 @@ pub enum RenderTaskLocation {
|
|||
/// build phase, we invoke `RenderTargetList::alloc()` and store the
|
||||
/// resulting location in the first member. That location identifies the
|
||||
/// render target and the offset of the allocated region within that target.
|
||||
Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
|
||||
Dynamic(Option<(DeviceIntPoint, CacheTextureId, RenderTargetIndex)>, DeviceIntSize),
|
||||
/// The output of the `RenderTask` will be persisted beyond this frame, and
|
||||
/// thus should be drawn into the `TextureCache`.
|
||||
TextureCache {
|
||||
|
@ -106,7 +106,7 @@ impl RenderTaskLocation {
|
|||
pub fn to_source_rect(&self) -> (DeviceIntRect, LayerIndex) {
|
||||
match *self {
|
||||
RenderTaskLocation::Dynamic(None, _) => panic!("Expected position to be set for the task!"),
|
||||
RenderTaskLocation::Dynamic(Some((origin, layer)), size) => (DeviceIntRect::new(origin, size), layer.0 as LayerIndex),
|
||||
RenderTaskLocation::Dynamic(Some((origin, _, layer)), size) => (DeviceIntRect::new(origin, size), layer.0 as LayerIndex),
|
||||
RenderTaskLocation::TextureCache { rect, layer, .. } => (rect, layer),
|
||||
RenderTaskLocation::PictureCache { .. } => {
|
||||
panic!("bug: picture cache tasks should never be a source!");
|
||||
|
@ -718,7 +718,7 @@ pub struct RenderTask {
|
|||
pub location: RenderTaskLocation,
|
||||
pub children: TaskDependencies,
|
||||
pub kind: RenderTaskKind,
|
||||
pub saved_index: Option<SavedTargetIndex>,
|
||||
pub save_target: bool,
|
||||
}
|
||||
|
||||
impl RenderTask {
|
||||
|
@ -732,7 +732,7 @@ impl RenderTask {
|
|||
location,
|
||||
children: TaskDependencies::new(),
|
||||
kind,
|
||||
saved_index: None,
|
||||
save_target: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -758,7 +758,7 @@ impl RenderTask {
|
|||
location: RenderTaskLocation::Dynamic(None, size),
|
||||
children,
|
||||
kind,
|
||||
saved_index: None,
|
||||
save_target: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -772,7 +772,7 @@ impl RenderTask {
|
|||
location,
|
||||
children,
|
||||
kind: RenderTaskKind::Test(target),
|
||||
saved_index: None,
|
||||
save_target: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1338,6 +1338,20 @@ impl RenderTask {
|
|||
self.location.size()
|
||||
}
|
||||
|
||||
pub fn get_target_texture(&self) -> CacheTextureId {
|
||||
match self.location {
|
||||
RenderTaskLocation::Dynamic(Some((_, texture_id, _)), _) => {
|
||||
assert_ne!(texture_id, CacheTextureId::INVALID);
|
||||
texture_id
|
||||
}
|
||||
RenderTaskLocation::Dynamic(None, _) |
|
||||
RenderTaskLocation::TextureCache { .. } |
|
||||
RenderTaskLocation::PictureCache { .. } => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_target_rect(&self) -> (DeviceIntRect, RenderTargetIndex) {
|
||||
match self.location {
|
||||
// Previously, we only added render tasks after the entire
|
||||
|
@ -1354,7 +1368,7 @@ impl RenderTask {
|
|||
// TODO(gw): Consider some kind of tag or other method
|
||||
// to mark a task as unused explicitly. This
|
||||
// would allow us to restore this debug check.
|
||||
RenderTaskLocation::Dynamic(Some((origin, target_index)), size) => {
|
||||
RenderTaskLocation::Dynamic(Some((origin, _, target_index)), size) => {
|
||||
(DeviceIntRect::new(origin, size), target_index)
|
||||
}
|
||||
RenderTaskLocation::Dynamic(None, _) => {
|
||||
|
@ -1479,7 +1493,7 @@ impl RenderTask {
|
|||
pub fn mark_for_saving(&mut self) {
|
||||
match self.location {
|
||||
RenderTaskLocation::Dynamic(..) => {
|
||||
self.saved_index = Some(SavedTargetIndex::PENDING);
|
||||
self.save_target = true;
|
||||
}
|
||||
RenderTaskLocation::TextureCache { .. } |
|
||||
RenderTaskLocation::PictureCache { .. } => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
use api::ImageFormat;
|
||||
use api::units::*;
|
||||
use crate::internal_types::{CacheTextureId, FastHashMap, SavedTargetIndex};
|
||||
use crate::internal_types::{CacheTextureId, FastHashMap};
|
||||
use crate::render_backend::FrameId;
|
||||
use crate::render_target::{RenderTargetKind, RenderTargetList, ColorRenderTarget};
|
||||
use crate::render_target::{PictureCacheTarget, TextureCacheRenderTarget, AlphaRenderTarget};
|
||||
|
@ -24,7 +24,6 @@ pub struct RenderTaskGraph {
|
|||
///
|
||||
/// We render these unconditionally before-rendering the rest of the tree.
|
||||
pub cacheable_render_tasks: Vec<RenderTaskId>,
|
||||
next_saved: SavedTargetIndex,
|
||||
frame_id: FrameId,
|
||||
}
|
||||
|
||||
|
@ -55,7 +54,6 @@ impl RenderTaskGraph {
|
|||
tasks: Vec::with_capacity(counters.tasks_len + extra_items),
|
||||
task_data: Vec::with_capacity(counters.task_data_len + extra_items),
|
||||
cacheable_render_tasks: Vec::with_capacity(counters.cacheable_render_tasks_len + extra_items),
|
||||
next_saved: SavedTargetIndex(0),
|
||||
frame_id,
|
||||
}
|
||||
}
|
||||
|
@ -337,12 +335,6 @@ impl RenderTaskGraph {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn save_target(&mut self) -> SavedTargetIndex {
|
||||
let id = self.next_saved;
|
||||
self.next_saved.0 += 1;
|
||||
id
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn frame_id(&self) -> FrameId {
|
||||
self.frame_id
|
||||
|
@ -408,6 +400,7 @@ pub struct RenderPass {
|
|||
/// The set of tasks to be performed in this pass, as indices into the
|
||||
/// `RenderTaskGraph`.
|
||||
pub tasks: Vec<RenderTaskId>,
|
||||
pub textures_to_invalidate: Vec<CacheTextureId>,
|
||||
}
|
||||
|
||||
impl RenderPass {
|
||||
|
@ -430,6 +423,7 @@ impl RenderPass {
|
|||
texture_cache: FastHashMap::default(),
|
||||
picture_cache: Vec::new(),
|
||||
tasks: vec![],
|
||||
textures_to_invalidate: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -499,7 +493,7 @@ pub fn dump_render_tasks_as_svg(
|
|||
let tx = rect.x + rect.w / 2.0;
|
||||
let ty = rect.y + 10.0;
|
||||
|
||||
let saved = if task.saved_index.is_some() { " (Saved)" } else { "" };
|
||||
let saved = if task.save_target { " (Saved)" } else { "" };
|
||||
let label = text(tx, ty, format!("{}{}", task.kind.as_str(), saved));
|
||||
let size = text(tx, ty + 12.0, format!("{:?}", task.location.size()));
|
||||
|
||||
|
@ -799,7 +793,7 @@ fn blur_task_graph() {
|
|||
assert_eq!(passes[7].tasks, vec![main_pic]);
|
||||
|
||||
// See vblur4's comment above.
|
||||
assert!(tasks[scale2].saved_index.is_some());
|
||||
assert!(tasks[scale2].save_target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -76,7 +76,7 @@ use crate::gpu_types::{ClearInstance, CompositeInstance, TransformData, ZBufferI
|
|||
use crate::internal_types::{TextureSource, ResourceCacheError};
|
||||
use crate::internal_types::{CacheTextureId, DebugOutput, FastHashMap, FastHashSet, LayerIndex, RenderedDocument, ResultMsg};
|
||||
use crate::internal_types::{TextureCacheAllocationKind, TextureCacheUpdate, TextureUpdateList, TextureUpdateSource};
|
||||
use crate::internal_types::{RenderTargetInfo, SavedTargetIndex, Swizzle, DeferredResolveIndex};
|
||||
use crate::internal_types::{RenderTargetInfo, Swizzle, DeferredResolveIndex};
|
||||
use malloc_size_of::MallocSizeOfOps;
|
||||
use crate::picture::{self, ResolvedSurfaceTexture};
|
||||
use crate::prim_store::DeferredResolve;
|
||||
|
@ -91,11 +91,10 @@ use crate::resource_cache::ResourceCache;
|
|||
use crate::scene_builder_thread::{SceneBuilderThread, SceneBuilderThreadChannels, LowPrioritySceneBuilderThread};
|
||||
use crate::screen_capture::AsyncScreenshotGrabber;
|
||||
use crate::shade::{Shaders, WrShaders};
|
||||
use smallvec::SmallVec;
|
||||
use crate::guillotine_allocator::{GuillotineAllocator, FreeRectSlice};
|
||||
use crate::texture_cache::TextureCache;
|
||||
use crate::render_target::{AlphaRenderTarget, ColorRenderTarget, PictureCacheTarget};
|
||||
use crate::render_target::{RenderTarget, TextureCacheRenderTarget, RenderTargetList};
|
||||
use crate::render_target::{RenderTarget, TextureCacheRenderTarget};
|
||||
use crate::render_target::{RenderTargetKind, BlitJob, BlitJobSource};
|
||||
use crate::tile_cache::PictureCacheDebugInfo;
|
||||
use crate::util::drain_filter;
|
||||
|
@ -1153,13 +1152,6 @@ enum PartialPresentMode {
|
|||
},
|
||||
}
|
||||
|
||||
/// A Texture that has been initialized by the `device` module and is ready to
|
||||
/// be used.
|
||||
struct ActiveTexture {
|
||||
texture: Texture,
|
||||
saved_index: Option<SavedTargetIndex>,
|
||||
}
|
||||
|
||||
/// Helper struct for resolving device Textures for use during rendering passes.
|
||||
///
|
||||
/// Manages the mapping between the at-a-distance texture handles used by the
|
||||
|
@ -1178,30 +1170,8 @@ struct TextureResolver {
|
|||
dummy_cache_texture: Texture,
|
||||
|
||||
/// The outputs of the previous pass, if applicable.
|
||||
prev_pass_color: Option<ActiveTexture>,
|
||||
prev_pass_alpha: Option<ActiveTexture>,
|
||||
|
||||
/// Saved render targets from previous passes. This is used when a pass
|
||||
/// needs access to the result of a pass other than the immediately-preceding
|
||||
/// one. In this case, the `RenderTask` will get a non-`None` `saved_index`,
|
||||
/// which will cause the resulting render target to be persisted in this list
|
||||
/// (at that index) until the end of the frame.
|
||||
saved_targets: Vec<Texture>,
|
||||
|
||||
/// Pool of idle render target textures ready for re-use.
|
||||
///
|
||||
/// Naively, it would seem like we only ever need two pairs of (color,
|
||||
/// alpha) render targets: one for the output of the previous pass (serving
|
||||
/// as input to the current pass), and one for the output of the current
|
||||
/// pass. However, there are cases where the output of one pass is used as
|
||||
/// the input to multiple future passes. For example, drop-shadows draw the
|
||||
/// picture in pass X, then reference it in pass X+1 to create the blurred
|
||||
/// shadow, and pass the results of both X and X+1 to pass X+2 draw the
|
||||
/// actual content.
|
||||
///
|
||||
/// See the comments in `allocate_target_texture` for more insight on why
|
||||
/// reuse is a win.
|
||||
render_target_pool: Vec<Texture>,
|
||||
prev_pass_color: Option<CacheTextureId>,
|
||||
prev_pass_alpha: Option<CacheTextureId>,
|
||||
}
|
||||
|
||||
impl TextureResolver {
|
||||
|
@ -1227,8 +1197,6 @@ impl TextureResolver {
|
|||
dummy_cache_texture,
|
||||
prev_pass_alpha: None,
|
||||
prev_pass_color: None,
|
||||
saved_targets: Vec::default(),
|
||||
render_target_pool: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1238,135 +1206,26 @@ impl TextureResolver {
|
|||
for (_id, texture) in self.texture_cache_map {
|
||||
device.delete_texture(texture);
|
||||
}
|
||||
|
||||
for texture in self.render_target_pool {
|
||||
device.delete_texture(texture);
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_frame(&mut self) {
|
||||
assert!(self.prev_pass_color.is_none());
|
||||
assert!(self.prev_pass_alpha.is_none());
|
||||
assert!(self.saved_targets.is_empty());
|
||||
}
|
||||
|
||||
fn end_frame(&mut self, device: &mut Device, frame_id: GpuFrameId) {
|
||||
// return the cached targets to the pool
|
||||
self.end_pass(device, None, None);
|
||||
// return the saved targets as well
|
||||
while let Some(target) = self.saved_targets.pop() {
|
||||
self.return_to_pool(device, target);
|
||||
}
|
||||
|
||||
// GC the render target pool, if it's currently > 32 MB in size.
|
||||
//
|
||||
// We use a simple scheme whereby we drop any texture that hasn't been used
|
||||
// in the last 60 frames, until we are below the size threshold. This should
|
||||
// generally prevent any sustained build-up of unused textures, unless we don't
|
||||
// generate frames for a long period. This can happen when the window is
|
||||
// minimized, and we probably want to flush all the WebRender caches in that case [1].
|
||||
// There is also a second "red line" memory threshold which prevents
|
||||
// memory exhaustion if many render targets are allocated within a small
|
||||
// number of frames. For now this is set at 320 MB (10x the normal memory threshold).
|
||||
//
|
||||
// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1494099
|
||||
self.gc_targets(
|
||||
device,
|
||||
frame_id,
|
||||
32 * 1024 * 1024,
|
||||
32 * 1024 * 1024 * 10,
|
||||
60,
|
||||
);
|
||||
}
|
||||
|
||||
/// Transfers ownership of a render target back to the pool.
|
||||
fn return_to_pool(&mut self, device: &mut Device, target: Texture) {
|
||||
device.invalidate_render_target(&target);
|
||||
self.render_target_pool.push(target);
|
||||
}
|
||||
|
||||
/// Frees any memory possible, in the event of a memory pressure signal.
|
||||
fn on_memory_pressure(
|
||||
&mut self,
|
||||
device: &mut Device,
|
||||
) {
|
||||
// Clear all textures in the render target pool
|
||||
for target in self.render_target_pool.drain(..) {
|
||||
device.delete_texture(target);
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops all targets from the render target pool that do not satisfy the predicate.
|
||||
pub fn gc_targets(
|
||||
&mut self,
|
||||
device: &mut Device,
|
||||
current_frame_id: GpuFrameId,
|
||||
total_bytes_threshold: usize,
|
||||
total_bytes_red_line_threshold: usize,
|
||||
frames_threshold: usize,
|
||||
) {
|
||||
// Get the total GPU memory size used by the current render target pool
|
||||
let mut rt_pool_size_in_bytes: usize = self.render_target_pool
|
||||
.iter()
|
||||
.map(|t| t.size_in_bytes())
|
||||
.sum();
|
||||
|
||||
// If the total size of the pool is less than the threshold, don't bother
|
||||
// trying to GC any targets
|
||||
if rt_pool_size_in_bytes <= total_bytes_threshold {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort the current pool by age, so that we remove oldest textures first
|
||||
self.render_target_pool.sort_by_key(|t| t.last_frame_used());
|
||||
|
||||
// We can't just use retain() because `Texture` requires manual cleanup.
|
||||
let mut retained_targets = SmallVec::<[Texture; 8]>::new();
|
||||
|
||||
for target in self.render_target_pool.drain(..) {
|
||||
// Drop oldest textures until we are under the allowed size threshold.
|
||||
// However, if it's been used in very recently, it is always kept around,
|
||||
// which ensures we don't thrash texture allocations on pages that do
|
||||
// require a very large render target pool and are regularly changing.
|
||||
if (rt_pool_size_in_bytes > total_bytes_red_line_threshold) ||
|
||||
(rt_pool_size_in_bytes > total_bytes_threshold &&
|
||||
!target.used_recently(current_frame_id, frames_threshold))
|
||||
{
|
||||
rt_pool_size_in_bytes -= target.size_in_bytes();
|
||||
device.delete_texture(target);
|
||||
} else {
|
||||
retained_targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
self.render_target_pool.extend(retained_targets);
|
||||
}
|
||||
|
||||
fn end_pass(
|
||||
&mut self,
|
||||
device: &mut Device,
|
||||
a8_texture: Option<ActiveTexture>,
|
||||
rgba8_texture: Option<ActiveTexture>,
|
||||
textures_to_invalidate: &[CacheTextureId],
|
||||
a8_texture: Option<CacheTextureId>,
|
||||
rgba8_texture: Option<CacheTextureId>,
|
||||
) {
|
||||
// If we have cache textures from previous pass, return them to the pool.
|
||||
// Also assign the pool index of those cache textures to last pass's index because this is
|
||||
// the result of last pass.
|
||||
// Note: the order here is important, needs to match the logic in `RenderPass::build()`.
|
||||
if let Some(at) = self.prev_pass_color.take() {
|
||||
if let Some(index) = at.saved_index {
|
||||
assert_eq!(self.saved_targets.len() as u32, index.0);
|
||||
self.saved_targets.push(at.texture);
|
||||
} else {
|
||||
self.return_to_pool(device, at.texture);
|
||||
}
|
||||
}
|
||||
if let Some(at) = self.prev_pass_alpha.take() {
|
||||
if let Some(index) = at.saved_index {
|
||||
assert_eq!(self.saved_targets.len() as u32, index.0);
|
||||
self.saved_targets.push(at.texture);
|
||||
} else {
|
||||
self.return_to_pool(device, at.texture);
|
||||
}
|
||||
// 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];
|
||||
device.invalidate_render_target(render_target);
|
||||
}
|
||||
|
||||
// We have another pass to process, make these textures available
|
||||
|
@ -1388,7 +1247,7 @@ impl TextureResolver {
|
|||
}
|
||||
TextureSource::PrevPassAlpha => {
|
||||
let texture = match self.prev_pass_alpha {
|
||||
Some(ref at) => &at.texture,
|
||||
Some(ref id) => &self.texture_cache_map[id],
|
||||
None => &self.dummy_cache_texture,
|
||||
};
|
||||
let swizzle = Swizzle::default();
|
||||
|
@ -1397,7 +1256,7 @@ impl TextureResolver {
|
|||
}
|
||||
TextureSource::PrevPassColor => {
|
||||
let texture = match self.prev_pass_color {
|
||||
Some(ref at) => &at.texture,
|
||||
Some(ref id) => &self.texture_cache_map[id],
|
||||
None => &self.dummy_cache_texture,
|
||||
};
|
||||
let swizzle = Swizzle::default();
|
||||
|
@ -1411,33 +1270,11 @@ impl TextureResolver {
|
|||
device.bind_external_texture(sampler, texture);
|
||||
Swizzle::default()
|
||||
}
|
||||
TextureSource::TextureCache(index, swizzle) => {
|
||||
TextureSource::TextureCache(index, _, swizzle) => {
|
||||
let texture = &self.texture_cache_map[&index];
|
||||
device.bind_texture(sampler, texture, swizzle);
|
||||
swizzle
|
||||
}
|
||||
TextureSource::RenderTaskCache(saved_index, swizzle) => {
|
||||
if saved_index.0 < self.saved_targets.len() as u32 {
|
||||
let texture = &self.saved_targets[saved_index.0 as usize];
|
||||
device.bind_texture(sampler, texture, swizzle)
|
||||
} else {
|
||||
// Check if this saved index is referring to a the prev pass
|
||||
if Some(saved_index) == self.prev_pass_color.as_ref().and_then(|at| at.saved_index) {
|
||||
let texture = match self.prev_pass_color {
|
||||
Some(ref at) => &at.texture,
|
||||
None => &self.dummy_cache_texture,
|
||||
};
|
||||
device.bind_texture(sampler, texture, swizzle);
|
||||
} else if Some(saved_index) == self.prev_pass_alpha.as_ref().and_then(|at| at.saved_index) {
|
||||
let texture = match self.prev_pass_alpha {
|
||||
Some(ref at) => &at.texture,
|
||||
None => &self.dummy_cache_texture,
|
||||
};
|
||||
device.bind_texture(sampler, texture, swizzle);
|
||||
}
|
||||
}
|
||||
swizzle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1452,14 +1289,14 @@ impl TextureResolver {
|
|||
}
|
||||
TextureSource::PrevPassAlpha => Some((
|
||||
match self.prev_pass_alpha {
|
||||
Some(ref at) => &at.texture,
|
||||
Some(ref id) => &self.texture_cache_map[id],
|
||||
None => &self.dummy_cache_texture,
|
||||
},
|
||||
Swizzle::default(),
|
||||
)),
|
||||
TextureSource::PrevPassColor => Some((
|
||||
match self.prev_pass_color {
|
||||
Some(ref at) => &at.texture,
|
||||
Some(ref id) => &self.texture_cache_map[id],
|
||||
None => &self.dummy_cache_texture,
|
||||
},
|
||||
Swizzle::default(),
|
||||
|
@ -1467,12 +1304,9 @@ impl TextureResolver {
|
|||
TextureSource::External(..) => {
|
||||
panic!("BUG: External textures cannot be resolved, they can only be bound.");
|
||||
}
|
||||
TextureSource::TextureCache(index, swizzle) => {
|
||||
TextureSource::TextureCache(index, _, swizzle) => {
|
||||
Some((&self.texture_cache_map[&index], swizzle))
|
||||
}
|
||||
TextureSource::RenderTaskCache(saved_index, swizzle) => {
|
||||
Some((&self.saved_targets[saved_index.0 as usize], swizzle))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1504,9 +1338,6 @@ impl TextureResolver {
|
|||
for t in self.texture_cache_map.values() {
|
||||
report.texture_cache_textures += t.size_in_bytes();
|
||||
}
|
||||
for t in self.render_target_pool.iter() {
|
||||
report.render_target_textures += t.size_in_bytes();
|
||||
}
|
||||
|
||||
report
|
||||
}
|
||||
|
@ -3070,7 +2901,6 @@ impl Renderer {
|
|||
// the device module asserts if we delete textures while
|
||||
// not in a frame.
|
||||
if memory_pressure {
|
||||
self.texture_resolver.on_memory_pressure(&mut self.device);
|
||||
self.texture_upload_pbo_pool.on_memory_pressure(&mut self.device);
|
||||
}
|
||||
|
||||
|
@ -3773,12 +3603,6 @@ impl Renderer {
|
|||
surface_origin_is_top_left,
|
||||
);
|
||||
}
|
||||
// See comment for texture_resolver.begin_frame() for explanation
|
||||
// of why this must be done after all rendering, including debug
|
||||
// overlays. The end_frame() call implicitly calls end_pass(), which
|
||||
// should ensure any left over render targets get invalidated and
|
||||
// returned to the pool correctly.
|
||||
self.texture_resolver.end_frame(&mut self.device, cpu_frame_id);
|
||||
self.texture_upload_pbo_pool.end_frame(&mut self.device);
|
||||
self.device.end_frame();
|
||||
|
||||
|
@ -5617,41 +5441,33 @@ impl Renderer {
|
|||
) {
|
||||
profile_scope!("draw_texture_cache_target");
|
||||
|
||||
let texture_source = TextureSource::TextureCache(*texture, Swizzle::default());
|
||||
let projection = {
|
||||
let (texture, _) = self.texture_resolver
|
||||
.resolve(&texture_source)
|
||||
.expect("BUG: invalid target texture");
|
||||
let target_size = texture.get_dimensions();
|
||||
|
||||
Transform3D::ortho(
|
||||
0.0,
|
||||
target_size.width as f32,
|
||||
0.0,
|
||||
target_size.height as f32,
|
||||
self.device.ortho_near_plane(),
|
||||
self.device.ortho_far_plane(),
|
||||
)
|
||||
};
|
||||
|
||||
self.device.disable_depth();
|
||||
self.device.disable_depth_write();
|
||||
|
||||
self.set_blend(false, FramebufferKind::Other);
|
||||
|
||||
let texture = &self.texture_resolver.texture_cache_map[texture];
|
||||
let target_size = texture.get_dimensions();
|
||||
|
||||
let projection = Transform3D::ortho(
|
||||
0.0,
|
||||
target_size.width as f32,
|
||||
0.0,
|
||||
target_size.height as f32,
|
||||
self.device.ortho_near_plane(),
|
||||
self.device.ortho_far_plane(),
|
||||
);
|
||||
|
||||
let draw_target = DrawTarget::from_texture(
|
||||
texture,
|
||||
layer,
|
||||
false,
|
||||
);
|
||||
self.device.bind_draw_target(draw_target);
|
||||
|
||||
{
|
||||
let _timer = self.gpu_profiler.start_timer(GPU_TAG_CLEAR);
|
||||
|
||||
let (texture, _) = self.texture_resolver
|
||||
.resolve(&texture_source)
|
||||
.expect("BUG: invalid target texture");
|
||||
let draw_target = DrawTarget::from_texture(
|
||||
texture,
|
||||
layer,
|
||||
false,
|
||||
);
|
||||
self.device.bind_draw_target(draw_target);
|
||||
|
||||
self.device.disable_depth();
|
||||
self.device.disable_depth_write();
|
||||
self.set_blend(false, FramebufferKind::Other);
|
||||
|
@ -6017,85 +5833,6 @@ impl Renderer {
|
|||
partial_present_mode
|
||||
}
|
||||
|
||||
/// Allocates a texture to be used as the output for a rendering pass.
|
||||
///
|
||||
/// We make an effort to reuse render target textures across passes and
|
||||
/// across frames when the format and dimensions match. Because we use
|
||||
/// immutable storage, we can't resize textures.
|
||||
///
|
||||
/// We could consider approaches to re-use part of a larger target, if
|
||||
/// available. However, we'd need to be careful about eviction. Currently,
|
||||
/// render targets are freed if they haven't been used in 30 frames. If we
|
||||
/// used partial targets, we'd need to track how _much_ of the target has
|
||||
/// been used in the last 30 frames, since we could otherwise end up
|
||||
/// keeping an enormous target alive indefinitely by constantly using it
|
||||
/// in situations where a much smaller target would suffice.
|
||||
fn allocate_target_texture<T: RenderTarget>(
|
||||
&mut self,
|
||||
list: &mut RenderTargetList<T>,
|
||||
) -> Option<ActiveTexture> {
|
||||
if list.targets.is_empty() {
|
||||
return None
|
||||
}
|
||||
|
||||
// Get a bounding rect of all the layers, and round it up to a multiple
|
||||
// of 256. This improves render target reuse when resizing the window,
|
||||
// since we don't need to create a new render target for each slightly-
|
||||
// larger frame.
|
||||
let mut bounding_rect = DeviceIntRect::zero();
|
||||
for t in list.targets.iter() {
|
||||
bounding_rect = t.used_rect().union(&bounding_rect);
|
||||
}
|
||||
debug_assert_eq!(bounding_rect.origin, DeviceIntPoint::zero());
|
||||
let dimensions = DeviceIntSize::new(
|
||||
(bounding_rect.size.width + 255) & !255,
|
||||
(bounding_rect.size.height + 255) & !255,
|
||||
);
|
||||
|
||||
self.profile.inc(profiler::USED_TARGETS);
|
||||
|
||||
// Try finding a match in the existing pool. If there's no match, we'll
|
||||
// create a new texture.
|
||||
let selector = TargetSelector {
|
||||
size: dimensions,
|
||||
num_layers: list.targets.len(),
|
||||
format: list.format,
|
||||
};
|
||||
let index = self.texture_resolver.render_target_pool
|
||||
.iter()
|
||||
.position(|texture| {
|
||||
selector == TargetSelector {
|
||||
size: texture.get_dimensions(),
|
||||
num_layers: texture.get_layer_count() as usize,
|
||||
format: texture.get_format(),
|
||||
}
|
||||
});
|
||||
|
||||
let rt_info = RenderTargetInfo { has_depth: list.needs_depth() };
|
||||
let texture = if let Some(idx) = index {
|
||||
let mut t = self.texture_resolver.render_target_pool.swap_remove(idx);
|
||||
self.device.reuse_render_target::<u8>(&mut t, rt_info);
|
||||
t
|
||||
} else {
|
||||
self.profile.inc(profiler::CREATED_TARGETS);
|
||||
self.device.create_texture(
|
||||
ImageBufferKind::Texture2DArray,
|
||||
list.format,
|
||||
dimensions.width,
|
||||
dimensions.height,
|
||||
TextureFilter::Linear,
|
||||
Some(rt_info),
|
||||
list.targets.len() as _,
|
||||
)
|
||||
};
|
||||
|
||||
list.check_ready(&texture);
|
||||
Some(ActiveTexture {
|
||||
texture,
|
||||
saved_index: list.saved_index.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn bind_frame_data(&mut self, frame: &mut Frame) {
|
||||
profile_scope!("bind_frame_data");
|
||||
|
||||
|
@ -6258,9 +5995,6 @@ impl Renderer {
|
|||
|
||||
profile_scope!("offscreen target");
|
||||
|
||||
let alpha_tex = self.allocate_target_texture(&mut pass.alpha);
|
||||
let color_tex = self.allocate_target_texture(&mut pass.color);
|
||||
|
||||
// If this frame has already been drawn, then any texture
|
||||
// cache targets have already been updated and can be
|
||||
// skipped this time.
|
||||
|
@ -6352,8 +6086,19 @@ impl Renderer {
|
|||
|
||||
for (target_index, target) in pass.alpha.targets.iter().enumerate() {
|
||||
results.stats.alpha_target_count += 1;
|
||||
|
||||
let texture_id = pass
|
||||
.alpha
|
||||
.texture_id
|
||||
.expect("bug: no surface for pass");
|
||||
|
||||
let alpha_tex = self.texture_resolver
|
||||
.texture_cache_map
|
||||
.get_mut(&texture_id)
|
||||
.expect("bug: texture not allocated");
|
||||
|
||||
let draw_target = DrawTarget::from_texture(
|
||||
&alpha_tex.as_ref().unwrap().texture,
|
||||
alpha_tex,
|
||||
target_index,
|
||||
false,
|
||||
);
|
||||
|
@ -6376,10 +6121,28 @@ impl Renderer {
|
|||
);
|
||||
}
|
||||
|
||||
let color_rt_info = RenderTargetInfo { has_depth: pass.color.needs_depth() };
|
||||
|
||||
for (target_index, target) in pass.color.targets.iter().enumerate() {
|
||||
results.stats.color_target_count += 1;
|
||||
|
||||
let texture_id = pass
|
||||
.color
|
||||
.texture_id
|
||||
.expect("bug: no surface for pass");
|
||||
|
||||
let color_tex = self.texture_resolver
|
||||
.texture_cache_map
|
||||
.get_mut(&texture_id)
|
||||
.expect("bug: texture not allocated");
|
||||
|
||||
self.device.reuse_render_target::<u8>(
|
||||
color_tex,
|
||||
color_rt_info,
|
||||
);
|
||||
|
||||
let draw_target = DrawTarget::from_texture(
|
||||
&color_tex.as_ref().unwrap().texture,
|
||||
color_tex,
|
||||
target_index,
|
||||
target.needs_depth(),
|
||||
);
|
||||
|
@ -6417,8 +6180,9 @@ impl Renderer {
|
|||
// resolve stage in mobile / tiled GPUs.
|
||||
self.texture_resolver.end_pass(
|
||||
&mut self.device,
|
||||
alpha_tex,
|
||||
color_tex,
|
||||
&pass.textures_to_invalidate,
|
||||
pass.alpha.texture_id,
|
||||
pass.color.texture_id,
|
||||
);
|
||||
{
|
||||
profile_scope!("gl.flush");
|
||||
|
@ -6601,8 +6365,11 @@ impl Renderer {
|
|||
None => return,
|
||||
};
|
||||
|
||||
let textures =
|
||||
self.texture_resolver.render_target_pool.iter().collect::<Vec<&Texture>>();
|
||||
let textures = self.texture_resolver
|
||||
.texture_cache_map
|
||||
.values()
|
||||
.filter(|texture| { texture.is_render_target() })
|
||||
.collect::<Vec<&Texture>>();
|
||||
|
||||
Self::do_debug_blit(
|
||||
&mut self.device,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* 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/. */
|
||||
|
||||
use api::{BlobImageResources, BlobImageRequest, RasterizedBlobImage};
|
||||
use api::{BlobImageResources, BlobImageRequest, RasterizedBlobImage, ImageFormat};
|
||||
use api::{DebugFlags, FontInstanceKey, FontKey, FontTemplate, GlyphIndex};
|
||||
use api::{ExternalImageData, ExternalImageType, ExternalImageId, BlobImageResult, FontInstanceData};
|
||||
use api::{DirtyRect, GlyphDimensions, IdNamespace, DEFAULT_TILE_SIZE};
|
||||
|
@ -25,7 +25,7 @@ use crate::glyph_cache::GlyphCacheEntry;
|
|||
use crate::glyph_rasterizer::{GLYPH_FLASHING, FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
|
||||
use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
|
||||
use crate::gpu_types::UvRectKind;
|
||||
use crate::internal_types::{FastHashMap, FastHashSet, TextureSource, ResourceUpdateList};
|
||||
use crate::internal_types::{CacheTextureId, FastHashMap, FastHashSet, TextureSource, ResourceUpdateList};
|
||||
use crate::profiler::{self, TransactionProfile, bytes_to_mb};
|
||||
use crate::render_backend::{FrameId, FrameStamp};
|
||||
use crate::render_task_graph::{RenderTaskGraph, RenderTaskId};
|
||||
|
@ -422,6 +422,30 @@ pub type GlyphDimensionsCache = FastHashMap<(FontInstanceKey, GlyphIndex), Optio
|
|||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct BlobImageRasterizerEpoch(usize);
|
||||
|
||||
/// Internal information about allocated render targets in the pool
|
||||
struct RenderTarget {
|
||||
size: DeviceIntSize,
|
||||
num_layers: usize,
|
||||
format: ImageFormat,
|
||||
texture_id: CacheTextureId,
|
||||
/// If true, this is currently leant out, and not available to other passes
|
||||
is_active: bool,
|
||||
last_frame_used: FrameId,
|
||||
}
|
||||
|
||||
impl RenderTarget {
|
||||
fn size_in_bytes(&self) -> usize {
|
||||
let bpp = self.format.bytes_per_pixel() as usize;
|
||||
self.num_layers * (self.size.width * self.size.height) as usize * bpp
|
||||
}
|
||||
|
||||
/// Returns true if this texture was used within `threshold` frames of
|
||||
/// the current frame.
|
||||
pub fn used_recently(&self, current_frame_id: FrameId, threshold: usize) -> bool {
|
||||
self.last_frame_used + threshold >= current_frame_id
|
||||
}
|
||||
}
|
||||
|
||||
/// High-level container for resources managed by the `RenderBackend`.
|
||||
///
|
||||
/// This includes a variety of things, including images, fonts, and glyphs,
|
||||
|
@ -464,6 +488,9 @@ pub struct ResourceCache {
|
|||
|
||||
image_templates_memory: usize,
|
||||
font_templates_memory: usize,
|
||||
|
||||
/// A pool of render targets for use by the render task graph
|
||||
render_target_pool: Vec<RenderTarget>,
|
||||
}
|
||||
|
||||
impl ResourceCache {
|
||||
|
@ -497,6 +524,7 @@ impl ResourceCache {
|
|||
capture_dirty: true,
|
||||
image_templates_memory: 0,
|
||||
font_templates_memory: 0,
|
||||
render_target_pool: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1405,6 +1433,25 @@ impl ResourceCache {
|
|||
debug_assert_eq!(self.state, State::QueryResources);
|
||||
profile_scope!("end_frame");
|
||||
self.state = State::Idle;
|
||||
|
||||
// GC the render target pool, if it's currently > 32 MB in size.
|
||||
//
|
||||
// We use a simple scheme whereby we drop any texture that hasn't been used
|
||||
// in the last 60 frames, until we are below the size threshold. This should
|
||||
// generally prevent any sustained build-up of unused textures, unless we don't
|
||||
// generate frames for a long period. This can happen when the window is
|
||||
// minimized, and we probably want to flush all the WebRender caches in that case [1].
|
||||
// There is also a second "red line" memory threshold which prevents
|
||||
// memory exhaustion if many render targets are allocated within a small
|
||||
// number of frames. For now this is set at 320 MB (10x the normal memory threshold).
|
||||
//
|
||||
// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1494099
|
||||
self.gc_render_targets(
|
||||
32 * 1024 * 1024,
|
||||
32 * 1024 * 1024 * 10,
|
||||
60,
|
||||
);
|
||||
|
||||
self.texture_cache.end_frame(profile);
|
||||
}
|
||||
|
||||
|
@ -1431,6 +1478,9 @@ impl ResourceCache {
|
|||
if what.contains(ClearCache::TEXTURE_CACHE) {
|
||||
self.texture_cache.clear_all();
|
||||
}
|
||||
if what.contains(ClearCache::RENDER_TARGETS) {
|
||||
self.clear_render_target_pool();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_namespace(&mut self, namespace: IdNamespace) {
|
||||
|
@ -1522,6 +1572,120 @@ impl ResourceCache {
|
|||
assert!(!self.rasterized_blob_images.keys().any(&blob_f));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a render target from the pool, or allocate a new one if none are
|
||||
/// currently available that match the requested parameters.
|
||||
pub fn get_or_create_render_target_from_pool(
|
||||
&mut self,
|
||||
size: DeviceIntSize,
|
||||
num_layers: usize,
|
||||
format: ImageFormat,
|
||||
) -> CacheTextureId {
|
||||
for target in &mut self.render_target_pool {
|
||||
if target.size == size &&
|
||||
target.num_layers == num_layers &&
|
||||
target.format == format &&
|
||||
!target.is_active {
|
||||
// Found a target that's not currently in use which matches. Update
|
||||
// the last_frame_used for GC purposes.
|
||||
target.is_active = true;
|
||||
target.last_frame_used = self.current_frame_id;
|
||||
return target.texture_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to create a new render target and add it to the pool
|
||||
|
||||
let texture_id = self.texture_cache.alloc_render_target(
|
||||
size,
|
||||
num_layers,
|
||||
format,
|
||||
);
|
||||
|
||||
self.render_target_pool.push(RenderTarget {
|
||||
size,
|
||||
num_layers,
|
||||
format,
|
||||
texture_id,
|
||||
is_active: true,
|
||||
last_frame_used: self.current_frame_id,
|
||||
});
|
||||
|
||||
texture_id
|
||||
}
|
||||
|
||||
/// Return a render target to the pool.
|
||||
pub fn return_render_target_to_pool(
|
||||
&mut self,
|
||||
id: CacheTextureId,
|
||||
) {
|
||||
let target = self.render_target_pool
|
||||
.iter_mut()
|
||||
.find(|t| t.texture_id == id)
|
||||
.expect("bug: invalid render target id");
|
||||
|
||||
assert!(target.is_active);
|
||||
target.is_active = false;
|
||||
}
|
||||
|
||||
/// Clear all current render targets (e.g. on memory pressure)
|
||||
fn clear_render_target_pool(
|
||||
&mut self,
|
||||
) {
|
||||
for target in self.render_target_pool.drain(..) {
|
||||
debug_assert!(!target.is_active);
|
||||
self.texture_cache.free_render_target(target.texture_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Garbage collect and remove old render targets from the pool that haven't
|
||||
/// been used for some time.
|
||||
fn gc_render_targets(
|
||||
&mut self,
|
||||
total_bytes_threshold: usize,
|
||||
total_bytes_red_line_threshold: usize,
|
||||
frames_threshold: usize,
|
||||
) {
|
||||
// Get the total GPU memory size used by the current render target pool
|
||||
let mut rt_pool_size_in_bytes: usize = self.render_target_pool
|
||||
.iter()
|
||||
.map(|t| t.size_in_bytes())
|
||||
.sum();
|
||||
|
||||
// If the total size of the pool is less than the threshold, don't bother
|
||||
// trying to GC any targets
|
||||
if rt_pool_size_in_bytes <= total_bytes_threshold {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort the current pool by age, so that we remove oldest textures first
|
||||
self.render_target_pool.sort_by_key(|t| t.last_frame_used);
|
||||
|
||||
// We can't just use retain() because `RenderTarget` requires manual cleanup.
|
||||
let mut retained_targets = SmallVec::<[RenderTarget; 8]>::new();
|
||||
|
||||
for target in self.render_target_pool.drain(..) {
|
||||
debug_assert!(!target.is_active);
|
||||
|
||||
// Drop oldest textures until we are under the allowed size threshold.
|
||||
// However, if it's been used in very recently, it is always kept around,
|
||||
// which ensures we don't thrash texture allocations on pages that do
|
||||
// require a very large render target pool and are regularly changing.
|
||||
let above_red_line = rt_pool_size_in_bytes > total_bytes_red_line_threshold;
|
||||
let above_threshold = rt_pool_size_in_bytes > total_bytes_threshold;
|
||||
let used_recently = target.used_recently(self.current_frame_id, frames_threshold);
|
||||
let used_this_frame = target.last_frame_used == self.current_frame_id;
|
||||
|
||||
if !used_this_frame && (above_red_line || (above_threshold && !used_recently)) {
|
||||
rt_pool_size_in_bytes -= target.size_in_bytes();
|
||||
self.texture_cache.free_render_target(target.texture_id);
|
||||
} else {
|
||||
retained_targets.push(target);
|
||||
}
|
||||
}
|
||||
|
||||
self.render_target_pool.extend(retained_targets);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ResourceCache {
|
||||
|
|
|
@ -825,7 +825,11 @@ impl TextureCache {
|
|||
let (texture_id, layer_index, uv_rect, swizzle, uv_rect_handle, user_data) = self.get_cache_location(handle);
|
||||
CacheItem {
|
||||
uv_rect_handle,
|
||||
texture_id: TextureSource::TextureCache(texture_id, swizzle),
|
||||
texture_id: TextureSource::TextureCache(
|
||||
texture_id,
|
||||
ImageBufferKind::Texture2D,
|
||||
swizzle,
|
||||
),
|
||||
uv_rect,
|
||||
texture_layer: layer_index as i32,
|
||||
user_data,
|
||||
|
@ -1114,6 +1118,41 @@ impl TextureCache {
|
|||
allowed_in_shared_cache
|
||||
}
|
||||
|
||||
/// Allocate a render target via the pending updates sent to the renderer
|
||||
pub fn alloc_render_target(
|
||||
&mut self,
|
||||
size: DeviceIntSize,
|
||||
num_layers: usize,
|
||||
format: ImageFormat,
|
||||
) -> CacheTextureId {
|
||||
let texture_id = self.next_id;
|
||||
self.next_id.0 += 1;
|
||||
|
||||
// Push a command to allocate device storage of the right size / format.
|
||||
let info = TextureCacheAllocInfo {
|
||||
target: ImageBufferKind::Texture2DArray,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
format,
|
||||
filter: TextureFilter::Linear,
|
||||
layer_count: num_layers as i32,
|
||||
is_shared_cache: false,
|
||||
has_depth: false,
|
||||
};
|
||||
|
||||
self.pending_updates.push_alloc(texture_id, info);
|
||||
|
||||
texture_id
|
||||
}
|
||||
|
||||
/// Free an existing render target
|
||||
pub fn free_render_target(
|
||||
&mut self,
|
||||
id: CacheTextureId,
|
||||
) {
|
||||
self.pending_updates.push_free(id);
|
||||
}
|
||||
|
||||
/// Allocates a new standalone cache entry.
|
||||
fn allocate_standalone_entry(
|
||||
&mut self,
|
||||
|
|
Загрузка…
Ссылка в новой задаче