/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{BlobImageRequest, ImageDescriptorFlags, ImageFormat, RasterizedBlobImage}; use api::{DebugFlags, FontInstanceKey, FontKey, FontTemplate, GlyphIndex}; use api::{ExternalImageData, ExternalImageType, ExternalImageId, BlobImageResult}; use api::{DirtyRect, GlyphDimensions, IdNamespace, DEFAULT_TILE_SIZE}; use api::{ColorF, ImageData, ImageDescriptor, ImageKey, ImageRendering, TileSize}; use api::{BlobImageHandler, BlobImageKey, VoidPtrToSizeFn}; use api::units::*; use euclid::size2; usecrate::render_target::RenderTargetKind; usecrate::render_task::{RenderTaskLocation, StaticRenderTaskSurface}; usecrate::{render_api::{ClearCache, AddFont, ResourceUpdate, MemoryReport}, util::WeakTable}; usecrate::prim_store::image::AdjustedImageSource; usecrate::image_tiling::{compute_tile_size, compute_tile_range}; #[cfg(feature = "capture")] usecrate::capture::ExternalCaptureImage; #[cfg(feature = "replay")] usecrate::capture::PlainExternalImage; #[cfg(any(feature = "replay", feature = "png", feature="capture"))] usecrate::capture::CaptureConfig; usecrate::composite::{NativeSurfaceId, NativeSurfaceOperation, NativeTileId, NativeSurfaceOperationDetails}; usecrate::device::TextureFilter; usecrate::glyph_cache::{GlyphCache, CachedGlyphInfo}; usecrate::glyph_cache::GlyphCacheEntry; use glyph_rasterizer::{GLYPH_FLASHING, FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer, GlyphRasterJob}; use glyph_rasterizer::{SharedFontResources, BaseFontInstance}; usecrate::gpu_types::UvRectKind; usecrate::internal_types::{
CacheTextureId, FastHashMap, FastHashSet, TextureSource, ResourceUpdateList,
FrameId, FrameStamp,
}; usecrate::profiler::{self, TransactionProfile, bytes_to_mb}; usecrate::render_task_graph::{RenderTaskId, RenderTaskGraphBuilder}; usecrate::render_task_cache::{RenderTaskCache, RenderTaskCacheKey, RenderTaskParent}; usecrate::render_task_cache::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle}; usecrate::renderer::{GpuBufferAddress, GpuBufferBuilder, GpuBufferBuilderF, GpuBufferHandle}; usecrate::surface::SurfaceBuilder; use euclid::point2; use smallvec::SmallVec; use std::collections::hash_map::Entry::{self, Occupied, Vacant}; use std::collections::hash_map::{Iter, IterMut}; use std::collections::VecDeque; use std::{cmp, mem}; use std::fmt::Debug; use std::hash::Hash; use std::os::raw::c_void; #[cfg(any(feature = "capture", feature = "replay"))] use std::path::PathBuf; use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use std::u32; usecrate::texture_cache::{TextureCache, TextureCacheHandle, Eviction, TargetShader}; usecrate::picture_textures::PictureTextures; use peek_poke::PeekPoke;
// These coordinates are always in texels. // They are converted to normalized ST // values in the vertex shader. The reason // for this is that the texture may change // dimensions (e.g. the pages in a texture // atlas can grow). When this happens, by // storing the coordinates as texel values // we don't need to go through and update // various CPU-side structures. #[derive(Debug, Clone)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct CacheItem { pub texture_id: TextureSource, pub uv_rect_handle: GpuBufferHandle, pub uv_rect: DeviceIntRect, pub user_data: [f32; 4],
}
/// Represents the backing store of an image in the cache. /// This storage can take several forms. #[derive(Clone, Debug)] pubenum CachedImageData { /// A simple series of bytes, provided by the embedding and owned by WebRender. /// The format is stored out-of-band, currently in ImageDescriptor.
Raw(Arc<Vec<u8>>), /// 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, /// A stacking context for which a snapshot has been requested. /// /// The snapshot is grabbed from GPU-side rasterized pixels so there is no /// CPU-side data to store here.
Snapshot, /// 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),
}
struct ImageResource {
data: CachedImageData,
descriptor: ImageDescriptor,
tiling: Option<TileSize>, /// This is used to express images that are virtually very large /// but with only a visible sub-set that is valid at a given time.
visible_rect: DeviceIntRect,
adjustment: AdjustedImageSource,
generation: ImageGeneration,
}
impl ImageResult { /// Releases any texture cache entries held alive by this ImageResult. fn drop_from_cache(&mutself, texture_cache: &mut TextureCache) { match *self {
ImageResult::UntiledAuto(refmut entry) => {
entry.mark_unused(texture_cache);
},
ImageResult::Multi(refmut entries) => { for entry in entries.resources.values_mut() {
entry.mark_unused(texture_cache);
}
},
ImageResult::Err(_) => {},
}
}
}
type ImageCache = ResourceClassCache<ImageKey, ImageResult, ()>;
struct Resources {
fonts: SharedFontResources,
image_templates: ImageTemplates, // We keep a set of Weak references to the fonts so that we're able to include them in memory // reports even if only the OS is holding on to the Vec<u8>. PtrWeakHashSet will periodically // drop any references that have gone dead.
weak_fonts: WeakTable
}
// We only use this to report glyph dimensions to the user of the API, so using // the font instance key should be enough. If we start using it to cache dimensions // for internal font instances we should change the hash key accordingly. pubtype GlyphDimensionsCache = FastHashMap<(FontInstanceKey, GlyphIndex), Option<GlyphDimensions>>;
/// Internal information about allocated render targets in the pool struct RenderTarget {
size: DeviceIntSize,
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.size.width * self.size.height) as usize * bpp
}
/// Returns true if this texture was used within `threshold` frames of /// the current frame. pubfn used_recently(&self, current_frame_id: FrameId, threshold: u64) -> 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, /// which may be stored as memory buffers, GPU textures, or handles to resources /// managed by the OS or other parts of WebRender. pubstruct ResourceCache {
cached_glyphs: GlyphCache,
cached_images: ImageCache,
cached_render_tasks: RenderTaskCache,
#[cfg(feature = "capture")] /// Used for capture sequences. If the resource cache is updated, then we /// mark it as dirty. When the next frame is captured in the sequence, we /// dump the state of the resource cache.
capture_dirty: bool,
/// TODO(gw): We should expire (parts of) this cache semi-regularly!
cached_glyph_dimensions: GlyphDimensionsCache,
glyph_rasterizer: GlyphRasterizer,
/// The set of images that aren't present or valid in the texture cache, /// and need to be rasterized and/or uploaded this frame. This includes /// both blobs and regular images.
pending_image_requests: FastHashSet<ImageRequest>,
/// A log of the last three frames worth of deleted image keys kept /// for debugging purposes.
deleted_blob_keys: VecDeque<Vec<BlobImageKey>>,
/// We keep one around to be able to call clear_namespace /// after the api object is deleted. For most purposes the /// api object's blob handler should be used instead.
blob_image_handler: Option<Box<dyn BlobImageHandler>>,
/// A list of queued compositor surface updates to apply next frame.
pending_native_surface_updates: Vec<NativeSurfaceOperation>,
/// A pool of render targets for use by the render task graph
render_target_pool: Vec<RenderTarget>,
/// An empty (1x1 transparent) image used when a stacking context snapshot /// is missing. /// /// For now it acts as a catch-all solution for cases where WebRender fails /// to produce a texture cache item for a snapshotted tacking context. /// These cases include: /// - Empty stacking contexts. /// - Stacking contexts that are more aggressively culled out than they /// should, for example when they are in a perspective transform that /// cannot be projected to screen space. /// - Likely other cases we have not found yet. /// Over time it would be better to handle each of these cases explicitly /// and make it a hard error to fail to snapshot a stacking context.
fallback_handle: TextureCacheHandle,
debug_fallback_panic: bool,
debug_fallback_pink: bool,
}
/// Maximum texture size before we consider it preferrable to break the texture /// into tiles. pubfn tiling_threshold(&self) -> i32 { self.texture_cache.tiling_threshold()
}
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::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
}
CachedImageData::Snapshot => false,
}
}
/// Request an optionally cacheable render task. /// /// If the render task cache key is None, the render task is /// not cached. /// Otherwise, if the item is already cached, the texture cache /// handle will be returned. Otherwise, the user supplied /// closure will be invoked to generate the render task /// chain that is required to draw this task. /// /// This function takes care of adding the render task as a /// dependency to its parent task or surface. pubfn request_render_task(
&mutself,
key: Option<RenderTaskCacheKey>,
is_opaque: bool,
parent: RenderTaskParent,
gpu_buffer_builder: &mut GpuBufferBuilderF,
rg_builder: &mut RenderTaskGraphBuilder,
surface_builder: &mut SurfaceBuilder,
f: &mutdyn FnMut(&mut RenderTaskGraphBuilder, &mut GpuBufferBuilderF) -> RenderTaskId,
) -> RenderTaskId { self.cached_render_tasks.request_render_task(
key.clone(),
&mutself.texture_cache,
is_opaque,
parent,
gpu_buffer_builder,
rg_builder,
surface_builder,
f
)
}
let render_task = rg_builder.get_task_mut(task_id);
// Note: We are defaulting to `ImageRendering::Auto` and only support // this mode here, because the desired rendering mode is known later // when an image display item will read the produced snapshot. In theory, // multiple image items with different rendering modes could refer to // the snapshot's image key, or they could first appear in a later frame // So delaying snapshotting logic until the we know about the rendering // mode would, in addition to adding complexity, only work in certain // cases. // If supporting more rendering modes is important for snapshots, we could // consider specifying it in the stacking context's snapshot params so // that we have the information early enough. // Here and in other parts of the code, this restriction manifests itself // in the expectation that we are dealing with `ImageResult::UntiledAuto` // which implicitly specifies the rendering mode.
// Make sure to update the existing image info and texture cache handle // instead of overwriting them if they already exist for this key. let image_result = self.cached_images.entry(image_key).or_insert_with(|| {
ImageResult::UntiledAuto(CachedImageInfo {
texture_cache_handle: TextureCacheHandle::invalid(),
dirty_rect: ImageDirtyRect::All,
manual_eviction: true,
})
});
let ImageResult::UntiledAuto(refmut info) = *image_result else {
unreachable!("Expected untiled image with auto filter for snapshot");
};
let flags = if is_opaque {
ImageDescriptorFlags::IS_OPAQUE
} else {
ImageDescriptorFlags::empty()
};
let descriptor = ImageDescriptor::new(
size.width,
size.height, self.texture_cache.shared_color_expected_format(),
flags,
);
// TODO(bug 1975123) We currently do not have a way to ensure that an // atlas texture used as a destination for the snapshot will not be // also used as an input by a primitive of the snapshot. // We can't both read and write the same texture in a draw call // so we work around it by preventing the snapshot from being placed // in a texture atlas. let force_standalone_texture = true;
// Allocate space in the texture cache, but don't supply // and CPU-side data to be uploaded. let user_data = [0.0; 4]; self.texture_cache.update(
&mut info.texture_cache_handle,
descriptor,
TextureFilter::Linear,
None,
user_data,
DirtyRect::All,
gpu_buffer_builder,
None,
render_task.uv_rect_kind(),
Eviction::Manual,
TargetShader::Default,
force_standalone_texture,
);
// Get the allocation details in the texture cache, and store // this in the render task. The renderer will draw this task // into the appropriate rect of the texture cache on this frame. let (texture_id, uv_rect, _, _, _) = self.texture_cache.get_cache_location(&info.texture_cache_handle);
pubfn post_scene_building_update(
&mutself,
updates: Vec<ResourceUpdate>,
profile: &mut TransactionProfile,
) { // TODO, there is potential for optimization here, by processing updates in // bulk rather than one by one (for example by sorting allocations by size or // in a way that reduces fragmentation in the atlas). #[cfg(feature = "capture")] match updates.is_empty() { false => self.capture_dirty = true,
_ => {},
}
for update in updates { match update {
ResourceUpdate::AddImage(img) => { iflet ImageData::Raw(ref bytes) = img.data { self.image_templates_memory += bytes.len();
profile.set(profiler::IMAGE_TEMPLATES_MEM, bytes_to_mb(self.image_templates_memory));
} self.add_image_template(
img.key,
img.descriptor,
img.data.into(),
&img.descriptor.size.into(),
img.tiling,
);
profile.set(profiler::IMAGE_TEMPLATES, self.resources.image_templates.images.len());
}
ResourceUpdate::UpdateImage(img) => { self.update_image_template(img.key, img.descriptor, img.data.into(), &img.dirty_rect);
}
ResourceUpdate::AddBlobImage(img) => { self.add_image_template(
img.key.as_image(),
img.descriptor,
CachedImageData::Blob,
&img.visible_rect,
Some(img.tile_size),
);
}
ResourceUpdate::UpdateBlobImage(img) => { self.update_image_template(
img.key.as_image(),
img.descriptor,
CachedImageData::Blob,
&to_image_dirty_rect(
&img.dirty_rect
),
); self.discard_tiles_outside_visible_area(img.key, &img.visible_rect); // TODO: remove? self.set_image_visible_rect(img.key.as_image(), &img.visible_rect);
}
ResourceUpdate::DeleteImage(img) => { self.delete_image_template(img);
profile.set(profiler::IMAGE_TEMPLATES, self.resources.image_templates.images.len());
profile.set(profiler::IMAGE_TEMPLATES_MEM, bytes_to_mb(self.image_templates_memory));
}
ResourceUpdate::DeleteBlobImage(img) => { self.delete_image_template(img.as_image());
}
ResourceUpdate::AddSnapshotImage(img) => { let format = self.texture_cache.shared_color_expected_format(); self.add_image_template(
img.key.as_image(),
ImageDescriptor {
format, // We'll know about the size when creating the render task.
size: DeviceIntSize::zero(),
stride: None,
offset: 0,
flags: ImageDescriptorFlags::empty(),
},
CachedImageData::Snapshot,
&DeviceIntRect::zero(),
None,
);
}
ResourceUpdate::DeleteSnapshotImage(img) => { self.delete_image_template(img.as_image());
}
ResourceUpdate::DeleteFont(font) => { iflet Some(shared_key) = self.resources.fonts.font_keys.delete_key(&font) { self.delete_font_template(shared_key); iflet Some(refmut handler) = &mutself.blob_image_handler {
handler.delete_font(shared_key);
}
profile.set(profiler::FONT_TEMPLATES, self.resources.fonts.templates.len());
profile.set(profiler::FONT_TEMPLATES_MEM, bytes_to_mb(self.font_templates_memory));
}
}
ResourceUpdate::DeleteFontInstance(font) => { iflet Some(shared_key) = self.resources.fonts.instance_keys.delete_key(&font) { self.delete_font_instance(shared_key);
} iflet Some(refmut handler) = &mutself.blob_image_handler {
handler.delete_font_instance(font);
}
}
ResourceUpdate::SetBlobImageVisibleArea(key, area) => { self.discard_tiles_outside_visible_area(key, &area); self.set_image_visible_rect(key.as_image(), &area);
}
ResourceUpdate::AddFont(font) => { // The shared key was already added in ApiResources, but the first time it is // seen on the backend we still need to do some extra initialization here. let (key, template) = match font {
AddFont::Raw(key, bytes, index) => {
(key, FontTemplate::Raw(bytes, index))
}
AddFont::Native(key, native_font_handle) => {
(key, FontTemplate::Native(native_font_handle))
}
}; let shared_key = self.resources.fonts.font_keys.map_key(&key); if !self.glyph_rasterizer.has_font(shared_key) { self.add_font_template(shared_key, template);
profile.set(profiler::FONT_TEMPLATES, self.resources.fonts.templates.len());
profile.set(profiler::FONT_TEMPLATES_MEM, bytes_to_mb(self.font_templates_memory));
}
}
ResourceUpdate::AddFontInstance(..) => { // Already added in ApiResources.
}
}
}
}
pubfn add_rasterized_blob_images(
&mutself,
images: Vec<(BlobImageRequest, BlobImageResult)>,
profile: &mut TransactionProfile,
) { for (request, result) in images { let data = match result {
Ok(data) => data,
Err(..) => {
warn!("Failed to rasterize a blob image"); continue;
}
};
// First make sure we have an entry for this key (using a placeholder // if need be). let tiles = self.rasterized_blob_images.entry(request.key).or_insert_with(
|| { RasterizedBlob::default() }
);
pubfn add_font_template(&mutself, font_key: FontKey, template: FontTemplate) { // Push the new font to the font renderer, and also store // it locally for glyph metric requests. iflet FontTemplate::Raw(ref data, _) = template { self.resources.weak_fonts.insert(Arc::downgrade(data)); self.font_templates_memory += data.len();
} self.glyph_rasterizer.add_font(font_key, template.clone()); self.resources.fonts.templates.add_font(font_key, template);
}
pubfn add_image_template(
&mutself,
image_key: ImageKey,
descriptor: ImageDescriptor,
data: CachedImageData,
visible_rect: &DeviceIntRect, mut tiling: Option<TileSize>,
) { iflet Some(refmut tile_size) = tiling { // Sanitize the value since it can be set by a pref.
*tile_size = (*tile_size).max(16).min(2048);
}
if tiling.is_none() && Self::should_tile(self.tiling_threshold(), &descriptor, &data) { // We aren't going to be able to upload a texture this big, so tile it, even // if tiling was not requested.
tiling = Some(DEFAULT_TILE_SIZE);
}
// Each cache entry stores its own copy of the image's dirty rect. This allows them to be // updated independently. matchself.cached_images.try_get_mut(&image_key) {
Some(&mut ImageResult::UntiledAuto(refmut entry)) => {
entry.dirty_rect = entry.dirty_rect.union(dirty_rect);
}
Some(&mut ImageResult::Multi(refmut entries)) => { for (key, entry) in entries.iter_mut() { // We want the dirty rect relative to the tile and not the whole image. let local_dirty_rect = match (tiling, key.tile) {
(Some(tile_size), Some(tile)) => {
dirty_rect.map(|mut rect|{ let tile_offset = DeviceIntPoint::new(
tile.x as i32,
tile.y as i32,
) * tile_size as i32;
rect = rect.translate(-tile_offset.to_vector());
let tile_rect = compute_tile_size(
&descriptor.size.into(),
tile_size,
tile,
).into();
pubfn delete_image_template(&mutself, image_key: ImageKey) { // Remove the template. let value = self.resources.image_templates.remove(image_key);
// Release the corresponding texture cache entry, if any. iflet Some(mut cached) = self.cached_images.remove(&image_key) {
cached.drop_from_cache(&mutself.texture_cache);
}
match value {
Some(image) => if image.data.is_blob() { iflet CachedImageData::Raw(data) = image.data { self.image_templates_memory -= data.len();
}
let blob_key = BlobImageKey(image_key); self.deleted_blob_keys.back_mut().unwrap().push(blob_key); self.rasterized_blob_images.remove(&blob_key);
},
None => {
warn!("Delete the non-exist key");
debug!("key={:?}", image_key);
}
}
}
/// Return the current generation of an image template pubfn get_image_generation(&self, key: ImageKey) -> ImageGeneration { self.resources
.image_templates
.get(key)
.map_or(ImageGeneration::INVALID, |template| template.generation)
}
/// Requests an image to ensure that it will be in the texture cache this frame. /// /// returns the size in device pixel of the image or tile. pubfn request_image(
&mutself, mut request: ImageRequest,
gpu_buffer: &mut GpuBufferBuilderF,
) -> DeviceIntSize {
debug_assert_eq!(self.state, State::AddResources);
let size = match request.tile {
Some(tile) => compute_tile_size(&template.visible_rect, template.tiling.unwrap(), tile),
None => template.descriptor.size,
};
// Images that don't use the texture cache can early out. if !template.data.uses_texture_cache() { return size;
}
if template.data.is_snapshot() { // We only Support `Auto` for snapshots. This is because we have // to make the decision about the filtering mode earlier when // producing the snapshot. // See the comment at the top of `render_as_image`.
request.rendering = ImageRendering::Auto;
}
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 DeviceIntSize::zero();
}
let storage = matchself.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(refmut entry) => {
Some(mem::replace(entry, CachedImageInfo {
texture_cache_handle: TextureCacheHandle::invalid(),
dirty_rect: DirtyRect::All,
manual_eviction: false,
}))
}
_ => None
};
// 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(refmut entry) => entry,
ImageResult::Multi(refmut 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_buffer);
if !needs_upload && entry.dirty_rect.is_empty() { return size;
}
if !self.pending_image_requests.insert(request) { return size;
}
if template.data.is_blob() { let request: BlobImageRequest = request.into(); let missing = matchself.rasterized_blob_images.get(&request.key) {
Some(tiles) => !tiles.contains_key(&request.tile),
_ => true,
};
self.glyph_rasterizer.prepare_font(&mut font); let glyph_key_cache = self.cached_glyphs.insert_glyph_key_cache_for_font(&font); let texture_cache = &mutself.texture_cache; self.glyph_rasterizer.request_glyphs(
font,
glyph_keys,
|key| { let cache_key = key.cache_key(); iflet Some(entry) = glyph_key_cache.try_get(&cache_key) { match entry {
GlyphCacheEntry::Cached(ref glyph) => { if !texture_cache.request(&glyph.texture_cache_handle, gpu_buffer) { returnfalse;
} // This case gets hit when we already rasterized the glyph, but the // glyph has been evicted from the texture cache. Just force it to // pending so it gets rematerialized.
} // Otherwise, skip the entry if it is blank or pending.
GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => returnfalse,
}
};
#[inline] fn get_image_info(&self, request: ImageRequest) -> Result<&CachedImageInfo, ()> { // TODO(Jerry): add a debug option to visualize the corresponding area for // the Err() case of CacheItem. match *self.cached_images.get(&request.key) {
ImageResult::UntiledAuto(ref image_info) => Ok(image_info),
ImageResult::Multi(ref entries) => Ok(entries.get(&request.into())),
ImageResult::Err(_) => Err(()),
}
}
// Pop the old frame and push a new one. // Recycle the allocation if any. letmut v = self.deleted_blob_keys.pop_front().unwrap_or_else(Vec::new);
v.clear(); self.deleted_blob_keys.push_back(v);
for request inself.pending_image_requests.drain() { let image_template = self.resources.image_templates.get_mut(request.key).unwrap();
debug_assert!(image_template.data.uses_texture_cache());
match image_template.data {
CachedImageData::Snapshot => { // The update is done in ResourceCache::render_as_image.
}
CachedImageData::Raw(..)
| CachedImageData::External(..) => { // 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(); let img = &blob_image[&request.tile.unwrap()];
updates.push((
CachedImageData::Raw(Arc::clone(&img.data)),
Some(img.rasterized_rect)
));
}
};
for (image_data, blob_rasterized_rect) in updates { let entry = match *self.cached_images.get_mut(&request.key) {
ImageResult::UntiledAuto(refmut entry) => entry,
ImageResult::Multi(refmut entries) => entries.get_mut(&request.into()),
ImageResult::Err(_) => panic!("Update requested for invalid entry")
};
iflet Some(tile) = request.tile { let tile_size = image_template.tiling.unwrap(); let clipped_tile_size = compute_tile_size(&image_template.visible_rect, tile_size, tile); // The tiled image could be stored on the CPU as one large image or be // already broken up into tiles. This affects the way we compute the stride // and offset. let tiled_on_cpu = image_template.data.is_blob(); if !tiled_on_cpu { // we don't expect to have partial tiles at the top and left of non-blob // images.
debug_assert_eq!(image_template.visible_rect.min, point2(0, 0)); let bpp = descriptor.format.bytes_per_pixel(); let stride = descriptor.compute_stride();
descriptor.stride = Some(stride);
descriptor.offset +=
tile.y as i32 * tile_size as i32 * stride +
tile.x as i32 * tile_size as i32 * bpp;
}
descriptor.size = clipped_tile_size;
}
// If we are uploading the dirty region of a blob image we might have several // rects to upload so we use each of these rasterized rects rather than the // overall dirty rect of the image. iflet Some(rect) = blob_rasterized_rect {
dirty_rect = DirtyRect::Partial(rect);
}
let filter = match request.rendering {
ImageRendering::Pixelated => {
TextureFilter::Nearest
}
ImageRendering::Auto | ImageRendering::CrispEdges => { // If the texture uses linear filtering, enable mipmaps and // trilinear filtering, for better image quality. We only // support this for now on textures that are not placed // into the shared cache. This accounts for any image // that is > 512 in either dimension, so it should cover // the most important use cases. We may want to support // mip-maps on shared cache items in the future. if descriptor.allow_mipmaps() &&
descriptor.size.width > 512 &&
descriptor.size.height > 512 &&
!self.texture_cache.is_allowed_in_shared_cache(
TextureFilter::Linear,
&descriptor,
) {
TextureFilter::Trilinear
} else {
TextureFilter::Linear
}
}
};
//Note: at this point, the dirty rectangle is local to the descriptor space self.texture_cache.update(
&mut entry.texture_cache_handle,
descriptor,
filter,
Some(image_data),
[0.0; 4],
dirty_rect,
&mut gpu_buffer.f32,
None,
UvRectKind::Rect,
eviction,
TargetShader::Default, false,
);
}
}
}
pubfn create_compositor_backdrop_surface(
&mutself,
color: ColorF
) -> NativeSurfaceId { let id = NativeSurfaceId(NEXT_NATIVE_SURFACE_ID.fetch_add(1, Ordering::Relaxed) as u64);
/// Queue up allocation of a new OS native compositor surface with the /// specified tile size. pubfn create_compositor_surface(
&mutself,
virtual_offset: DeviceIntPoint,
tile_size: DeviceIntSize,
is_opaque: bool,
) -> NativeSurfaceId { let id = NativeSurfaceId(NEXT_NATIVE_SURFACE_ID.fetch_add(1, Ordering::Relaxed) as u64);
/// Queue up destruction of an existing native OS surface. This is used when /// a picture cache surface is dropped or resized. pubfn destroy_compositor_surface(
&mutself,
id: NativeSurfaceId,
) { self.pending_native_surface_updates.push(
NativeSurfaceOperation {
details: NativeSurfaceOperationDetails::DestroySurface {
id,
}
}
);
}
/// Queue construction of a native compositor tile on a given surface. pubfn create_compositor_tile(
&mutself,
id: NativeTileId,
) { self.pending_native_surface_updates.push(
NativeSurfaceOperation {
details: NativeSurfaceOperationDetails::CreateTile {
id,
},
}
);
}
/// Queue destruction of a native compositor tile. pubfn destroy_compositor_tile(
&mutself,
id: NativeTileId,
) { self.pending_native_surface_updates.push(
NativeSurfaceOperation {
details: NativeSurfaceOperationDetails::DestroyTile {
id,
},
}
);
}
// GC the render target pool, if it's currently > 64 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( 64 * 1024 * 1024, 32 * 1024 * 1024 * 10, 60,
);
// First clear out any non-shared resources associated with the namespace. self.resources.fonts.instances.clear_namespace(namespace); let deleted_keys = self.resources.fonts.templates.clear_namespace(namespace); self.glyph_rasterizer.delete_fonts(&deleted_keys); self.cached_glyphs.clear_namespace(namespace); iflet Some(handler) = &mutself.blob_image_handler {
handler.clear_namespace(namespace);
}
// Check for any shared instance keys that were remapped from the namespace. let shared_instance_keys = self.resources.fonts.instance_keys.clear_namespace(namespace); if !shared_instance_keys.is_empty() { self.resources.fonts.instances.delete_font_instances(&shared_instance_keys); self.cached_glyphs.delete_font_instances(&shared_instance_keys, &mutself.glyph_rasterizer); // Blob font instances are not shared across namespaces, so there is no // need to call the handler for them individually.
}
// Finally check for any shared font keys that were remapped from the namespace. let shared_keys = self.resources.fonts.font_keys.clear_namespace(namespace); if !shared_keys.is_empty() { self.glyph_rasterizer.delete_fonts(&shared_keys); self.resources.fonts.templates.delete_fonts(&shared_keys); self.cached_glyphs.delete_fonts(&shared_keys); iflet Some(handler) = &mutself.blob_image_handler { for &key in &shared_keys {
handler.delete_font(key);
}
}
}
}
/// Reports the CPU heap usage of this ResourceCache. /// /// NB: It would be much better to use the derive(MallocSizeOf) machinery /// here, but the Arcs complicate things. The two ways to handle that would /// be to either (a) Implement MallocSizeOf manually for the things that own /// them and manually avoid double-counting, or (b) Use the "seen this pointer /// yet" machinery from the proper malloc_size_of crate. We can do this if/when /// more accurate memory reporting on these resources becomes a priority. pubfn report_memory(&self, op: VoidPtrToSizeFn) -> MemoryReport { letmut report = MemoryReport::default();
letmut seen_fonts = std::collections::HashSet::new(); // Measure fonts. We only need the templates here, because the instances // don't have big buffers. for (_, font) inself.resources.fonts.templates.lock().iter() { iflet FontTemplate::Raw(ref raw, _) = font {
report.fonts += unsafe { op(raw.as_ptr() as *const c_void) };
seen_fonts.insert(raw.as_ptr());
}
}
for font inself.resources.weak_fonts.iter() { if !seen_fonts.contains(&font.as_ptr()) {
report.weak_fonts += unsafe { op(font.as_ptr() as *const c_void) };
}
}
// 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)inself.rasterized_blob_images.iter(){ letmutaccumulate=|b:&RasterizedBlobImage|{ report.rasterized_blobs+=unsafe{op(b.data.as_ptr()as*constc_void)}; }; matchimage{ RasterizedBlob::Tiled(map)=>map.values().for_each(&mutaccumulate), RasterizedBlob::NonTiled(vec)=>vec.iter().for_each(&mutaccumulate), }; }
*/
report
}
/// Properly deletes all images matching the predicate. fn clear_images<F: Fn(&ImageKey) -> bool>(&mutself, f: F) { let keys = self.resources.image_templates.images.keys().filter(|k| f(*k))
.cloned().collect::<SmallVec<[ImageKey; 16]>>();
for key in keys { self.delete_image_template(key);
}
#[cfg(feature="leak_checks")] let check_leaks = true; #[cfg(not(feature="leak_checks"))] let check_leaks = false;
if check_leaks { let blob_f = |key: &BlobImageKey| { f(&key.as_image()) };
assert!(!self.resources.image_templates.images.keys().any(&f));
assert!(!self.cached_images.resources.keys().any(&f));
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. pubfn get_or_create_render_target_from_pool(
&mutself,
size: DeviceIntSize,
format: ImageFormat,
) -> CacheTextureId { for target in &mutself.render_target_pool { if target.size == size &&
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,
format,
);
self.render_target_pool.push(RenderTarget {
size,
format,
texture_id,
is_active: true,
last_frame_used: self.current_frame_id,
});
texture_id
}
/// Return a render target to the pool. pubfn return_render_target_to_pool(
&mutself,
id: CacheTextureId,
) { let target = self.render_target_pool
.iter_mut()
.find(|t| t.texture_id == id)
.expect("bug: invalid render target id");
/// Clear all current render targets (e.g. on memory pressure) fn clear_render_target_pool(
&mutself,
) { for target inself.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(
&mutself,
total_bytes_threshold: usize,
total_bytes_red_line_threshold: usize,
frames_threshold: u64,
) { // Get the total GPU memory size used by the current render target pool letmut 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. letmut retained_targets = SmallVec::<[RenderTarget; 8]>::new();
for target inself.render_target_pool.drain(..) {
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;
// This currently only casts the unit but will soon apply an offset fn to_image_dirty_rect(blob_dirty_rect: &BlobDirtyRect) -> ImageDirtyRect { match *blob_dirty_rect {
DirtyRect::Partial(rect) => DirtyRect::Partial(rect.cast_unit()),
DirtyRect::All => DirtyRect::All,
}
}
impl ResourceCache { #[cfg(feature = "capture")] pubfn save_capture(
&mutself, root: &PathBuf
) -> (PlainResources, Vec<ExternalCaptureImage>) { use std::fs; use std::io::Write;
info!("saving resource cache"); let res = &self.resources; let path_fonts = root.join("fonts"); if !path_fonts.is_dir() {
fs::create_dir(&path_fonts).unwrap();
} let path_images = root.join("images"); if !path_images.is_dir() {
fs::create_dir(&path_images).unwrap();
} let path_blobs = root.join("blobs"); if !path_blobs.is_dir() {
fs::create_dir(&path_blobs).unwrap();
} let path_externals = root.join("externals"); if !path_externals.is_dir() {
fs::create_dir(&path_externals).unwrap();
}
info!("\tfont templates"); letmut font_paths = FastHashMap::default(); for template in res.fonts.templates.lock().values() { let data: &[u8] = match *template {
FontTemplate::Raw(ref arc, _) => arc,
FontTemplate::Native(_) => continue,
}; let font_id = res.fonts.templates.len() + 1; let entry = match font_paths.entry(data.as_ptr()) {
Entry::Occupied(_) => continue,
Entry::Vacant(e) => e,
}; let file_name = format!("{}.raw", font_id); let short_path = format!("fonts/{}", file_name);
fs::File::create(path_fonts.join(file_name))
.expect(&format!("Unable to create {}", short_path))
.write_all(data)
.unwrap();
entry.insert(short_path);
}
info!("\timage templates"); letmut image_paths = FastHashMap::default(); letmut other_paths = FastHashMap::default(); letmut num_blobs = 0; letmut external_images = Vec::new(); for (&key, template) in res.image_templates.images.iter() { let desc = &template.descriptor; match template.data {
CachedImageData::Raw(ref arc) => { let image_id = image_paths.len() + 1; let entry = match image_paths.entry(arc.as_ptr()) {
Entry::Occupied(_) => continue,
Entry::Vacant(e) => e,
};
#[cfg(feature = "png")]
CaptureConfig::save_png(
root.join(format!("images/{}.png", image_id)),
desc.size,
desc.format,
desc.stride,
&arc,
); let file_name = format!("{}.raw", image_id); let short_path = format!("images/{}", file_name);
fs::File::create(path_images.join(file_name))
.expect(&format!("Unable to create {}", short_path))
.write_all(&*arc)
.unwrap();
entry.insert(short_path);
}
CachedImageData::Blob => {
warn!("Tiled blob images aren't supported yet"); let result = RasterizedBlobImage {
rasterized_rect: desc.size.into(),
data: Arc::new(vec![0; desc.compute_total_size() as usize])
};
assert_eq!(result.rasterized_rect.size(), desc.size);
assert_eq!(result.data.len(), desc.compute_total_size() as usize);
info!("loading resource cache"); //TODO: instead of filling the local path to Arc<data> map as we process // each of the resource types, we could go through all of the local paths // and fill out the map as the first step. letmut raw_map = FastHashMap::<String, Arc<Vec<u8>>>::default();
self.glyph_rasterizer.reset(); let res = &mutself.resources;
res.fonts.templates.clear();
res.fonts.instances.clear();
res.image_templates.images.clear();
info!("\tfont templates..."); let root = config.resource_root(); let native_font_replacement = Arc::new(NATIVE_FONT.to_vec()); for (key, plain_template) in resources.font_templates { let arc = match raw_map.entry(plain_template.data) {
Entry::Occupied(e) => {
e.get().clone()
}
Entry::Vacant(e) => { let file_path = if Path::new(e.key()).is_absolute() {
PathBuf::from(e.key())
} else {
root.join(e.key())
}; let arc = match fs::read(file_path) {
Ok(buffer) => Arc::new(buffer),
Err(err) => {
error!("Unable to open font template {:?}: {:?}", e.key(), err);
Arc::clone(&native_font_replacement)
}
};
e.insert(arc).clone()
}
};
let template = FontTemplate::Raw(arc, plain_template.index); // Only add the template if this is the first time it has been seen. iflet Some(shared_key) = res.fonts.font_keys.add_key(&key, &template) { self.glyph_rasterizer.add_font(shared_key, template.clone());
res.fonts.templates.add_font(shared_key, template);
}
}
info!("\tfont instances..."); for instance in resources.font_instances { // Target the instance to a shared font key. let base = BaseFontInstance {
font_key: res.fonts.font_keys.map_key(&instance.font_key),
..instance
}; iflet Some(shared_instance) = res.fonts.instance_keys.add_key(base) {
res.fonts.instances.add_font_instance(shared_instance);
}
}
info!("\timage templates..."); letmut external_images = Vec::new(); for (key, template) in resources.image_templates { let data = if template.data.starts_with("snapshots/") { // TODO(nical): If a snapshot was captured in a previous frame, // we have to serialize/deserialize the image itself.
CachedImageData::Snapshot
} else { match config.deserialize_for_resource::<PlainExternalImage, _>(&template.data) {
Some(plain) => { let ext_data = plain.external;
external_images.push(plain);
CachedImageData::External(ext_data)
}
None => { let arc = match raw_map.entry(template.data) {
Entry::Occupied(e) => e.get().clone(),
Entry::Vacant(e) => { match fs::read(root.join(e.key())) {
Ok(buffer) => {
e.insert(Arc::new(buffer)).clone()
}
Err(err) => {
log::warn!("Unable to open {}: {err:?}", e.key()); continue;
}
}
}
};
CachedImageData::Raw(arc)
}
}
};
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.57Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-22)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.