Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically grow the texture cache #3282

Merged
merged 5 commits into from Nov 7, 2018
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Overhaul texture cache command pipeline to prepare for reallocations.

A big part of this is handling coalescing, so that if we end up creating
several new regions in a given frame, we won't allocate/blit/free
textures unnecessarily.

Differential Revision: https://phabricator.services.mozilla.com/D10853
  • Loading branch information
bholley committed Nov 6, 2018
commit ab72ef40cf6b2cd62735aac3b1049bdf52ce8f9a
@@ -1557,7 +1557,9 @@ impl Device {
debug_assert!(self.inside_frame);
debug_assert!(dst.width >= src.width);
debug_assert!(dst.height >= src.height);
debug_assert!(dst.layer_count >= src.layer_count);

// Note that zip() truncates to the shorter of the two iterators.
let rect = DeviceIntRect::new(DeviceIntPoint::zero(), src.get_dimensions().to_i32());
for (read_fbo, draw_fbo) in src.fbos.iter().zip(&dst.fbos) {
self.bind_read_target_impl(*read_fbo);
@@ -110,48 +110,138 @@ pub enum TextureUpdateSource {
Bytes { data: Arc<Vec<u8>> },
}

/// Command to allocate, reallocate, or free a texture for the texture cache.
#[derive(Debug)]
pub enum TextureUpdateOp {
Create {
width: u32,
height: u32,
format: ImageFormat,
filter: TextureFilter,
render_target: Option<RenderTargetInfo>,
layer_count: i32,
},
Update {
rect: DeviceUintRect,
stride: Option<u32>,
offset: u32,
layer_index: i32,
source: TextureUpdateSource,
},
pub struct TextureCacheAllocation {
/// The virtual ID (i.e. distinct from device ID) of the texture.
pub id: CacheTextureId,
/// Details corresponding to the operation in question.
pub kind: TextureCacheAllocationKind,
}

/// Information used when allocating / reallocating.
#[derive(Debug)]
pub struct TextureCacheAllocInfo {
pub width: u32,
pub height: u32,
pub layer_count: i32,
pub format: ImageFormat,
pub filter: TextureFilter,
}

/// Sub-operation-specific information for allocation operations.
#[derive(Debug)]
pub enum TextureCacheAllocationKind {
/// Performs an initial texture allocation.
Alloc(TextureCacheAllocInfo),
/// Reallocates the texture. The existing live texture with the same id
/// will be deallocated and its contents blitted over. The new size must
/// be greater than the old size.
Realloc(TextureCacheAllocInfo),
/// Frees the texture and the corresponding cache ID.
Free,
}

/// Command to update the contents of the texture cache.
#[derive(Debug)]
pub struct TextureUpdate {
pub struct TextureCacheUpdate {
pub id: CacheTextureId,
pub op: TextureUpdateOp,
pub rect: DeviceUintRect,
pub stride: Option<u32>,
pub offset: u32,
pub layer_index: i32,
pub source: TextureUpdateSource,
}

/// Atomic set of commands to manipulate the texture cache, generated on the
/// RenderBackend thread and executed on the Renderer thread.
///
/// The list of allocation operations is processed before the updates. This is
/// important to allow coalescing of certain allocation operations.
#[derive(Default)]
pub struct TextureUpdateList {
pub updates: Vec<TextureUpdate>,
/// Commands to alloc/realloc/free the textures. Processed first.
pub allocations: Vec<TextureCacheAllocation>,
/// Commands to update the contents of the textures. Processed second.
pub updates: Vec<TextureCacheUpdate>,
}

impl TextureUpdateList {
/// Mints a new `TextureUpdateList`.
pub fn new() -> Self {
TextureUpdateList {
allocations: Vec::new(),
updates: Vec::new(),
}
}

/// Pushes an update operation onto the list.
#[inline]
pub fn push(&mut self, update: TextureUpdate) {
pub fn push_update(&mut self, update: TextureCacheUpdate) {
self.updates.push(update);
}

/// Pushes an allocation operation onto the list.
pub fn push_alloc(&mut self, id: CacheTextureId, info: TextureCacheAllocInfo) {
debug_assert!(!self.allocations.iter().any(|x| x.id == id));
self.allocations.push(TextureCacheAllocation {
id,
kind: TextureCacheAllocationKind::Alloc(info),
});
}

/// Pushes a reallocation operation onto the list, potentially coalescing
/// with previous operations.
pub fn push_realloc(&mut self, id: CacheTextureId, info: TextureCacheAllocInfo) {
self.debug_assert_coalesced(id);

// Coallesce this realloc into a previous alloc or realloc, if available.
if let Some(cur) = self.allocations.iter_mut().find(|x| x.id == id) {
match cur.kind {
TextureCacheAllocationKind::Alloc(ref mut i) => *i = info,
TextureCacheAllocationKind::Realloc(ref mut i) => *i = info,
TextureCacheAllocationKind::Free => panic!("Reallocating freed texture"),
}

return;
}

self.allocations.push(TextureCacheAllocation {
id,
kind: TextureCacheAllocationKind::Realloc(info),
});
}

/// Pushes a free operation onto the list, potentially coalescing with
/// previous operations.
pub fn push_free(&mut self, id: CacheTextureId) {
self.debug_assert_coalesced(id);

// Drop any unapplied updates to the to-be-freed texture.
self.updates.retain(|x| x.id != id);

// Drop any allocations for it as well. If we happen to be allocating and
// freeing in the same batch, we can collapse them to a no-op.
let idx = self.allocations.iter().position(|x| x.id == id);
let removed_kind = idx.map(|i| self.allocations.remove(i).kind);
match removed_kind {
Some(TextureCacheAllocationKind::Alloc(..)) => { /* no-op! */ },
Some(TextureCacheAllocationKind::Free) => panic!("Double free"),
Some(TextureCacheAllocationKind::Realloc(..)) | None => {
self.allocations.push(TextureCacheAllocation {
id,
kind: TextureCacheAllocationKind::Free,
});
}
};
}

fn debug_assert_coalesced(&self, id: CacheTextureId) {
debug_assert!(
self.allocations.iter().filter(|x| x.id == id).count() <= 1,
"Allocations should have been coalesced",
);
}
}

/// Wraps a tiling::Frame, but conceptually could hold more information
@@ -53,8 +53,8 @@ use gpu_cache::GpuDebugChunk;
use gpu_glyph_renderer::GpuGlyphRenderer;
use gpu_types::ScalingInstance;
use internal_types::{TextureSource, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
use internal_types::{CacheTextureId, DebugOutput, FastHashMap, RenderedDocument, ResultMsg};
use internal_types::{LayerIndex, TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
use internal_types::{CacheTextureId, DebugOutput, FastHashMap, LayerIndex, RenderedDocument, ResultMsg};
use internal_types::{TextureCacheAllocationKind, TextureCacheUpdate, TextureUpdateList, TextureUpdateSource};
use internal_types::{RenderTargetInfo, SavedTargetIndex};
use prim_store::DeferredResolve;
use profiler::{BackendProfileCounters, FrameProfileCounters,
@@ -2800,90 +2800,92 @@ impl Renderer {
let mut pending_texture_updates = mem::replace(&mut self.pending_texture_updates, vec![]);

for update_list in pending_texture_updates.drain(..) {
for update in update_list.updates {
match update.op {
TextureUpdateOp::Create {
width,
height,
layer_count,
format,
filter,
render_target,
} => {
for allocation in update_list.allocations {
let is_realloc = matches!(allocation.kind, TextureCacheAllocationKind::Realloc(..));
match allocation.kind {
TextureCacheAllocationKind::Alloc(info) |
TextureCacheAllocationKind::Realloc(info) => {
// Create a new native texture, as requested by the texture cache.
//
// Ensure no PBO is bound when creating the texture storage,
// or GL will attempt to read data from there.
let texture = self.device.create_texture(
TextureTarget::Array,
format,
width,
height,
filter,
render_target,
layer_count,
);
self.texture_resolver.texture_cache_map.insert(update.id, texture);
}
TextureUpdateOp::Update {
rect,
source,
stride,
layer_index,
offset,
} => {
let texture = &self.texture_resolver.texture_cache_map[&update.id];
let mut uploader = self.device.upload_texture(
texture,
&self.texture_cache_upload_pbo,
0,
info.format,
info.width,
info.height,
info.filter,
// This needs to be a render target because some render
// tasks get rendered into the texture cache.
Some(RenderTargetInfo { has_depth: false }),
info.layer_count,
);

let bytes_uploaded = match source {
TextureUpdateSource::Bytes { data } => {
let old = self.texture_resolver.texture_cache_map.insert(allocation.id, texture);
assert_eq!(old.is_some(), is_realloc, "Renderer and RenderBackend disagree");
if let Some(old) = old {
self.device.blit_renderable_texture(
self.texture_resolver.texture_cache_map.get_mut(&allocation.id).unwrap(),
&old
);
self.device.delete_texture(old);
}
},
TextureCacheAllocationKind::Free => {
let texture = self.texture_resolver.texture_cache_map.remove(&allocation.id).unwrap();
self.device.delete_texture(texture);
},
}
}

for update in update_list.updates {
let TextureCacheUpdate { id, rect, stride, offset, layer_index, source } = update;
let texture = &self.texture_resolver.texture_cache_map[&id];
let mut uploader = self.device.upload_texture(
texture,
&self.texture_cache_upload_pbo,
0,
);

let bytes_uploaded = match source {
TextureUpdateSource::Bytes { data } => {
uploader.upload(
rect, layer_index, stride,
&data[offset as usize ..],
)
}
TextureUpdateSource::External { id, channel_index } => {
let handler = self.external_image_handler
.as_mut()
.expect("Found external image, but no handler set!");
// The filter is only relevant for NativeTexture external images.
let size = match handler.lock(id, channel_index, ImageRendering::Auto).source {
ExternalImageSource::RawData(data) => {
uploader.upload(
rect, layer_index, stride,
&data[offset as usize ..],
)
}
TextureUpdateSource::External { id, channel_index } => {
let handler = self.external_image_handler
.as_mut()
.expect("Found external image, but no handler set!");
// The filter is only relevant for NativeTexture external images.
let size = match handler.lock(id, channel_index, ImageRendering::Auto).source {
ExternalImageSource::RawData(data) => {
uploader.upload(
rect, layer_index, stride,
&data[offset as usize ..],
)
}
ExternalImageSource::Invalid => {
// Create a local buffer to fill the pbo.
let bpp = texture.get_format().bytes_per_pixel();
let width = stride.unwrap_or(rect.size.width * bpp);
let total_size = width * rect.size.height;
// WR haven't support RGBAF32 format in texture_cache, so
// we use u8 type here.
let dummy_data: Vec<u8> = vec![255; total_size as usize];
uploader.upload(rect, layer_index, stride, &dummy_data)
}
ExternalImageSource::NativeTexture(eid) => {
panic!("Unexpected external texture {:?} for the texture cache update of {:?}", eid, id);
}
};
handler.unlock(id, channel_index);
size
ExternalImageSource::Invalid => {
// Create a local buffer to fill the pbo.
let bpp = texture.get_format().bytes_per_pixel();
let width = stride.unwrap_or(rect.size.width * bpp);
let total_size = width * rect.size.height;
// WR haven't support RGBAF32 format in texture_cache, so
// we use u8 type here.
let dummy_data: Vec<u8> = vec![255; total_size as usize];
uploader.upload(rect, layer_index, stride, &dummy_data)
}
ExternalImageSource::NativeTexture(eid) => {
panic!("Unexpected external texture {:?} for the texture cache update of {:?}", eid, id);
}
};

self.profile_counters.texture_data_uploaded.add(bytes_uploaded >> 10);
}
TextureUpdateOp::Free => {
let texture = self.texture_resolver.texture_cache_map.remove(&update.id).unwrap();
self.device.delete_texture(texture);
handler.unlock(id, channel_index);
size
}
}
};

self.profile_counters.texture_data_uploaded.add(bytes_uploaded >> 10);
}
}

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.