summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/render_task_cache.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src/render_task_cache.rs')
-rw-r--r--gfx/wr/webrender/src/render_task_cache.rs336
1 files changed, 336 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/render_task_cache.rs b/gfx/wr/webrender/src/render_task_cache.rs
new file mode 100644
index 0000000000..ddd61dec68
--- /dev/null
+++ b/gfx/wr/webrender/src/render_task_cache.rs
@@ -0,0 +1,336 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+use api::{ImageDescriptor, ImageDescriptorFlags, DirtyRect};
+use api::units::*;
+use crate::border::BorderSegmentCacheKey;
+use crate::box_shadow::{BoxShadowCacheKey};
+use crate::device::TextureFilter;
+use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
+use crate::gpu_cache::GpuCache;
+use crate::internal_types::FastHashMap;
+use crate::picture::{SurfaceIndex, SurfaceInfo};
+use crate::prim_store::image::ImageCacheKey;
+use crate::prim_store::gradient::GradientCacheKey;
+use crate::prim_store::line_dec::LineDecorationCacheKey;
+use crate::resource_cache::CacheItem;
+use std::{mem, usize, f32, i32};
+use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction, TargetShader};
+use crate::render_target::RenderTargetKind;
+use crate::render_task::{RenderTask, StaticRenderTaskSurface, RenderTaskLocation};
+use crate::render_task_graph::{RenderTaskGraphBuilder, RenderTaskId};
+use crate::frame_builder::add_child_render_task;
+use euclid::Scale;
+
+const MAX_CACHE_TASK_SIZE: f32 = 4096.0;
+
+/// Describes a parent dependency for a render task. Render tasks
+/// may depend on a surface (e.g. when a surface uses a cached border)
+/// or an arbitrary render task (e.g. when a clip mask uses a blurred
+/// box-shadow input).
+pub enum RenderTaskParent {
+ /// Parent is a surface
+ Surface(SurfaceIndex),
+ /// Parent is a render task
+ RenderTask(RenderTaskId),
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum RenderTaskCacheKeyKind {
+ BoxShadow(BoxShadowCacheKey),
+ Image(ImageCacheKey),
+ BorderSegment(BorderSegmentCacheKey),
+ LineDecoration(LineDecorationCacheKey),
+ Gradient(GradientCacheKey),
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct RenderTaskCacheKey {
+ pub size: DeviceIntSize,
+ pub kind: RenderTaskCacheKeyKind,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct RenderTaskCacheEntry {
+ user_data: Option<[f32; 3]>,
+ is_opaque: bool,
+ pub handle: TextureCacheHandle,
+ /// If a render task was generated for this cache entry on _this_ frame,
+ /// we need to track the task id here. This allows us to hook it up as
+ /// a dependency of any parent tasks that make a reqiest from the render
+ /// task cache.
+ pub render_task_id: Option<RenderTaskId>,
+}
+
+#[derive(Debug, MallocSizeOf)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+pub enum RenderTaskCacheMarker {}
+
+// A cache of render tasks that are stored in the texture
+// cache for usage across frames.
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct RenderTaskCache {
+ map: FastHashMap<RenderTaskCacheKey, FreeListHandle<RenderTaskCacheMarker>>,
+ cache_entries: FreeList<RenderTaskCacheEntry, RenderTaskCacheMarker>,
+}
+
+pub type RenderTaskCacheEntryHandle = WeakFreeListHandle<RenderTaskCacheMarker>;
+
+impl RenderTaskCache {
+ pub fn new() -> Self {
+ RenderTaskCache {
+ map: FastHashMap::default(),
+ cache_entries: FreeList::new(),
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.map.clear();
+ self.cache_entries.clear();
+ }
+
+ pub fn begin_frame(
+ &mut self,
+ texture_cache: &mut TextureCache,
+ ) {
+ profile_scope!("begin_frame");
+ // Drop any items from the cache that have been
+ // evicted from the texture cache.
+ //
+ // This isn't actually necessary for the texture
+ // cache to be able to evict old render tasks.
+ // It will evict render tasks as required, since
+ // the access time in the texture cache entry will
+ // be stale if this task hasn't been requested
+ // for a while.
+ //
+ // Nonetheless, we should remove stale entries
+ // from here so that this hash map doesn't
+ // grow indefinitely!
+ let cache_entries = &mut self.cache_entries;
+
+ self.map.retain(|_, handle| {
+ let retain = texture_cache.is_allocated(
+ &cache_entries.get(handle).handle,
+ );
+ if !retain {
+ let handle = mem::replace(handle, FreeListHandle::invalid());
+ cache_entries.free(handle);
+ }
+ retain
+ });
+
+ // Clear out the render task ID of any remaining cache entries that were drawn
+ // on the previous frame, so we don't accidentally hook up stale dependencies
+ // when building the frame graph.
+ for (_, handle) in &self.map {
+ let entry = self.cache_entries.get_mut(handle);
+ entry.render_task_id = None;
+ }
+ }
+
+ fn alloc_render_task(
+ render_task: &mut RenderTask,
+ entry: &mut RenderTaskCacheEntry,
+ gpu_cache: &mut GpuCache,
+ texture_cache: &mut TextureCache,
+ ) {
+ // Find out what size to alloc in the texture cache.
+ let size = render_task.location.size();
+ let target_kind = render_task.target_kind();
+
+ // Select the right texture page to allocate from.
+ let image_format = match target_kind {
+ RenderTargetKind::Color => texture_cache.shared_color_expected_format(),
+ RenderTargetKind::Alpha => texture_cache.shared_alpha_expected_format(),
+ };
+
+ let flags = if entry.is_opaque {
+ ImageDescriptorFlags::IS_OPAQUE
+ } else {
+ ImageDescriptorFlags::empty()
+ };
+
+ let descriptor = ImageDescriptor::new(
+ size.width,
+ size.height,
+ image_format,
+ flags,
+ );
+
+ // Allocate space in the texture cache, but don't supply
+ // and CPU-side data to be uploaded.
+ texture_cache.update(
+ &mut entry.handle,
+ descriptor,
+ TextureFilter::Linear,
+ None,
+ entry.user_data.unwrap_or([0.0; 3]),
+ DirtyRect::All,
+ gpu_cache,
+ None,
+ render_task.uv_rect_kind(),
+ Eviction::Auto,
+ TargetShader::Default,
+ );
+
+ // Get the allocation details in the texture cache, and store
+ // this in the render task. The renderer will draw this
+ // task into the appropriate layer and rect of the texture
+ // cache on this frame.
+ let (texture_id, texture_layer, uv_rect, _, _, _) =
+ texture_cache.get_cache_location(&entry.handle);
+
+ let surface = StaticRenderTaskSurface::TextureCache {
+ texture: texture_id,
+ layer: texture_layer,
+ target_kind,
+ };
+
+ render_task.location = RenderTaskLocation::Static {
+ surface,
+ rect: uv_rect.to_i32(),
+ };
+ }
+
+ pub fn request_render_task<F>(
+ &mut self,
+ key: RenderTaskCacheKey,
+ texture_cache: &mut TextureCache,
+ gpu_cache: &mut GpuCache,
+ rg_builder: &mut RenderTaskGraphBuilder,
+ user_data: Option<[f32; 3]>,
+ is_opaque: bool,
+ parent: RenderTaskParent,
+ surfaces: &[SurfaceInfo],
+ f: F,
+ ) -> Result<RenderTaskCacheEntryHandle, ()>
+ where
+ F: FnOnce(&mut RenderTaskGraphBuilder) -> Result<RenderTaskId, ()>,
+ {
+ // Get the texture cache handle for this cache key,
+ // or create one.
+ let cache_entries = &mut self.cache_entries;
+ let entry_handle = self.map.entry(key).or_insert_with(|| {
+ let entry = RenderTaskCacheEntry {
+ handle: TextureCacheHandle::invalid(),
+ user_data,
+ is_opaque,
+ render_task_id: None,
+ };
+ cache_entries.insert(entry)
+ });
+ let cache_entry = cache_entries.get_mut(entry_handle);
+
+ // Check if this texture cache handle is valid.
+ if texture_cache.request(&cache_entry.handle, gpu_cache) {
+ // Invoke user closure to get render task chain
+ // to draw this into the texture cache.
+ let render_task_id = f(rg_builder)?;
+
+ cache_entry.user_data = user_data;
+ cache_entry.is_opaque = is_opaque;
+ cache_entry.render_task_id = Some(render_task_id);
+
+ let render_task = rg_builder.get_task_mut(render_task_id);
+
+ RenderTaskCache::alloc_render_task(
+ render_task,
+ cache_entry,
+ gpu_cache,
+ texture_cache,
+ );
+ }
+
+ // If this render task cache is being drawn this frame, ensure we hook up the
+ // render task for it as a dependency of any render task that uses this as
+ // an input source.
+ if let Some(render_task_id) = cache_entry.render_task_id {
+ match parent {
+ RenderTaskParent::Surface(surface_index) => {
+ // If parent is a surface, use helper fn to add this dependency,
+ // which correctly takes account of the render task configuration
+ // of the surface.
+ add_child_render_task(
+ surface_index,
+ render_task_id,
+ surfaces,
+ rg_builder
+ );
+ }
+ RenderTaskParent::RenderTask(parent_render_task_id) => {
+ // For render tasks, just add it as a direct dependency on the
+ // task graph builder.
+ rg_builder.add_dependency(
+ parent_render_task_id,
+ render_task_id,
+ );
+ }
+ }
+ }
+
+ Ok(entry_handle.weak())
+ }
+
+ pub fn get_cache_entry(
+ &self,
+ handle: &RenderTaskCacheEntryHandle,
+ ) -> &RenderTaskCacheEntry {
+ self.cache_entries
+ .get_opt(handle)
+ .expect("bug: invalid render task cache handle")
+ }
+
+ #[allow(dead_code)]
+ pub fn get_cache_item_for_render_task(&self,
+ texture_cache: &TextureCache,
+ key: &RenderTaskCacheKey)
+ -> CacheItem {
+ // Get the texture cache handle for this cache key.
+ let handle = self.map.get(key).unwrap();
+ let cache_entry = self.cache_entries.get(handle);
+ texture_cache.get(&cache_entry.handle)
+ }
+
+ #[allow(dead_code)]
+ pub fn get_allocated_size_for_render_task(&self,
+ texture_cache: &TextureCache,
+ key: &RenderTaskCacheKey)
+ -> Option<usize> {
+ let handle = self.map.get(key).unwrap();
+ let cache_entry = self.cache_entries.get(handle);
+ texture_cache.get_allocated_size(&cache_entry.handle)
+ }
+}
+
+// TODO(gw): Rounding the content rect here to device pixels is not
+// technically correct. Ideally we should ceil() here, and ensure that
+// the extra part pixel in the case of fractional sizes is correctly
+// handled. For now, just use rounding which passes the existing
+// Gecko tests.
+// Note: zero-square tasks are prohibited in WR task graph, so
+// we ensure each dimension to be at least the length of 1 after rounding.
+pub fn to_cache_size(size: LayoutSize, device_pixel_scale: &mut Scale<f32, LayoutPixel, DevicePixel>) -> DeviceIntSize {
+ let mut device_size = (size * *device_pixel_scale).round();
+
+ if device_size.width > MAX_CACHE_TASK_SIZE || device_size.height > MAX_CACHE_TASK_SIZE {
+ let scale = MAX_CACHE_TASK_SIZE / f32::max(device_size.width, device_size.height);
+ *device_pixel_scale = *device_pixel_scale * Scale::new(scale);
+ device_size = (size * *device_pixel_scale).round();
+ }
+
+ DeviceIntSize::new(
+ 1.max(device_size.width as i32),
+ 1.max(device_size.height as i32),
+ )
+}