Bug 1583879 - Skip uploading and rendering empty blob tiles. r=jrmuizel

This patch adds a notion of "fully transparent" image in the resource cache.
These are not uploaded in the texture cache and image requests return the
necessary information to allow the frame building code to skip emitting
primitives accordingly.

Differential Revision: https://phabricator.services.mozilla.com/D47878

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Nicolas Silva 2019-10-09 13:34:51 +00:00
parent 24ff5a9aa6
commit c1f7e63ce2
12 changed files with 315 additions and 178 deletions

View File

@ -333,25 +333,26 @@ struct Reader {
layers::BlobFont ReadBlobFont() { return Read<layers::BlobFont>(); }
};
static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
gfx::SurfaceFormat aFormat,
const mozilla::wr::DeviceIntRect* aVisibleRect,
const mozilla::wr::LayoutIntRect* aRenderRect,
const uint16_t* aTileSize,
const mozilla::wr::TileOffset* aTileOffset,
const mozilla::wr::LayoutIntRect* aDirtyRect,
Range<uint8_t> aOutput) {
static wr::BlobRenderStatus
Moz2DRenderCallback(const Range<const uint8_t> aBlob,
gfx::SurfaceFormat aFormat,
const mozilla::wr::DeviceIntRect* aVisibleRect,
const mozilla::wr::LayoutIntRect* aRenderRect,
const uint16_t* aTileSize,
const mozilla::wr::TileOffset* aTileOffset,
const mozilla::wr::LayoutIntRect* aDirtyRect,
Range<uint8_t> aOutput) {
IntSize size(aRenderRect->size.width, aRenderRect->size.height);
AUTO_PROFILER_TRACING("WebRender", "RasterizeSingleBlob", GRAPHICS);
MOZ_RELEASE_ASSERT(size.width > 0 && size.height > 0);
if (size.width <= 0 || size.height <= 0) {
return false;
return BlobRenderStatus::Error;
}
auto stride = size.width * gfx::BytesPerPixel(aFormat);
if (aOutput.length() < static_cast<size_t>(size.height * stride)) {
return false;
return BlobRenderStatus::Error;
}
// In bindings.rs we allocate a buffer filled with opaque white.
@ -362,7 +363,7 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
uninitialized);
if (!dt) {
return false;
return BlobRenderStatus::Error;
}
// We try hard to not have empty blobs but we can end up with
@ -392,7 +393,7 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
aDirtyRect->size.width, aDirtyRect->size.height));
}
bool ret = true;
wr::BlobRenderStatus status = wr::BlobRenderStatus::Empty;
size_t offset = 0;
auto absBounds = IntRectAbsolute::FromRect(bounds);
while (reader.pos < reader.len) {
@ -407,6 +408,8 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
continue;
}
status = BlobRenderStatus::Ok;
layers::WebRenderTranslator translator(dt);
Reader fontReader(aBlob.begin().get() + end, extra_end - end);
size_t count = fontReader.ReadSize();
@ -418,9 +421,10 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
}
Range<const uint8_t> blob(aBlob.begin() + offset, aBlob.begin() + end);
ret =
bool ok =
translator.TranslateRecording((char*)blob.begin().get(), blob.length());
if (!ret) {
if (!ok) {
status = BlobRenderStatus::Error;
gfxCriticalNote << "Replay failure: " << translator.GetError();
MOZ_RELEASE_ASSERT(false);
}
@ -447,7 +451,7 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
gfxUtils::WriteAsPNG(dt, filename);
#endif
return ret;
return status;
}
} // namespace wr
@ -455,14 +459,15 @@ static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
extern "C" {
bool wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob,
mozilla::wr::ImageFormat aFormat,
const mozilla::wr::LayoutIntRect* aRenderRect,
const mozilla::wr::DeviceIntRect* aVisibleRect,
const uint16_t* aTileSize,
const mozilla::wr::TileOffset* aTileOffset,
const mozilla::wr::LayoutIntRect* aDirtyRect,
mozilla::wr::MutByteSlice output) {
mozilla::wr::BlobRenderStatus
wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob,
mozilla::wr::ImageFormat aFormat,
const mozilla::wr::LayoutIntRect* aRenderRect,
const mozilla::wr::DeviceIntRect* aVisibleRect,
const uint16_t* aTileSize,
const mozilla::wr::TileOffset* aTileOffset,
const mozilla::wr::LayoutIntRect* aDirtyRect,
mozilla::wr::MutByteSlice output) {
return mozilla::wr::Moz2DRenderCallback(
mozilla::wr::ByteSliceToRange(blob),
mozilla::wr::ImageFormatToSurfaceFormat(aFormat), aVisibleRect,

View File

@ -3263,20 +3263,24 @@ pub unsafe extern "C" fn wr_dec_ref_arc(arc: *const VecU8) {
Arc::from_raw(arc);
}
// TODO: nical
// Update for the new blob image interface changes.
//
#[repr(u32)]
pub enum BlobRenderStatus {
Ok,
Empty,
Error,
}
extern "C" {
// TODO: figure out the API for tiled blob images.
pub fn wr_moz2d_render_cb(blob: ByteSlice,
format: ImageFormat,
render_rect: &LayoutIntRect,
visible_rect: &DeviceIntRect,
tile_size: Option<&u16>,
tile_offset: Option<&TileOffset>,
dirty_rect: Option<&LayoutIntRect>,
output: MutByteSlice)
-> bool;
// TODO: figure out the API for tiled blob images.
pub fn wr_moz2d_render_cb(blob: ByteSlice,
format: ImageFormat,
render_rect: &LayoutIntRect,
visible_rect: &DeviceIntRect,
tile_size: Option<&u16>,
tile_offset: Option<&TileOffset>,
dirty_rect: Option<&LayoutIntRect>,
output: MutByteSlice)
-> BlobRenderStatus;
}
#[no_mangle]

View File

@ -8,7 +8,8 @@
use webrender::api::*;
use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation, DeviceIntRect};
use bindings::{ByteSlice, MutByteSlice, wr_moz2d_render_cb, ArcVecU8, gecko_profiler_start_marker, gecko_profiler_end_marker};
use bindings::{ByteSlice, MutByteSlice, wr_moz2d_render_cb, ArcVecU8};
use bindings::{BlobRenderStatus, gecko_profiler_start_marker, gecko_profiler_end_marker};
use rayon::ThreadPool;
use rayon::prelude::*;
@ -579,7 +580,7 @@ fn rasterize_blob(job: Job) -> (BlobImageRequest, BlobImageResult) {
assert!(descriptor.rect.size.width > 0 && descriptor.rect.size.height > 0);
let result = unsafe {
if wr_moz2d_render_cb(
match wr_moz2d_render_cb(
ByteSlice::new(&job.commands[..]),
descriptor.format,
&descriptor.rect,
@ -589,18 +590,27 @@ fn rasterize_blob(job: Job) -> (BlobImageRequest, BlobImageResult) {
dirty_rect.as_ref(),
MutByteSlice::new(output.as_mut_slice()),
) {
// We want the dirty rect local to the tile rather than the whole image.
// TODO(nical): move that up and avoid recomupting the tile bounds in the callback
let dirty_rect = job.dirty_rect.to_subrect_of(&descriptor.rect);
let tx: BlobToDeviceTranslation = (-descriptor.rect.origin.to_vector()).into();
let rasterized_rect = tx.transform_rect(&dirty_rect);
BlobRenderStatus::Ok => {
// We want the dirty rect local to the tile rather than the whole image.
// TODO(nical): move that up and avoid recomputing the tile bounds in the callback
let local_dirty_rect = job.dirty_rect.to_subrect_of(&descriptor.rect);
let tx: BlobToDeviceTranslation = (-descriptor.rect.origin.to_vector()).into();
let rasterized_rect = tx.transform_rect(&local_dirty_rect);
Ok(RasterizedBlobImage {
rasterized_rect,
data: Arc::new(output),
})
} else {
panic!("Moz2D replay problem");
Ok(RasterizedBlobImage {
rasterized_rect,
data: Some(Arc::new(output)),
})
}
BlobRenderStatus::Empty => {
Ok(RasterizedBlobImage {
rasterized_rect: DeviceIntRect::zero(),
data: None,
})
}
BlobRenderStatus::Error => {
panic!("Moz2D replay problem");
}
}
};

View File

@ -106,7 +106,7 @@ fn render_blob(
}
Ok(api::RasterizedBlobImage {
data: Arc::new(texels),
data: Some(Arc::new(texels)),
rasterized_rect: size2(w, h).into(),
})
}

View File

@ -403,21 +403,26 @@ impl ClipNodeInfo {
tile_size as i32,
);
for tile in tiles {
let mut render = true;
if request_resources {
resource_cache.request_image(
render = resource_cache.request_image(
request.with_tile(tile.offset),
gpu_cache,
);
).should_render();
}
if render {
mask_tiles.push(VisibleMaskImageTile {
tile_offset: tile.offset,
tile_rect: tile.rect,
});
}
mask_tiles.push(VisibleMaskImageTile {
tile_offset: tile.offset,
tile_rect: tile.rect,
});
}
}
visible_tiles = Some(mask_tiles);
} else if request_resources {
resource_cache.request_image(request, gpu_cache);
if resource_cache.request_image(request, gpu_cache).should_skip() {
return None;
}
}
} else {
// If the supplied image key doesn't exist in the resource cache,

View File

@ -17,7 +17,7 @@ use crate::prim_store::{
PrimitiveInstanceKind, PrimitiveOpacity, PrimitiveSceneData,
PrimitiveStore, InternablePrimitive,
};
use crate::resource_cache::{ImageRequest, ResourceCache};
use crate::resource_cache::{ImageRequest, ResourceCache, ImageRequestStatus};
use crate::storage;
#[cfg_attr(feature = "capture", derive(Serialize))]
@ -260,11 +260,11 @@ impl ImageBorderData {
&mut self,
resource_cache: &mut ResourceCache,
gpu_cache: &mut GpuCache,
) {
) -> ImageRequestStatus {
resource_cache.request_image(
self.request,
gpu_cache,
);
)
}
fn write_prim_gpu_blocks(

View File

@ -24,7 +24,7 @@ use crate::render_task::{BlitSource, RenderTask};
use crate::render_task_cache::{
RenderTaskCacheEntryHandle, RenderTaskCacheKey, RenderTaskCacheKeyKind
};
use crate::resource_cache::{ImageRequest, ResourceCache};
use crate::resource_cache::{ImageRequest, ResourceCache, ImageRequestStatus};
use crate::util::pack_as_float;
#[derive(Debug)]
@ -457,11 +457,11 @@ impl YuvImageData {
&mut self,
resource_cache: &mut ResourceCache,
gpu_cache: &mut GpuCache,
) {
) -> ImageRequestStatus {
let channel_num = self.format.get_plane_num();
debug_assert!(channel_num <= 3);
for channel in 0 .. channel_num {
resource_cache.request_image(
let status = resource_cache.request_image(
ImageRequest {
key: self.yuv_key[channel],
rendering: self.image_rendering,
@ -469,7 +469,13 @@ impl YuvImageData {
},
gpu_cache,
);
if status != ImageRequestStatus::Requested {
return status;
}
}
ImageRequestStatus::Requested
}
pub fn write_prim_gpu_blocks(&self, request: &mut GpuDataRequest) {

View File

@ -2364,11 +2364,12 @@ impl PrimitiveStore {
match image_properties {
Some(ImageProperties { tiling: None, .. }) => {
frame_state.resource_cache.request_image(
if frame_state.resource_cache.request_image(
request,
frame_state.gpu_cache,
);
).should_skip() {
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
}
}
Some(ImageProperties { tiling: Some(tile_size), visible_rect, .. }) => {
image_instance.visible_tiles.clear();
@ -2433,17 +2434,17 @@ impl PrimitiveStore {
);
for tile in tiles {
frame_state.resource_cache.request_image(
if frame_state.resource_cache.request_image(
request.with_tile(tile.offset),
frame_state.gpu_cache,
);
image_instance.visible_tiles.push(VisibleImageTile {
tile_offset: tile.offset,
edge_flags: tile.edge_flags & edge_flags,
local_rect: tile.rect,
local_clip_rect: tight_clip_rect,
});
).should_render() {
image_instance.visible_tiles.push(VisibleImageTile {
tile_offset: tile.offset,
edge_flags: tile.edge_flags & edge_flags,
local_rect: tile.rect,
local_clip_rect: tight_clip_rect,
});
}
}
}
@ -2457,17 +2458,21 @@ impl PrimitiveStore {
}
PrimitiveInstanceKind::ImageBorder { data_handle, .. } => {
let prim_data = &mut frame_state.data_stores.image_border[data_handle];
prim_data.kind.request_resources(
if prim_data.kind.request_resources(
frame_state.resource_cache,
frame_state.gpu_cache,
);
).should_skip() {
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
}
}
PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
let prim_data = &mut frame_state.data_stores.yuv_image[data_handle];
prim_data.kind.request_resources(
if prim_data.kind.request_resources(
frame_state.resource_cache,
frame_state.gpu_cache,
);
).should_skip() {
prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
}
}
_ => {}
}

View File

@ -101,6 +101,8 @@ pub enum CachedImageData {
/// An image owned by the embedding, and referenced by WebRender. This may
/// take the form of a texture or a heap-allocated buffer.
External(ExternalImageData),
/// The image data is only transparent pixels, we can skip rendering it.
Empty,
}
impl From<ImageData> for CachedImageData {
@ -131,8 +133,9 @@ impl CachedImageData {
ExternalImageType::TextureHandle(_) => false,
ExternalImageType::Buffer => true,
},
CachedImageData::Blob => true,
CachedImageData::Raw(_) => true,
CachedImageData::Blob
| CachedImageData::Raw(_)
| CachedImageData::Empty => true,
}
}
}
@ -223,6 +226,7 @@ struct CachedImageInfo {
texture_cache_handle: TextureCacheHandle,
dirty_rect: ImageDirtyRect,
manual_eviction: bool,
is_fully_transparent: bool
}
impl CachedImageInfo {
@ -456,6 +460,27 @@ pub struct AsyncBlobImageInfo {
pub clear_requests: Vec<BlobImageClearParams>,
}
#[must_use]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ImageRequestStatus {
Requested,
FullyTransparent,
Error,
}
impl ImageRequestStatus {
pub fn should_render(self) -> bool {
match self {
Self::Requested => true,
_ => false,
}
}
pub fn should_skip(self) -> bool {
!self.should_render()
}
}
/// High-level container for resources managed by the `RenderBackend`.
///
/// This includes a variety of things, including images, fonts, and glyphs,
@ -549,7 +574,9 @@ impl ResourceCache {
fn should_tile(limit: i32, descriptor: &ImageDescriptor, data: &CachedImageData) -> bool {
let size_check = descriptor.size.width > limit || descriptor.size.height > limit;
match *data {
CachedImageData::Raw(_) | CachedImageData::Blob => size_check,
CachedImageData::Raw(_)
| CachedImageData::Blob
| CachedImageData::Empty => size_check,
CachedImageData::External(info) => {
// External handles already represent existing textures so it does
// not make sense to tile them into smaller ones.
@ -783,6 +810,20 @@ impl ResourceCache {
|| { RasterizedBlob::Tiled(FastHashMap::default()) }
);
if data.data.is_none() {
let entry = ensure_cached_image_entry(
&mut self.cached_images,
ImageRequest {
key: request.key.as_image(),
tile: request.tile,
rendering: ImageRendering::Auto,
},
);
// Mark the cached image as empty to skip rendering it later.
entry.is_fully_transparent = true;
}
if let Some(tile) = request.tile {
if let RasterizedBlob::NonTiled(..) = *image {
*image = RasterizedBlob::Tiled(FastHashMap::default());
@ -1110,11 +1151,13 @@ impl ResourceCache {
self.active_image_keys.insert(image_key);
}
// Returns false if the image should not be rendered, either due to an error,
// or if the image only contains fully transparent pixels.
pub fn request_image(
&mut self,
request: ImageRequest,
gpu_cache: &mut GpuCache,
) {
) -> ImageRequestStatus {
debug_assert_eq!(self.state, State::AddResources);
let template = match self.resources.image_templates.get(request.key) {
@ -1122,13 +1165,13 @@ impl ResourceCache {
None => {
warn!("ERROR: Trying to render deleted / non-existent key");
debug!("key={:?}", request.key);
return
return ImageRequestStatus::Error;
}
};
// Images that don't use the texture cache can early out.
if !template.data.uses_texture_cache() {
return;
return ImageRequestStatus::Requested;
}
let side_size =
@ -1139,76 +1182,27 @@ impl ResourceCache {
warn!("Dropping image, image:(w:{},h:{}, tile:{}) is too big for hardware!",
template.descriptor.size.width, template.descriptor.size.height, template.tiling.unwrap_or(0));
self.cached_images.insert(request.key, ImageResult::Err(ImageCacheError::OverLimitSize));
return;
return ImageRequestStatus::Error;
}
let storage = match self.cached_images.entry(request.key) {
Occupied(e) => {
// We might have an existing untiled entry, and need to insert
// a second entry. In such cases we need to move the old entry
// out first, replacing it with a dummy entry, and then creating
// the tiled/multi-entry variant.
let entry = e.into_mut();
if !request.is_untiled_auto() {
let untiled_entry = match entry {
&mut ImageResult::UntiledAuto(ref mut entry) => {
Some(mem::replace(entry, CachedImageInfo {
texture_cache_handle: TextureCacheHandle::invalid(),
dirty_rect: DirtyRect::All,
manual_eviction: false,
}))
}
_ => None
};
if let Some(untiled_entry) = untiled_entry {
let mut entries = ResourceClassCache::new();
let untiled_key = CachedImageKey {
rendering: ImageRendering::Auto,
tile: None,
};
entries.insert(untiled_key, untiled_entry);
*entry = ImageResult::Multi(entries);
}
}
entry
}
Vacant(entry) => {
entry.insert(if request.is_untiled_auto() {
ImageResult::UntiledAuto(CachedImageInfo {
texture_cache_handle: TextureCacheHandle::invalid(),
dirty_rect: DirtyRect::All,
manual_eviction: false,
})
} else {
ImageResult::Multi(ResourceClassCache::new())
})
}
};
// If this image exists in the texture cache, *and* the dirty rect
// in the cache is empty, then it is valid to use as-is.
let entry = match *storage {
ImageResult::UntiledAuto(ref mut entry) => entry,
ImageResult::Multi(ref mut entries) => {
entries.entry(request.into())
.or_insert(CachedImageInfo {
texture_cache_handle: TextureCacheHandle::invalid(),
dirty_rect: DirtyRect::All,
manual_eviction: false,
})
},
ImageResult::Err(_) => panic!("Errors should already have been handled"),
};
let entry = ensure_cached_image_entry(&mut self.cached_images, request);
let needs_upload = self.texture_cache.request(&entry.texture_cache_handle, gpu_cache);
if entry.is_fully_transparent {
// Empty tile, skip rendering.
return ImageRequestStatus::FullyTransparent;
}
// If this image exists in the texture cache, *and* the dirty rect
// in the cache is empty, then it is valid to use as-is.
if !needs_upload && entry.dirty_rect.is_empty() {
return
return ImageRequestStatus::Requested;
}
if !self.pending_image_requests.insert(request) {
return
return ImageRequestStatus::Requested;
}
// By this point, we know that the image request is considered dirty, and will
@ -1256,6 +1250,8 @@ impl ResourceCache {
);
}
}
return ImageRequestStatus::Requested;
}
pub fn create_blob_scene_builder_requests(
@ -1583,6 +1579,14 @@ impl ResourceCache {
pub fn get_cached_image(&self, request: ImageRequest) -> Result<CacheItem, ()> {
debug_assert_eq!(self.state, State::QueryResources);
let image_info = self.get_image_info(request)?;
if image_info.is_fully_transparent {
// TODO(nical): Ideally we should have discarded the primitive already.
// If a blob image was not rasterized before frame building we end up
// knowing whether its content is transparent after the culling phase
// so we have to check again here. The plan is to remove the late blob
// rasterization code, after which we can simplify this.
return Err(())
}
Ok(self.get_texture_cache_item(&image_info.texture_cache_handle))
}
@ -1620,7 +1624,9 @@ impl ResourceCache {
ExternalImageType::Buffer => None,
},
// raw and blob image are all using resource_cache.
CachedImageData::Raw(..) | CachedImageData::Blob => None,
CachedImageData::Raw(..)
| CachedImageData::Blob
| CachedImageData::Empty => None,
};
ImageProperties {
@ -1727,7 +1733,9 @@ impl ResourceCache {
let mut updates: SmallVec<[(CachedImageData, Option<DeviceIntRect>); 1]> = SmallVec::new();
match image_template.data {
CachedImageData::Raw(..) | CachedImageData::External(..) => {
CachedImageData::Raw(..)
| CachedImageData::External(..)
| CachedImageData::Empty => {
// Safe to clone here since the Raw image data is an
// Arc, and the external image data is small.
updates.push((image_template.data.clone(), None));
@ -1738,17 +1746,29 @@ impl ResourceCache {
match (blob_image, request.tile) {
(RasterizedBlob::Tiled(ref tiles), Some(tile)) => {
let img = &tiles[&tile];
updates.push((
CachedImageData::Raw(Arc::clone(&img.data)),
Some(img.rasterized_rect)
));
updates.push(
if let Some(ref data) = img.data {
(
CachedImageData::Raw(Arc::clone(data)),
Some(img.rasterized_rect)
)
} else {
(CachedImageData::Empty, None)
}
);
}
(RasterizedBlob::NonTiled(ref mut queue), None) => {
for img in queue.drain(..) {
updates.push((
CachedImageData::Raw(img.data),
Some(img.rasterized_rect)
));
updates.push(
if let Some(data) = img.data {
(
CachedImageData::Raw(data),
Some(img.rasterized_rect)
)
} else {
(CachedImageData::Empty, None)
}
);
}
}
_ => {
@ -1926,7 +1946,9 @@ impl ResourceCache {
for (_, image) in self.resources.image_templates.images.iter() {
report.images += match image.data {
CachedImageData::Raw(ref v) => unsafe { op(v.as_ptr() as *const c_void) },
CachedImageData::Blob | CachedImageData::External(..) => 0,
CachedImageData::Blob
| CachedImageData::External(..)
| CachedImageData::Empty => 0,
}
}
@ -1971,6 +1993,72 @@ impl Drop for ResourceCache {
}
}
fn ensure_cached_image_entry(cached_images: &mut ImageCache, request: ImageRequest) -> &mut CachedImageInfo {
let storage = match cached_images.entry(request.key) {
Occupied(e) => {
// We might have an existing untiled entry, and need to insert
// a second entry. In such cases we need to move the old entry
// out first, replacing it with a dummy entry, and then creating
// the tiled/multi-entry variant.
let entry = e.into_mut();
if !request.is_untiled_auto() {
let untiled_entry = match entry {
&mut ImageResult::UntiledAuto(ref mut entry) => {
Some(mem::replace(entry, CachedImageInfo {
texture_cache_handle: TextureCacheHandle::invalid(),
dirty_rect: DirtyRect::All,
manual_eviction: false,
is_fully_transparent: false,
}))
}
_ => None
};
if let Some(untiled_entry) = untiled_entry {
let mut entries = ResourceClassCache::new();
let untiled_key = CachedImageKey {
rendering: ImageRendering::Auto,
tile: None,
};
entries.insert(untiled_key, untiled_entry);
*entry = ImageResult::Multi(entries);
}
}
entry
}
Vacant(entry) => {
entry.insert(
if request.is_untiled_auto() {
ImageResult::UntiledAuto(CachedImageInfo {
texture_cache_handle: TextureCacheHandle::invalid(),
dirty_rect: DirtyRect::All,
manual_eviction: false,
is_fully_transparent: false,
})
} else {
ImageResult::Multi(ResourceClassCache::new())
}
)
}
};
match *storage {
ImageResult::UntiledAuto(ref mut entry) => entry,
ImageResult::Multi(ref mut entries) => {
entries.entry(request.into())
.or_insert(CachedImageInfo {
texture_cache_handle: TextureCacheHandle::invalid(),
dirty_rect: DirtyRect::All,
manual_eviction: false,
is_fully_transparent: false,
})
},
ImageResult::Err(_) => panic!("Errors should already have been handled"),
}
}
pub fn get_blob_tiling(
tiling: Option<TileSize>,
size: DeviceIntSize,
@ -2153,7 +2241,7 @@ impl ResourceCache {
warn!("Tiled blob images aren't supported yet");
RasterizedBlobImage {
rasterized_rect: desc.size.into(),
data: Arc::new(vec![0; desc.compute_total_size() as usize])
data: None,
}
} else {
let blob_handler = self.blob_image_handler.as_mut().unwrap();
@ -2163,25 +2251,26 @@ impl ResourceCache {
result.expect("Blob rasterization failed")
};
assert_eq!(result.rasterized_rect.size, desc.size);
assert_eq!(result.data.len(), desc.compute_total_size() as usize);
num_blobs += 1;
#[cfg(feature = "png")]
CaptureConfig::save_png(
root.join(format!("blobs/{}.png", num_blobs)),
desc.size,
desc.format,
&result.data,
);
let file_name = format!("{}.raw", num_blobs);
let short_path = format!("blobs/{}", file_name);
let full_path = path_blobs.clone().join(&file_name);
fs::File::create(full_path)
.expect(&format!("Unable to create {}", short_path))
.write_all(&result.data)
.unwrap();
other_paths.insert(key, short_path);
assert_eq!(result.rasterized_rect.size, desc.size);
if let Some(ref data) = result.data {
assert_eq!(data.len(), desc.compute_total_size() as usize);
#[cfg(feature = "png")]
CaptureConfig::save_png(
root.join(format!("blobs/{}.png", num_blobs)),
desc.size,
desc.format,
data,
);
let file_name = format!("{}.raw", num_blobs);
let short_path = format!("blobs/{}", file_name);
let full_path = path_blobs.clone().join(&file_name);
fs::File::create(full_path)
.expect(&format!("Unable to create {}", short_path))
.write_all(data)
.unwrap();
other_paths.insert(key, short_path);
}
}
CachedImageData::External(ref ext) => {
let short_path = format!("externals/{}", external_images.len() + 1);
@ -2192,6 +2281,7 @@ impl ResourceCache {
external: ext.clone(),
});
}
CachedImageData::Empty => {}
}
}

View File

@ -913,6 +913,12 @@ impl TextureCache {
) {
debug_assert!(self.now.is_valid());
if let Some(CachedImageData::Empty) = data {
self.mark_unused(handle);
*handle = TextureCacheHandle::invalid();
return;
}
// Determine if we need to allocate texture cache memory
// for this item. We need to reallocate if any of the following
// is true:
@ -1846,6 +1852,9 @@ impl TextureCacheUpdate {
CachedImageData::Blob => {
panic!("The vector image should have been rasterized.");
}
CachedImageData::Empty => {
panic!("The entry should have been cleared instead.");
}
CachedImageData::External(ext_image) => match ext_image.image_type {
ExternalImageType::TextureHandle(_) => {
panic!("External texture handle should not go through texture_cache.");

View File

@ -490,7 +490,10 @@ pub struct RasterizedBlobImage {
/// image or tile.
pub rasterized_rect: DeviceIntRect,
/// Backing store. The format is stored out of band in `BlobImageDescriptor`.
pub data: Arc<Vec<u8>>,
///
/// None means the image only has fully transparent content. We can skip storing
/// and rendering it.
pub data: Option<Arc<Vec<u8>>>,
}
/// Error code for when blob rasterization failed.

View File

@ -98,7 +98,7 @@ fn render_blob(
}
Ok(RasterizedBlobImage {
data: Arc::new(texels),
data: Some(Arc::new(texels)),
rasterized_rect,
})
}