Skip to main content

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