webrender/
render_task_cache.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
6use api::{DirtyRect, ImageDescriptor, ImageDescriptorFlags, SnapshotImageKey};
7use api::units::*;
8use crate::border::BorderSegmentCacheKey;
9use crate::box_shadow::BoxShadowCacheKey;
10use crate::device::TextureFilter;
11use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
12use crate::gpu_cache::GpuCache;
13use crate::internal_types::FastHashMap;
14use crate::prim_store::image::ImageCacheKey;
15use crate::prim_store::gradient::{
16    FastLinearGradientCacheKey, LinearGradientCacheKey, RadialGradientCacheKey,
17    ConicGradientCacheKey,
18};
19use crate::prim_store::line_dec::LineDecorationCacheKey;
20use crate::resource_cache::CacheItem;
21use std::{mem, usize, f32, i32};
22use crate::surface::SurfaceBuilder;
23use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction, TargetShader};
24use crate::renderer::GpuBufferBuilderF;
25use crate::render_target::RenderTargetKind;
26use crate::render_task::{RenderTask, StaticRenderTaskSurface, RenderTaskLocation, RenderTaskKind, CachedTask};
27use crate::render_task_graph::{RenderTaskGraphBuilder, RenderTaskId};
28use euclid::Scale;
29
30const MAX_CACHE_TASK_SIZE: f32 = 4096.0;
31
32/// Describes a parent dependency for a render task. Render tasks
33/// may depend on a surface (e.g. when a surface uses a cached border)
34/// or an arbitrary render task (e.g. when a clip mask uses a blurred
35/// box-shadow input).
36pub enum RenderTaskParent {
37    /// Parent is a surface
38    Surface,
39    /// Parent is a render task
40    RenderTask(RenderTaskId),
41}
42
43#[derive(Clone, Debug, Hash, PartialEq, Eq)]
44#[cfg_attr(feature = "capture", derive(Serialize))]
45#[cfg_attr(feature = "replay", derive(Deserialize))]
46pub enum RenderTaskCacheKeyKind {
47    BoxShadow(BoxShadowCacheKey),
48    Image(ImageCacheKey),
49    BorderSegment(BorderSegmentCacheKey),
50    LineDecoration(LineDecorationCacheKey),
51    FastLinearGradient(FastLinearGradientCacheKey),
52    LinearGradient(LinearGradientCacheKey),
53    RadialGradient(RadialGradientCacheKey),
54    ConicGradient(ConicGradientCacheKey),
55    Snapshot(SnapshotImageKey),
56}
57
58#[derive(Clone, Debug, Hash, PartialEq, Eq)]
59#[cfg_attr(feature = "capture", derive(Serialize))]
60#[cfg_attr(feature = "replay", derive(Deserialize))]
61pub struct RenderTaskCacheKey {
62    pub size: DeviceIntSize,
63    pub kind: RenderTaskCacheKeyKind,
64}
65
66#[derive(Debug)]
67#[cfg_attr(feature = "capture", derive(Serialize))]
68#[cfg_attr(feature = "replay", derive(Deserialize))]
69pub struct RenderTaskCacheEntry {
70    user_data: Option<[f32; 4]>,
71    target_kind: RenderTargetKind,
72    is_opaque: bool,
73    frame_id: u64,
74    pub handle: TextureCacheHandle,
75    /// If a render task was generated for this cache entry on _this_ frame,
76    /// we need to track the task id here. This allows us to hook it up as
77    /// a dependency of any parent tasks that make a reqiest from the render
78    /// task cache.
79    pub render_task_id: Option<RenderTaskId>,
80}
81
82#[derive(Debug, MallocSizeOf)]
83#[cfg_attr(feature = "capture", derive(Serialize))]
84pub enum RenderTaskCacheMarker {}
85
86// A cache of render tasks that are stored in the texture
87// cache for usage across frames.
88#[derive(Debug)]
89#[cfg_attr(feature = "capture", derive(Serialize))]
90#[cfg_attr(feature = "replay", derive(Deserialize))]
91pub struct RenderTaskCache {
92    map: FastHashMap<RenderTaskCacheKey, FreeListHandle<RenderTaskCacheMarker>>,
93    cache_entries: FreeList<RenderTaskCacheEntry, RenderTaskCacheMarker>,
94    frame_id: u64,
95}
96
97pub type RenderTaskCacheEntryHandle = WeakFreeListHandle<RenderTaskCacheMarker>;
98
99impl RenderTaskCache {
100    pub fn new() -> Self {
101        RenderTaskCache {
102            map: FastHashMap::default(),
103            cache_entries: FreeList::new(),
104            frame_id: 0,
105        }
106    }
107
108    pub fn clear(&mut self) {
109        self.map.clear();
110        self.cache_entries.clear();
111    }
112
113    pub fn begin_frame(
114        &mut self,
115        texture_cache: &mut TextureCache,
116    ) {
117        self.frame_id += 1;
118        profile_scope!("begin_frame");
119        // Drop any items from the cache that have been
120        // evicted from the texture cache.
121        //
122        // This isn't actually necessary for the texture
123        // cache to be able to evict old render tasks.
124        // It will evict render tasks as required, since
125        // the access time in the texture cache entry will
126        // be stale if this task hasn't been requested
127        // for a while.
128        //
129        // Nonetheless, we should remove stale entries
130        // from here so that this hash map doesn't
131        // grow indefinitely!
132        let cache_entries = &mut self.cache_entries;
133        let frame_id = self.frame_id;
134
135        self.map.retain(|_, handle| {
136            let mut retain = texture_cache.is_allocated(
137                &cache_entries.get(handle).handle,
138            );
139            if retain {
140                let entry = cache_entries.get_mut(&handle);
141                if frame_id > entry.frame_id + 10 {
142                    texture_cache.evict_handle(&entry.handle);
143                    retain = false;
144                }
145            }
146
147            if !retain {
148                let handle = mem::replace(handle, FreeListHandle::invalid());
149                cache_entries.free(handle);
150            }
151
152            retain
153        });
154
155        // Clear out the render task ID of any remaining cache entries that were drawn
156        // on the previous frame, so we don't accidentally hook up stale dependencies
157        // when building the frame graph.
158        for (_, handle) in &self.map {
159            let entry = self.cache_entries.get_mut(handle);
160            entry.render_task_id = None;
161        }
162    }
163
164    fn alloc_render_task(
165        size: DeviceIntSize,
166        render_task: &mut RenderTask,
167        entry: &mut RenderTaskCacheEntry,
168        gpu_cache: &mut GpuCache,
169        texture_cache: &mut TextureCache,
170    ) {
171        // Find out what size to alloc in the texture cache.
172        let target_kind = render_task.target_kind();
173
174        // Select the right texture page to allocate from.
175        let image_format = match target_kind {
176            RenderTargetKind::Color => texture_cache.shared_color_expected_format(),
177            RenderTargetKind::Alpha => texture_cache.shared_alpha_expected_format(),
178        };
179
180        let flags = if entry.is_opaque {
181            ImageDescriptorFlags::IS_OPAQUE
182        } else {
183            ImageDescriptorFlags::empty()
184        };
185
186        let descriptor = ImageDescriptor::new(
187            size.width,
188            size.height,
189            image_format,
190            flags,
191        );
192
193        // Allocate space in the texture cache, but don't supply
194        // and CPU-side data to be uploaded.
195        texture_cache.update(
196            &mut entry.handle,
197            descriptor,
198            TextureFilter::Linear,
199            None,
200            entry.user_data.unwrap_or([0.0; 4]),
201            DirtyRect::All,
202            gpu_cache,
203            None,
204            render_task.uv_rect_kind(),
205            Eviction::Auto,
206            TargetShader::Default,
207        );
208
209        // Get the allocation details in the texture cache, and store
210        // this in the render task. The renderer will draw this task
211        // into the appropriate rect of the texture cache on this frame.
212        let (texture_id, uv_rect, _, _, _) =
213            texture_cache.get_cache_location(&entry.handle);
214
215        let surface = StaticRenderTaskSurface::TextureCache {
216            texture: texture_id,
217            target_kind,
218        };
219
220        render_task.location = RenderTaskLocation::Static {
221            surface,
222            rect: uv_rect.to_i32(),
223        };
224    }
225
226    pub fn request_render_task(
227        &mut self,
228        key: Option<RenderTaskCacheKey>,
229        texture_cache: &mut TextureCache,
230        is_opaque: bool,
231        parent: RenderTaskParent,
232        gpu_cache: &mut GpuCache,
233        gpu_buffer_builder: &mut GpuBufferBuilderF,
234        rg_builder: &mut RenderTaskGraphBuilder,
235        surface_builder: &mut SurfaceBuilder,
236        f: &mut dyn FnMut(&mut RenderTaskGraphBuilder, &mut GpuBufferBuilderF, &mut GpuCache) -> RenderTaskId,
237    ) -> RenderTaskId {
238        // If this render task cache is being drawn this frame, ensure we hook up the
239        // render task for it as a dependency of any render task that uses this as
240        // an input source.
241        let (task_id, rendered_this_frame) = match key {
242            None => (f(rg_builder, gpu_buffer_builder, gpu_cache), true),
243            Some(key) => self.request_render_task_impl(
244                key,
245                is_opaque,
246                texture_cache,
247                gpu_cache,
248                gpu_buffer_builder,
249                rg_builder,
250                f
251            )
252        };
253
254        if rendered_this_frame {
255            match parent {
256                RenderTaskParent::Surface => {
257                    // If parent is a surface, use helper fn to add this dependency,
258                    // which correctly takes account of the render task configuration
259                    // of the surface.
260                    surface_builder.add_child_render_task(
261                        task_id,
262                        rg_builder,
263                    );
264                }
265                RenderTaskParent::RenderTask(parent_render_task_id) => {
266                    // For render tasks, just add it as a direct dependency on the
267                    // task graph builder.
268                    rg_builder.add_dependency(
269                        parent_render_task_id,
270                        task_id,
271                    );
272                }
273            }
274        }
275
276        task_id
277    }
278
279    /// Returns the render task id and a boolean indicating whether the
280    /// task was rendered this frame (was not already in cache).
281    fn request_render_task_impl(
282        &mut self,
283        key: RenderTaskCacheKey,
284        is_opaque: bool,
285        texture_cache: &mut TextureCache,
286        gpu_cache: &mut GpuCache,
287        gpu_buffer_builder: &mut GpuBufferBuilderF,
288        rg_builder: &mut RenderTaskGraphBuilder,
289        f: &mut dyn FnMut(&mut RenderTaskGraphBuilder, &mut GpuBufferBuilderF, &mut GpuCache) -> RenderTaskId,
290    ) -> (RenderTaskId, bool) {
291        let frame_id = self.frame_id;
292        let size = key.size;
293        // Get the texture cache handle for this cache key,
294        // or create one.
295        let cache_entries = &mut self.cache_entries;
296        let entry_handle = self.map.entry(key).or_insert_with(|| {
297            let entry = RenderTaskCacheEntry {
298                handle: TextureCacheHandle::invalid(),
299                user_data: None,
300                target_kind: RenderTargetKind::Color, // will be set below.
301                is_opaque,
302                frame_id,
303                render_task_id: None,
304            };
305            cache_entries.insert(entry)
306        });
307        let cache_entry = cache_entries.get_mut(entry_handle);
308        cache_entry.frame_id = self.frame_id;
309
310        // Check if this texture cache handle is valid.
311        if texture_cache.request(&cache_entry.handle, gpu_cache) {
312            // Invoke user closure to get render task chain
313            // to draw this into the texture cache.
314            let render_task_id = f(rg_builder, gpu_buffer_builder, gpu_cache);
315
316            cache_entry.user_data = None;
317            cache_entry.is_opaque = is_opaque;
318            cache_entry.render_task_id = Some(render_task_id);
319
320            let render_task = rg_builder.get_task_mut(render_task_id);
321            let task_size = render_task.location.size();
322
323            render_task.mark_cached(entry_handle.weak());
324            cache_entry.target_kind = render_task.kind.target_kind();
325
326            RenderTaskCache::alloc_render_task(
327                task_size,
328                render_task,
329                cache_entry,
330                gpu_cache,
331                texture_cache,
332            );
333        }
334
335        if let Some(render_task_id) = cache_entry.render_task_id {
336            return (render_task_id, true);
337        }
338
339        let target_kind = cache_entry.target_kind;
340        let mut task = RenderTask::new(
341            RenderTaskLocation::CacheRequest { size, },
342            RenderTaskKind::Cached(CachedTask {
343                target_kind,
344            }),
345        );
346        task.mark_cached(entry_handle.weak());
347        let render_task_id = rg_builder.add().init(task);
348
349        (render_task_id, false)
350    }
351
352    pub fn get_cache_entry(
353        &self,
354        handle: &RenderTaskCacheEntryHandle,
355    ) -> &RenderTaskCacheEntry {
356        self.cache_entries
357            .get_opt(handle)
358            .expect("bug: invalid render task cache handle")
359    }
360
361    #[allow(dead_code)]
362    pub fn get_cache_item_for_render_task(&self,
363                                          texture_cache: &TextureCache,
364                                          key: &RenderTaskCacheKey)
365                                          -> CacheItem {
366        // Get the texture cache handle for this cache key.
367        let handle = self.map.get(key).unwrap();
368        let cache_entry = self.cache_entries.get(handle);
369        texture_cache.get(&cache_entry.handle)
370    }
371
372    #[allow(dead_code)]
373    pub fn get_allocated_size_for_render_task(&self,
374                                              texture_cache: &TextureCache,
375                                              key: &RenderTaskCacheKey)
376                                              -> Option<usize> {
377        let handle = self.map.get(key).unwrap();
378        let cache_entry = self.cache_entries.get(handle);
379        texture_cache.get_allocated_size(&cache_entry.handle)
380    }
381}
382
383// TODO(gw): Rounding the content rect here to device pixels is not
384// technically correct. Ideally we should ceil() here, and ensure that
385// the extra part pixel in the case of fractional sizes is correctly
386// handled. For now, just use rounding which passes the existing
387// Gecko tests.
388// Note: zero-square tasks are prohibited in WR task graph, so
389// we ensure each dimension to be at least the length of 1 after rounding.
390pub fn to_cache_size(size: LayoutSize, device_pixel_scale: &mut Scale<f32, LayoutPixel, DevicePixel>) -> DeviceIntSize {
391    let mut device_size = (size * *device_pixel_scale).round();
392
393    if device_size.width > MAX_CACHE_TASK_SIZE || device_size.height > MAX_CACHE_TASK_SIZE {
394        let scale = MAX_CACHE_TASK_SIZE / f32::max(device_size.width, device_size.height);
395        *device_pixel_scale = *device_pixel_scale * Scale::new(scale);
396        device_size = (size * *device_pixel_scale).round();
397    }
398
399    DeviceIntSize::new(
400        1.max(device_size.width as i32),
401        1.max(device_size.height as i32),
402    )
403}