Bug 1682365 - Pt 3 - Remove array textures from picture cache tiles. r=nical,jnicol

This patch makes picture cache tiles use normal textures instead
of array textures. With this and the previous patch, WR no longer
uses array textures at all (except when provided by the external
image handler trait).

Differential Revision: https://phabricator.services.mozilla.com/D99013
This commit is contained in:
Glenn Watson 2020-12-18 22:23:40 +00:00
parent 60045ddd24
commit 780e30c7a2
3 changed files with 160 additions and 158 deletions

View File

@ -4763,14 +4763,14 @@ impl Renderer {
.borrow_mut()
.get_composite_shader(
CompositeSurfaceFormat::Rgba,
ImageBufferKind::Texture2DArray,
ImageBufferKind::Texture2D,
).bind(
&mut self.device,
projection,
&mut self.renderer_errors
);
let mut current_shader_params = (CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2DArray);
let mut current_shader_params = (CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2D);
let mut current_textures = BatchTextures::empty();
let mut instances = Vec::new();
@ -4840,7 +4840,7 @@ impl Renderer {
tile.z_id,
),
BatchTextures::composite_rgb(texture),
(CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2DArray),
(CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2D),
)
}
CompositeTileSurface::ExternalSurface { external_surface_index } => {

View File

@ -18,12 +18,13 @@ use crate::internal_types::{
};
use crate::lru_cache::LRUCache;
use crate::profiler::{self, TransactionProfile};
use crate::render_backend::FrameStamp;
use crate::render_backend::{FrameStamp, FrameId};
use crate::resource_cache::{CacheItem, CachedImageData};
use crate::atlas_allocator::*;
use crate::slab_allocator::*;
use smallvec::SmallVec;
use std::cell::Cell;
use std::mem;
use std::{cmp, mem};
use std::rc::Rc;
use euclid::size2;
@ -43,11 +44,6 @@ pub enum TargetShader {
/// The size of each region in shared cache texture arrays.
pub const TEXTURE_REGION_DIMENSIONS: i32 = 512;
const PICTURE_TEXTURE_SLICE_COUNT: usize = 8;
/// The chosen image format for picture tiles.
const PICTURE_TILE_FORMAT: ImageFormat = ImageFormat::RGBA8;
/// Items in the texture cache can either be standalone textures,
/// or a sub-rect inside the shared cache.
#[derive(Debug)]
@ -59,10 +55,8 @@ enum EntryDetails {
size_in_bytes: usize,
},
Picture {
// Index in the picture_textures array
texture_index: usize,
// Slice in the texture array
layer_index: usize,
/// Size of the tile (used for debug clears only)
size: DeviceIntSize,
},
Cache {
/// Origin within the texture layer where this item exists.
@ -73,10 +67,11 @@ enum EntryDetails {
}
impl EntryDetails {
// TODO(gw): We can remove LayerIndex from here completely in a follow up
fn describe(&self) -> (LayerIndex, DeviceIntPoint) {
match *self {
EntryDetails::Standalone { .. } => (0, DeviceIntPoint::zero()),
EntryDetails::Picture { layer_index, .. } => (layer_index, DeviceIntPoint::zero()),
EntryDetails::Picture { .. } => (0, DeviceIntPoint::zero()),
EntryDetails::Cache { origin, .. } => (0, origin),
}
}
@ -359,12 +354,34 @@ impl SharedTextures {
}
}
/// The texture arrays used to hold picture cache tiles.
/// The textures used to hold picture cache tiles.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
struct PictureTexture {
texture_id: CacheTextureId,
size: DeviceIntSize,
format: ImageFormat,
is_allocated: bool,
last_frame_used: FrameId,
}
impl PictureTexture {
fn size_in_bytes(&self) -> usize {
let bpp = self.format.bytes_per_pixel() as usize;
(self.size.width * self.size.height) as usize * bpp
}
}
/// The textures used to hold picture cache tiles.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
struct PictureTextures {
textures: Vec<WholeTextureArray>,
/// Current list of textures in the pool
textures: Vec<PictureTexture>,
/// Default tile size for content tiles
default_tile_size: DeviceIntSize,
/// Number of currently allocated textures in the pool
allocated_texture_count: usize,
}
impl PictureTextures {
@ -374,6 +391,7 @@ impl PictureTextures {
PictureTextures {
textures: Vec::new(),
default_tile_size,
allocated_texture_count: 0,
}
}
@ -384,50 +402,86 @@ impl PictureTextures {
next_texture_id: &mut CacheTextureId,
pending_updates: &mut TextureUpdateList,
) -> CacheEntry {
// Attempt to find an existing texture with matching tile size and
// and available slice.
for (i, texture) in self.textures.iter_mut().enumerate() {
if texture.size == tile_size {
if let Some(layer_index) = texture.find_free() {
return texture.occupy(i, layer_index, now);
}
let mut texture_id = None;
self.allocated_texture_count += 1;
for texture in &mut self.textures {
if texture.size == tile_size && !texture.is_allocated {
// Found a target that's not currently in use which matches. Update
// the last_frame_used for GC purposes.
texture.is_allocated = true;
texture.last_frame_used = FrameId::INVALID;
texture_id = Some(texture.texture_id);
break;
}
}
// Allocate a new texture with fixed number of slices.
let mut slices = Vec::new();
for _ in 0 .. PICTURE_TEXTURE_SLICE_COUNT {
slices.push(WholeTextureSlice {
uv_rect_handle: None,
// Need to create a new render target and add it to the pool
let texture_id = texture_id.unwrap_or_else(|| {
let texture_id = *next_texture_id;
next_texture_id.0 += 1;
// Push a command to allocate device storage of the right size / format.
let info = TextureCacheAllocInfo {
target: ImageBufferKind::Texture2D,
width: tile_size.width,
height: tile_size.height,
format: ImageFormat::RGBA8,
filter: TextureFilter::Nearest,
layer_count: 1,
is_shared_cache: false,
has_depth: true,
};
pending_updates.push_alloc(texture_id, info);
self.textures.push(PictureTexture {
texture_id,
is_allocated: true,
format: ImageFormat::RGBA8,
size: tile_size,
last_frame_used: FrameId::INVALID,
});
}
let mut texture = WholeTextureArray {
texture_id
});
CacheEntry {
size: tile_size,
user_data: [0.0; 3],
last_access: now,
details: EntryDetails::Picture {
size: tile_size,
},
uv_rect_handle: GpuCacheHandle::new(),
input_format: ImageFormat::RGBA8,
filter: TextureFilter::Nearest,
format: PICTURE_TILE_FORMAT,
texture_id: *next_texture_id,
slices,
has_depth: true,
};
next_texture_id.0 += 1;
// Occupy the first slice of the new texture
let entry = texture.occupy(
self.textures.len(),
0,
now,
);
// Push the alloc to render thread pending updates
let info = texture.to_info();
pending_updates.push_alloc(texture.texture_id, info);
self.textures.push(texture);
entry
swizzle: Swizzle::default(),
texture_id,
eviction_notice: None,
uv_rect_kind: UvRectKind::Rect,
shader: TargetShader::Default,
}
}
fn get(&mut self, index: usize) -> &mut WholeTextureArray {
&mut self.textures[index]
fn free_tile(
&mut self,
id: CacheTextureId,
current_frame_id: FrameId,
) {
self.allocated_texture_count -= 1;
let texture = self.textures
.iter_mut()
.find(|t| t.texture_id == id)
.expect("bug: invalid texture id");
assert!(texture.is_allocated);
texture.is_allocated = false;
assert_eq!(texture.last_frame_used, FrameId::INVALID);
texture.last_frame_used = current_frame_id;
}
fn clear(&mut self, pending_updates: &mut TextureUpdateList) {
@ -437,17 +491,56 @@ impl PictureTextures {
}
fn update_profile(&self, profile: &mut TransactionProfile) {
// For now, this profile counter just accumulates the slices and bytes
// from all picture cache texture arrays.
let mut picture_slices = 0;
// For now, this profile counter just accumulates the tiles and bytes
// from all picture cache textures.
let mut picture_tiles = 0;
let mut picture_bytes = 0;
for texture in &self.textures {
picture_slices += texture.slices.len();
picture_tiles += 1;
picture_bytes += texture.size_in_bytes();
}
profile.set(profiler::PICTURE_TILES, picture_slices);
profile.set(profiler::PICTURE_TILES, picture_tiles);
profile.set(profiler::PICTURE_TILES_MEM, profiler::bytes_to_mb(picture_bytes));
}
/// Simple garbage collect of picture cache tiles
fn gc(
&mut self,
pending_updates: &mut TextureUpdateList,
) {
// Allow the picture cache pool to keep 25% of the current allocated tile count
// as free textures to be reused. This ensures the allowed tile count is appropriate
// based on current window size.
let free_texture_count = self.textures.len() - self.allocated_texture_count;
let allowed_retained_count = (self.allocated_texture_count as f32 * 0.25).ceil() as usize;
let do_gc = free_texture_count > allowed_retained_count;
if do_gc {
// Sort the current pool by age, so that we remove oldest textures first
self.textures.sort_unstable_by_key(|t| cmp::Reverse(t.last_frame_used));
// We can't just use retain() because `PictureTexture` requires manual cleanup.
let mut allocated_targets = SmallVec::<[PictureTexture; 32]>::new();
let mut retained_targets = SmallVec::<[PictureTexture; 32]>::new();
for target in self.textures.drain(..) {
if target.is_allocated {
// Allocated targets can't be collected
allocated_targets.push(target);
} else if retained_targets.len() < allowed_retained_count {
// Retain the most recently used targets up to the allowed count
retained_targets.push(target);
} else {
// The rest of the targets get freed
assert_ne!(target.last_frame_used, FrameId::INVALID);
pending_updates.push_free(target.texture_id);
}
}
self.textures.extend(retained_targets);
self.textures.extend(allocated_targets);
}
}
}
/// Container struct for the various parameters used in cache allocation.
@ -669,6 +762,9 @@ impl TextureCache {
pub fn end_frame(&mut self, profile: &mut TransactionProfile) {
debug_assert!(self.now.is_valid());
self.expire_old_picture_cache_tiles();
self.picture_textures.gc(
&mut self.pending_updates,
);
let updates = &mut self.pending_updates; // To avoid referring to self in the closure.
let callback = &mut|texture_id| { updates.push_free(texture_id); };
@ -1020,9 +1116,8 @@ impl TextureCache {
// Free a cache entry from the standalone list or shared cache.
fn free(&mut self, entry: &CacheEntry) {
match entry.details {
EntryDetails::Picture { texture_index, layer_index } => {
let picture_texture = self.picture_textures.get(texture_index);
picture_texture.slices[layer_index].uv_rect_handle = None;
EntryDetails::Picture { size } => {
self.picture_textures.free_tile(entry.texture_id, self.now.frame_id());
if self.debug_flags.contains(
DebugFlags::TEXTURE_CACHE_DBG |
DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
@ -1030,9 +1125,9 @@ impl TextureCache {
self.pending_updates.push_debug_clear(
entry.texture_id,
DeviceIntPoint::zero(),
picture_texture.size.width,
picture_texture.size.height,
layer_index,
size.width,
size.height,
0,
);
}
}
@ -1347,99 +1442,6 @@ pub struct TextureParameters {
pub filter: TextureFilter,
}
/// A tracking structure for each slice in `WholeTextureArray`.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Clone, Copy, Debug)]
struct WholeTextureSlice {
uv_rect_handle: Option<GpuCacheHandle>,
}
/// A texture array that allocates whole slices and doesn't do any region tracking.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
struct WholeTextureArray {
size: DeviceIntSize,
filter: TextureFilter,
format: ImageFormat,
texture_id: CacheTextureId,
slices: Vec<WholeTextureSlice>,
has_depth: bool,
}
impl WholeTextureArray {
fn to_info(&self) -> TextureCacheAllocInfo {
TextureCacheAllocInfo {
target: ImageBufferKind::Texture2DArray,
width: self.size.width,
height: self.size.height,
format: self.format,
filter: self.filter,
layer_count: self.slices.len() as i32,
is_shared_cache: true, //TODO: reconsider
has_depth: self.has_depth,
}
}
/// Returns the number of GPU bytes consumed by this texture array.
fn size_in_bytes(&self) -> usize {
let bpp = self.format.bytes_per_pixel() as usize;
self.slices.len() * (self.size.width * self.size.height) as usize * bpp
}
/// Find an free slice.
fn find_free(&self) -> Option<LayerIndex> {
self.slices.iter().position(|slice| slice.uv_rect_handle.is_none())
}
fn cache_entry_impl(
&self,
texture_index: usize,
layer_index: usize,
now: FrameStamp,
uv_rect_handle: GpuCacheHandle,
texture_id: CacheTextureId,
) -> CacheEntry {
CacheEntry {
size: self.size,
user_data: [0.0; 3],
last_access: now,
details: EntryDetails::Picture {
texture_index,
layer_index,
},
uv_rect_handle,
input_format: self.format,
filter: self.filter,
swizzle: Swizzle::default(),
texture_id,
eviction_notice: None,
uv_rect_kind: UvRectKind::Rect,
shader: TargetShader::Default,
}
}
/// Occupy a specified slice by a cache entry.
fn occupy(
&mut self,
texture_index: usize,
layer_index: usize,
now: FrameStamp,
) -> CacheEntry {
let uv_rect_handle = GpuCacheHandle::new();
assert!(self.slices[layer_index].uv_rect_handle.is_none());
self.slices[layer_index].uv_rect_handle = Some(uv_rect_handle);
self.cache_entry_impl(
texture_index,
layer_index,
now,
uv_rect_handle,
self.texture_id,
)
}
}
impl TextureCacheUpdate {
// Constructs a TextureCacheUpdate operation to be passed to the
// rendering thread in order to do an upload to the right

View File

@ -15,7 +15,7 @@ skip_on(android,device) == filter-color-matrix.yaml filter-color-matrix-ref.yaml
== filter-invert.yaml filter-invert-ref.yaml
== filter-invert-2.yaml filter-invert-2-ref.yaml
platform(linux,mac) fuzzy(1,133) == filter-large-blur-radius.yaml filter-large-blur-radius.png
skip_on(android,device) fuzzy(1,12) fuzzy-if(platform(swgl),2,12276) == draw_calls(6) color_targets(6) alpha_targets(0) filter-small-blur-radius.yaml filter-small-blur-radius.png # fails on Pixel2
skip_on(android,device) fuzzy(1,12) fuzzy-if(platform(swgl),2,12276) == draw_calls(7) color_targets(6) alpha_targets(0) filter-small-blur-radius.yaml filter-small-blur-radius.png # fails on Pixel2
== filter-saturate-red-1.yaml filter-saturate-red-1-ref.yaml
== filter-saturate-red-2.yaml filter-saturate-red-2-ref.yaml
== filter-saturate-red-3.yaml filter-saturate-red-3-ref.yaml