☠☠ backed out by d19d644cb240 ☠ ☠ | |
author | Nicolas Silva <nsilva@mozilla.com> |
Wed, 09 Oct 2019 13:34:51 +0000 | |
changeset 497270 | 42d3ccf69629e077b151538a9aad733f82a52a0c |
parent 497269 | 4ec5e79bb310b77bfd1282aebfe9f81b68067751 |
child 497271 | 0046bcf5b1ffbe34e42b2c376cd4a9839ff7e165 |
push id | 36681 |
push user | cbrindusan@mozilla.com |
push date | Fri, 11 Oct 2019 21:50:12 +0000 |
treeherder | mozilla-central@c5e6477c3a24 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jrmuizel |
bugs | 1583879 |
milestone | 71.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/gfx/webrender_bindings/Moz2DImageRenderer.cpp +++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp @@ -328,46 +328,47 @@ struct Reader { size_t ReadSize() { return Read<size_t>(); } int ReadInt() { return Read<int>(); } IntRectAbsolute ReadBounds() { return Read<IntRectAbsolute>(); } 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. bool uninitialized = false; RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateDrawTargetForData( gfx::BackendType::SKIA, aOutput.begin().get(), size, stride, aFormat, uninitialized); if (!dt) { - return false; + return BlobRenderStatus::Error; } // We try hard to not have empty blobs but we can end up with // them because of CompositorHitTestInfo and merging. size_t footerSize = sizeof(size_t); MOZ_RELEASE_ASSERT(aBlob.length() >= footerSize); size_t indexOffset = ConvertFromBytes<size_t>(aBlob.end().get() - footerSize); @@ -387,45 +388,48 @@ static bool Moz2DRenderCallback(const Ra gfx::Rect dirty(aDirtyRect->origin.x, aDirtyRect->origin.y, aDirtyRect->size.width, aDirtyRect->size.height); dt->PushClipRect(dirty); bounds = bounds.Intersect( IntRect(aDirtyRect->origin.x, aDirtyRect->origin.y, 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) { size_t end = reader.ReadSize(); size_t extra_end = reader.ReadSize(); MOZ_RELEASE_ASSERT(extra_end >= end); MOZ_RELEASE_ASSERT(extra_end < aBlob.length()); auto combinedBounds = absBounds.Intersect(reader.ReadBounds()); if (combinedBounds.IsEmpty()) { offset = extra_end; continue; } + status = BlobRenderStatus::Ok; + layers::WebRenderTranslator translator(dt); Reader fontReader(aBlob.begin().get() + end, extra_end - end); size_t count = fontReader.ReadSize(); for (size_t i = 0; i < count; i++) { layers::BlobFont blobFont = fontReader.ReadBlobFont(); RefPtr<ScaledFont> scaledFont = GetScaledFont(&translator, blobFont.mFontInstanceKey); translator.AddScaledFont(blobFont.mScaledFontPtr, scaledFont); } 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); } offset = extra_end; } if (StaticPrefs::gfx_webrender_blob_paint_flashing()) { dt->SetTransform(gfx::Matrix()); @@ -442,32 +446,33 @@ static bool Moz2DRenderCallback(const Ra #if 0 static int i = 0; char filename[40]; sprintf(filename, "out%d.png", i++); gfxUtils::WriteAsPNG(dt, filename); #endif - return ret; + return status; } } // namespace wr } // namespace mozilla 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, aRenderRect, aTileSize, aTileOffset, aDirtyRect, mozilla::wr::MutByteSliceToRange(output)); } } // extern
--- a/gfx/webrender_bindings/src/bindings.rs +++ b/gfx/webrender_bindings/src/bindings.rs @@ -3258,30 +3258,34 @@ pub extern "C" fn wr_add_ref_arc(arc: &A Arc::into_raw(arc.clone()) } #[no_mangle] 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] pub extern "C" fn wr_root_scroll_node_id() -> WrSpatialId { // The PipelineId doesn't matter here, since we just want the numeric part of the id // produced for any given root reference frame. WrSpatialId { id: SpatialId::root_scroll_node(PipelineId(0, 0)).0 } }
--- a/gfx/webrender_bindings/src/moz2d_renderer.rs +++ b/gfx/webrender_bindings/src/moz2d_renderer.rs @@ -3,17 +3,18 @@ //! Provides the webrender-side implementation of gecko blob images. //! //! Pretty much this is just a shim that calls back into Moz2DImageRenderer, but //! it also handles merging "partial" blob images (see `merge_blob_images`) and //! registering fonts found in the blob (see `prepare_request`). 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::*; use std::collections::hash_map::HashMap; use std::collections::hash_map; use std::collections::btree_map::BTreeMap; use std::collections::Bound::Included; use std::mem; @@ -574,38 +575,47 @@ fn rasterize_blob(job: Job) -> (BlobImag let dirty_rect = match job.dirty_rect { DirtyRect::Partial(rect) => Some(rect), DirtyRect::All => None, }; 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, &job.visible_rect, job.tile_size.as_ref(), job.request.tile.as_ref(), 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"); + } } }; (job.request, result) } impl BlobImageHandler for Moz2dBlobImageHandler { fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect, tile_size: Option<TileSize>) {
--- a/gfx/wr/examples/blob.rs +++ b/gfx/wr/examples/blob.rs @@ -101,17 +101,17 @@ fn render_blob( format!("Unsupported image format"), )); } } } } Ok(api::RasterizedBlobImage { - data: Arc::new(texels), + data: Some(Arc::new(texels)), rasterized_rect: size2(w, h).into(), }) } struct CheckerboardRenderer { // We are going to defer the rendering work to worker threads. // Using a pre-built Arc<ThreadPool> rather than creating our own threads // makes it possible to share the same thread pool as the glyph renderer (if we
--- a/gfx/wr/webrender/src/clip.rs +++ b/gfx/wr/webrender/src/clip.rs @@ -398,31 +398,36 @@ impl ClipNodeInfo { }; let tiles = image::tiles( &layout_image_rect, &visible_rect, &props.visible_rect, 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(); } - mask_tiles.push(VisibleMaskImageTile { - tile_offset: tile.offset, - tile_rect: tile.rect, - }); + if render { + 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, // skip the clip node since there is nothing to mask with. warn!("Clip mask with missing image key {:?}", request.key); return None; } }
--- a/gfx/wr/webrender/src/prim_store/borders.rs +++ b/gfx/wr/webrender/src/prim_store/borders.rs @@ -12,17 +12,17 @@ use crate::gpu_cache::{GpuCache, GpuData use crate::intern; use crate::internal_types::LayoutPrimitiveInfo; use crate::prim_store::{ BorderSegmentInfo, BrushSegment, NinePatchDescriptor, PrimKey, PrimKeyCommonData, PrimTemplate, PrimTemplateCommonData, 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))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)] pub struct NormalBorderPrim { pub border: NormalBorderAu, pub widths: LayoutSideOffsetsAu, @@ -255,21 +255,21 @@ impl ImageBorderData { PrimitiveOpacity::opaque() } } pub fn request_resources( &mut self, resource_cache: &mut ResourceCache, gpu_cache: &mut GpuCache, - ) { + ) -> ImageRequestStatus { resource_cache.request_image( self.request, gpu_cache, - ); + ) } fn write_prim_gpu_blocks( &self, request: &mut GpuDataRequest, prim_size: &LayoutSize, ) { // Border primitives currently used for
--- a/gfx/wr/webrender/src/prim_store/image.rs +++ b/gfx/wr/webrender/src/prim_store/image.rs @@ -19,17 +19,17 @@ use crate::prim_store::{ PrimTemplate, PrimTemplateCommonData, PrimitiveStore, SegmentInstanceIndex, SizeKey, InternablePrimitive, }; use crate::render_target::RenderTargetKind; 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)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct VisibleImageTile { pub tile_offset: TileOffset, pub edge_flags: EdgeAaSegmentMask, @@ -452,29 +452,35 @@ impl YuvImageData { // YUV images never have transparency common.opacity = PrimitiveOpacity::opaque(); } pub fn request_resources( &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, tile: None, }, gpu_cache, ); + + if status != ImageRequestStatus::Requested { + return status; + } } + + ImageRequestStatus::Requested } pub fn write_prim_gpu_blocks(&self, request: &mut GpuDataRequest) { request.push([ self.color_depth.rescaling_factor(), pack_as_float(self.color_space as u32), pack_as_float(self.format as u32), 0.0
--- a/gfx/wr/webrender/src/prim_store/mod.rs +++ b/gfx/wr/webrender/src/prim_store/mod.rs @@ -2359,21 +2359,22 @@ impl PrimitiveStore { let request = ImageRequest { key: image_data.key, rendering: image_data.image_rendering, tile: None, }; 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(); // TODO: rename the blob's visible_rect into something that doesn't conflict // with the terminology we use during culling since it's not really the same // thing. let active_rect = visible_rect; @@ -2428,51 +2429,55 @@ impl PrimitiveStore { let tiles = crate::image::tiles( &layout_image_rect, &visible_rect, &active_rect, tile_size as i32, ); 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, + }); + } } } if image_instance.visible_tiles.is_empty() { // Mark as invisible prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID; } } None => {} } } 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; + } } _ => {} } } pub fn get_opacity_binding( &self, opacity_binding_index: OpacityBindingIndex,
--- a/gfx/wr/webrender/src/resource_cache.rs +++ b/gfx/wr/webrender/src/resource_cache.rs @@ -96,16 +96,18 @@ pub enum CachedImageData { /// An series of commands that can be rasterized into an image via an /// embedding-provided callback. /// /// The commands are stored elsewhere and this variant is used as a placeholder. Blob, /// 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 { fn from(img_data: ImageData) -> Self { match img_data { ImageData::Raw(data) => CachedImageData::Raw(data), ImageData::External(data) => CachedImageData::External(data), } @@ -126,18 +128,19 @@ impl CachedImageData { /// cache. #[inline] pub fn uses_texture_cache(&self) -> bool { match *self { CachedImageData::External(ref ext_data) => match ext_data.image_type { ExternalImageType::TextureHandle(_) => false, ExternalImageType::Buffer => true, }, - CachedImageData::Blob => true, - CachedImageData::Raw(_) => true, + CachedImageData::Blob + | CachedImageData::Raw(_) + | CachedImageData::Empty => true, } } } #[derive(Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct ImageProperties { @@ -218,16 +221,17 @@ impl ImageTemplates { } #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] struct CachedImageInfo { texture_cache_handle: TextureCacheHandle, dirty_rect: ImageDirtyRect, manual_eviction: bool, + is_fully_transparent: bool } impl CachedImageInfo { fn mark_unused(&mut self, texture_cache: &mut TextureCache) { texture_cache.mark_unused(&self.texture_cache_handle); self.manual_eviction = false; } } @@ -451,16 +455,37 @@ pub struct BlobImageClearParams { /// Information attached to AsyncBlobImageRasterizer. #[derive(Clone, Debug)] pub struct AsyncBlobImageInfo { pub epoch: BlobImageRasterizerEpoch, 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, /// which may be stored as memory buffers, GPU textures, or handles to resources /// managed by the OS or other parts of WebRender. pub struct ResourceCache { cached_glyphs: GlyphCache, cached_images: ImageCache, @@ -544,17 +569,19 @@ impl ResourceCache { pub fn max_texture_size(&self) -> i32 { self.texture_cache.max_texture_size() } 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. info.image_type == ExternalImageType::Buffer && size_check } } } @@ -778,16 +805,30 @@ impl ResourceCache { texture_cache_profile.rasterized_blob_pixels.inc(data.rasterized_rect.area() as usize); // First make sure we have an entry for this key (using a placeholder // if need be). let image = self.rasterized_blob_images.entry(request.key).or_insert_with( || { 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()); } if let RasterizedBlob::Tiled(ref mut tiles) = *image { tiles.insert(tile, data); } @@ -1105,115 +1146,68 @@ impl ResourceCache { pub fn set_image_active( &mut self, image_key: ImageKey, ) { 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) { Some(template) => template, 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 = template.tiling.map_or(cmp::max(template.descriptor.size.width, template.descriptor.size.height), |tile_size| tile_size as i32); if side_size > self.texture_cache.max_texture_size() { // The image or tiling size is too big for hardware texture size. 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 - }; + 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 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 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. - 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 needs_upload = self.texture_cache.request(&entry.texture_cache_handle, gpu_cache); - 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 // require a texture cache modification. self.dirty_image_keys.insert(request.key); if template.data.is_blob() { let request: BlobImageRequest = request.into(); @@ -1251,16 +1245,18 @@ impl ResourceCache { BlobImageParams { request, descriptor, dirty_rect: DirtyRect::All, } ); } } + + return ImageRequestStatus::Requested; } pub fn create_blob_scene_builder_requests( &mut self, keys: &[BlobImageKey] ) -> (Option<(Box<dyn AsyncBlobImageRasterizer>, AsyncBlobImageInfo)>, Vec<BlobImageParams>) { if self.blob_image_handler.is_none() || keys.is_empty() { return (None, Vec::new()); @@ -1578,16 +1574,24 @@ impl ResourceCache { pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> { self.glyph_rasterizer.get_glyph_index(font_key, ch) } #[inline] 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)) } pub fn get_cached_render_task( &self, handle: &RenderTaskCacheEntryHandle, ) -> &RenderTaskCacheEntry { self.cached_render_tasks.get_cache_entry(handle) @@ -1615,17 +1619,19 @@ impl ResourceCache { image_template.map(|image_template| { let external_image = match image_template.data { CachedImageData::External(ext_image) => match ext_image.image_type { ExternalImageType::TextureHandle(_) => Some(ext_image), // external buffer uses resource_cache. 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 { descriptor: image_template.descriptor, external_image, tiling: image_template.tiling, visible_rect: image_template.visible_rect, } @@ -1722,38 +1728,52 @@ impl ResourceCache { fn update_texture_cache(&mut self, gpu_cache: &mut GpuCache) { for request in self.pending_image_requests.drain() { let image_template = self.resources.image_templates.get_mut(request.key).unwrap(); debug_assert!(image_template.data.uses_texture_cache()); 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)); } CachedImageData::Blob => { let blob_image = self.rasterized_blob_images.get_mut(&BlobImageKey(request.key)).unwrap(); 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) + } + ); } } _ => { debug_assert!(false, "invalid blob image request during frame building"); continue; } } } @@ -1921,17 +1941,19 @@ impl ResourceCache { report.fonts += unsafe { op(raw.as_ptr() as *const c_void) }; } } // Measure images. 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, } } // Mesure rasterized blobs. // TODO(gw): Temporarily disabled while we roll back a crash. We can re-enable // these when that crash is fixed. /* for (_, image) in self.rasterized_blob_images.iter() { @@ -1966,16 +1988,82 @@ impl ResourceCache { } impl Drop for ResourceCache { fn drop(&mut self) { self.clear_images(|_| true); } } + +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, max_texture_size: i32, ) -> Option<TileSize> { if tiling.is_none() && (size.width > max_texture_size || size.height > max_texture_size) { @@ -2148,55 +2236,57 @@ impl ResourceCache { Some(tile_size) => desc.size.width > tile_size as i32 || desc.size.height > tile_size as i32, None => false, }; let result = if requires_tiling { 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(); blob_handler.prepare_resources(&self.resources, blob_request_params); let mut rasterizer = blob_handler.create_blob_rasterizer(); let (_, result) = rasterizer.rasterize(blob_request_params, false).pop().unwrap(); result.expect("Blob rasterization failed") }; + num_blobs += 1; 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); + 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); other_paths.insert(key, short_path.clone()); external_images.push(ExternalCaptureImage { short_path, descriptor: desc.clone(), external: ext.clone(), }); } + CachedImageData::Empty => {} } } let resources = PlainResources { font_templates: res.font_templates .iter() .map(|(key, template)| { (*key, match *template {
--- a/gfx/wr/webrender/src/texture_cache.rs +++ b/gfx/wr/webrender/src/texture_cache.rs @@ -908,16 +908,22 @@ impl TextureCache { mut dirty_rect: ImageDirtyRect, gpu_cache: &mut GpuCache, eviction_notice: Option<&EvictionNotice>, uv_rect_kind: UvRectKind, eviction: Eviction, ) { 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: // - Never been in the cache // - Has been in the cache but was evicted. // - Exists in the cache but dimensions / format have changed. let realloc = match self.entries.get_opt(handle) { Some(entry) => { @@ -1841,16 +1847,19 @@ impl TextureCacheUpdate { layer_index: i32, use_upload_format: bool, dirty_rect: &ImageDirtyRect, ) -> TextureCacheUpdate { let source = match data { 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."); } ExternalImageType::Buffer => TextureUpdateSource::External { id: ext_image.id, channel_index: ext_image.channel_index, },
--- a/gfx/wr/webrender_api/src/image.rs +++ b/gfx/wr/webrender_api/src/image.rs @@ -485,17 +485,20 @@ pub struct BlobImageDescriptor { /// Representation of a rasterized blob image. This is obtained by passing /// `BlobImageData` to the embedding via the rasterization callback. pub struct RasterizedBlobImage { /// The rectangle that was rasterized in device pixels, relative to the /// 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. #[derive(Clone, Debug)] pub enum BlobImageError { /// Out of memory. Oom, /// Other failure, embedding-specified.
--- a/gfx/wr/wrench/src/blob.rs +++ b/gfx/wr/wrench/src/blob.rs @@ -93,17 +93,17 @@ fn render_blob( format!("Unsupported image format {:?}", descriptor.format), )); } } } } Ok(RasterizedBlobImage { - data: Arc::new(texels), + data: Some(Arc::new(texels)), rasterized_rect, }) } /// See rawtest.rs. We use this to test that blob images are requested the right /// amount of times. pub struct BlobCallbacks { pub request: Box<dyn Fn(&[BlobImageParams]) + Send + 'static>,