diff --git a/gfx/wr/webrender/src/batch.rs b/gfx/wr/webrender/src/batch.rs index d42a467adda2..42c29282d8b1 100644 --- a/gfx/wr/webrender/src/batch.rs +++ b/gfx/wr/webrender/src/batch.rs @@ -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), diff --git a/gfx/wr/webrender/src/device/gl.rs b/gfx/wr/webrender/src/device/gl.rs index 63cfef3fb5dc..c6767a60bf2a 100644 --- a/gfx/wr/webrender/src/device/gl.rs +++ b/gfx/wr/webrender/src/device/gl.rs @@ -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 { diff --git a/gfx/wr/webrender/src/frame_builder.rs b/gfx/wr/webrender/src/frame_builder.rs index 314576d31f4f..a51c9f8a074e 100644 --- a/gfx/wr/webrender/src/frame_builder.rs +++ b/gfx/wr/webrender/src/frame_builder.rs @@ -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 diff --git a/gfx/wr/webrender/src/internal_types.rs b/gfx/wr/webrender/src/internal_types.rs index d5c387e3c9f1..3b72d3b23b4b 100644 --- a/gfx/wr/webrender/src/internal_types.rs +++ b/gfx/wr/webrender/src/internal_types.rs @@ -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, diff --git a/gfx/wr/webrender/src/render_api.rs b/gfx/wr/webrender/src/render_api.rs index 596a17510778..ae7ec496449a 100644 --- a/gfx/wr/webrender/src/render_api.rs +++ b/gfx/wr/webrender/src/render_api.rs @@ -851,6 +851,8 @@ bitflags!{ const RENDER_TASKS = 0b0001; /// const TEXTURE_CACHE = 0b00001; + /// Clear render target pool + const RENDER_TARGETS = 0b000001; } } diff --git a/gfx/wr/webrender/src/render_target.rs b/gfx/wr/webrender/src/render_target.rs index 405a5d44727d..f8850064b6d7 100644 --- a/gfx/wr/webrender/src/render_target.rs +++ b/gfx/wr/webrender/src/render_target.rs @@ -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 { @@ -173,8 +173,8 @@ pub struct RenderTargetList { /// allocator for the next slice to be just large enough to accomodate it. pub max_dynamic_size: DeviceIntSize, pub targets: Vec, - pub saved_index: Option, pub alloc_tracker: GuillotineAllocator, + pub texture_id: Option, gpu_supports_fast_clears: bool, } @@ -189,8 +189,8 @@ impl RenderTargetList { 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 RenderTargetList { gpu_cache: &mut GpuCache, render_tasks: &mut RenderTaskGraph, deferred_resolves: &mut Vec, - saved_index: Option, 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 RenderTargetList { 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 }; } diff --git a/gfx/wr/webrender/src/render_task.rs b/gfx/wr/webrender/src/render_task.rs index 69e70f620b80..0b6a384798ff 100644 --- a/gfx/wr/webrender/src/render_task.rs +++ b/gfx/wr/webrender/src/render_task.rs @@ -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, + 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 { .. } => { diff --git a/gfx/wr/webrender/src/render_task_graph.rs b/gfx/wr/webrender/src/render_task_graph.rs index fd52efb8ec3d..b6ce39c2e0c1 100644 --- a/gfx/wr/webrender/src/render_task_graph.rs +++ b/gfx/wr/webrender/src/render_task_graph.rs @@ -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, - 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, + pub textures_to_invalidate: Vec, } 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] diff --git a/gfx/wr/webrender/src/renderer.rs b/gfx/wr/webrender/src/renderer.rs index 056131b79dde..7124a19c56fe 100644 --- a/gfx/wr/webrender/src/renderer.rs +++ b/gfx/wr/webrender/src/renderer.rs @@ -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, -} - /// 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, - prev_pass_alpha: Option, - - /// 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, - - /// 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, + prev_pass_color: Option, + prev_pass_alpha: Option, } 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, - rgba8_texture: Option, + textures_to_invalidate: &[CacheTextureId], + a8_texture: Option, + rgba8_texture: Option, ) { - // 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( - &mut self, - list: &mut RenderTargetList, - ) -> Option { - 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::(&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::( + 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::>(); + let textures = self.texture_resolver + .texture_cache_map + .values() + .filter(|texture| { texture.is_render_target() }) + .collect::>(); Self::do_debug_blit( &mut self.device, diff --git a/gfx/wr/webrender/src/resource_cache.rs b/gfx/wr/webrender/src/resource_cache.rs index 3ebead719afc..dcdff7bfee5a 100644 --- a/gfx/wr/webrender/src/resource_cache.rs +++ b/gfx/wr/webrender/src/resource_cache.rs @@ -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, } 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 { diff --git a/gfx/wr/webrender/src/texture_cache.rs b/gfx/wr/webrender/src/texture_cache.rs index fc6455fbe47e..ff8196ccfbec 100644 --- a/gfx/wr/webrender/src/texture_cache.rs +++ b/gfx/wr/webrender/src/texture_cache.rs @@ -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,