/* 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/. */
mod guillotine; usecrate::texture_cache::TextureCacheHandle; usecrate::internal_types::FastHashMap; pubuse guillotine::*;
/* 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::units::*; usecrate::internal_types::CacheTextureId; use euclid::{point2, size2, default::Box2D}; use smallvec::SmallVec;
pubuse etagere::AllocatorOptions as ShelfAllocatorOptions; pubuse etagere::BucketedAtlasAllocator as BucketedShelfAllocator; pubuse etagere::AtlasAllocator as ShelfAllocator;
/// ID of an allocation within a given allocator. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct AllocId(pub u32);
pubtrait AtlasAllocator { /// Specific parameters of the allocator. type Parameters; /// Constructor fn new(size: i32, parameters: &Self::Parameters) -> Self; /// Allocate a rectangle. fn allocate(&mutself, size: DeviceIntSize) -> Option<(AllocId, DeviceIntRect)>; /// Deallocate a rectangle and return its size. fn deallocate(&mutself, id: AllocId); /// Return true if there is no live allocations. fn is_empty(&self) -> bool; /// Allocated area in pixels. fn allocated_space(&self) -> i32; /// Write a debug visualization of the atlas fitting in the provided rectangle. /// /// This is inserted in a larger dump so it shouldn't contain the xml start/end tags. fn dump_into_svg(&self, rect: &Box2D<f32>, output: &mutdyn std::io::Write) -> std::io::Result<()>;
}
pubtrait AtlasAllocatorList<TextureParameters> { /// Allocate a rectangle. /// /// If allocation fails, call the provided callback, add a new allocator to the list and try again. fn allocate(
&mutself,
size: DeviceIntSize,
texture_alloc_cb: &mutdyn FnMut(DeviceIntSize, &TextureParameters) -> CacheTextureId,
) -> (CacheTextureId, AllocId, DeviceIntRect);
/// A number of 2D textures (single layer), with their own atlas allocator. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] struct TextureUnit<Allocator> {
allocator: Allocator,
handles: FastHashMap<AllocId, TextureCacheHandle>,
texture_id: CacheTextureId, // The texture might become empty during a frame where we copy items out // of it, in which case we want to postpone deleting the texture to the // next frame.
delay_deallocation: bool,
}
pubfn allocate(
&mutself,
requested_size: DeviceIntSize,
texture_alloc_cb: &mutdyn FnMut(DeviceIntSize, &TextureParameters) -> CacheTextureId,
) -> (CacheTextureId, AllocId, DeviceIntRect) { // Try to allocate from one of the existing textures. for unit in &mutself.units { iflet Some((alloc_id, rect)) = unit.allocator.allocate(requested_size) { return (unit.texture_id, alloc_id, rect);
}
}
// Need to create a new texture to hold the allocation. let texture_id = texture_alloc_cb(size2(self.size, self.size), &>self.texture_parameters); let unit_index = self.units.len();
letmut y = unit_spacing; for unit in &self.units {
writeln!(output, " {}", text(unit_spacing, y, format!("{:?}", unit.texture_id)).color(rgb(230, 230, 230)))?;
let rect = Box2D {
min: point2(unit_spacing, y),
max: point2(unit_spacing + texture_size, y + texture_size),
};
unit.allocator.dump_into_svg(&rect, output)?;
y += unit_spacing + texture_size + text_spacing;
}
writeln!(output, "{}", EndSvg)
}
pubfn allocated_space(&self) -> i32 { letmut accum = 0; for unit in &self.units {
accum += unit.allocator.allocated_space();
}
impl<P> AllocatorList<ShelfAllocator, P> { /// Attempt to move some allocations from a texture to another to reduce the number of textures. pubfn try_compaction(
&mutself,
max_pixels: i32,
changes: &mut Vec<CompactionChange>,
) { // The goal here is to consolidate items in the first texture by moving them from the last.
ifself.units.len() < 2 { // Nothing to do we are already "compact". return;
}
let last_unit = self.units.len() - 1; letmut pixels = 0; whilelet Some(alloc) = self.units[last_unit].allocator.iter().next() { // For each allocation in the last texture, try to allocate it in the first one. let new_alloc = matchself.units[0].allocator.allocate(alloc.rectangle.size()) {
Some(new_alloc) => new_alloc,
None => { // Stop when we fail to fit an item into the first texture. // We could potentially fit another smaller item in there but we take it as // an indication that the texture is more or less full, and we'll eventually // manage to move the items later if they still exist as other items expire, // which is what matters. break;
}
};
// The item was successfully reallocated in the first texture, we can proceed // with removing it from the last.
// We keep track of the texture cache handle for each allocation, make sure // the new allocation has the proper handle. let alloc_id = AllocId(alloc.id.serialize()); let new_alloc_id = AllocId(new_alloc.id.serialize()); let handle = self.units[last_unit].handles.get(&alloc_id).unwrap().clone(); self.units[0].handles.insert(new_alloc_id, handle.clone());
// Remove the allocation for the last texture. self.units[last_unit].handles.remove(&alloc_id); self.units[last_unit].allocator.deallocate(alloc.id);
// Prevent the texture from being deleted on the same frame. self.units[last_unit].delay_deallocation = true;
// Record the change so that the texture cache can do additional bookkeeping.
changes.push(CompactionChange {
handle,
old_tex: self.units[last_unit].texture_id,
old_rect: alloc.rectangle.cast_unit(),
new_id: AllocId(new_alloc.id.serialize()),
new_tex: self.units[0].texture_id,
new_rect: new_alloc.rectangle.cast_unit(),
});
// We are not in a hurry to move all allocations we can in one go, as long as we // eventually have a chance to move them all within a reasonable amount of time. // It's best to spread the load over multiple frames to avoid sudden spikes, so we // stop after we have passed a certain threshold.
pixels += alloc.rectangle.area(); if pixels > max_pixels { break;
}
}
}
// Make some allocations, forcing the the creation of multiple textures. for _ in0..50 { let alloc = allocators.allocate(size2(256, 256), alloc_cb);
allocators.set_handle(alloc.0, alloc.1, &TextureCacheHandle::Empty);
allocations.push(alloc);
}
// Deallocate everything. // It should empty all atlases and we still have textures allocated because // we haven't called release_empty_textures yet. for alloc in allocations.drain(..) {
allocators.deallocate(alloc.0, alloc.1);
}
// Allocate something else. // Bug 1680769 was causing this allocation to be duplicated and leaked in // all textures.
allocations.push(allocators.allocate(size2(8, 8), alloc_cb));
// Deallocate all known allocations. for alloc in allocations.drain(..) {
allocators.deallocate(alloc.0, alloc.1);
}
// If we have leaked items, this won't manage to remove all textures.
allocators.release_empty_textures(&mut |_| {});
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.