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            false,
208        );
209
210        // Get the allocation details in the texture cache, and store
211        // this in the render task. The renderer will draw this task
212        // into the appropriate rect of the texture cache on this frame.
213        let (texture_id, uv_rect, _, _, _) =
214            texture_cache.get_cache_location(&entry.handle);
215
216        let surface = StaticRenderTaskSurface::TextureCache {
217            texture: texture_id,
218            target_kind,
219        };
220
221        render_task.location = RenderTaskLocation::Static {
222            surface,
223            rect: uv_rect.to_i32(),
224        };
225    }
226
227    pub fn request_render_task(
228        &mut self,
229        key: Option<RenderTaskCacheKey>,
230        texture_cache: &mut TextureCache,
231        is_opaque: bool,
232        parent: RenderTaskParent,
233        gpu_cache: &mut GpuCache,
234        gpu_buffer_builder: &mut GpuBufferBuilderF,
235        rg_builder: &mut RenderTaskGraphBuilder,
236        surface_builder: &mut SurfaceBuilder,
237        f: &mut dyn FnMut(&mut RenderTaskGraphBuilder, &mut GpuBufferBuilderF, &mut GpuCache) -> RenderTaskId,
238    ) -> RenderTaskId {
239        // If this render task cache is being drawn this frame, ensure we hook up the
240        // render task for it as a dependency of any render task that uses this as
241        // an input source.
242        let (task_id, rendered_this_frame) = match key {
243            None => (f(rg_builder, gpu_buffer_builder, gpu_cache), true),
244            Some(key) => self.request_render_task_impl(
245                key,
246                is_opaque,
247                texture_cache,
248                gpu_cache,
249                gpu_buffer_builder,
250                rg_builder,
251                f
252            )
253        };
254
255        if rendered_this_frame {
256            match parent {
257                RenderTaskParent::Surface => {
258                    // If parent is a surface, use helper fn to add this dependency,
259                    // which correctly takes account of the render task configuration
260                    // of the surface.
261                    surface_builder.add_child_render_task(
262                        task_id,
263                        rg_builder,
264                    );
265                }
266                RenderTaskParent::RenderTask(parent_render_task_id) => {
267                    // For render tasks, just add it as a direct dependency on the
268                    // task graph builder.
269                    rg_builder.add_dependency(
270                        parent_render_task_id,
271                        task_id,
272                    );
273                }
274            }
275        }
276
277        task_id
278    }
279
280    /// Returns the render task id and a boolean indicating whether the
281    /// task was rendered this frame (was not already in cache).
282    fn request_render_task_impl(
283        &mut self,
284        key: RenderTaskCacheKey,
285        is_opaque: bool,
286        texture_cache: &mut TextureCache,
287        gpu_cache: &mut GpuCache,
288        gpu_buffer_builder: &mut GpuBufferBuilderF,
289        rg_builder: &mut RenderTaskGraphBuilder,
290        f: &mut dyn FnMut(&mut RenderTaskGraphBuilder, &mut GpuBufferBuilderF, &mut GpuCache) -> RenderTaskId,
291    ) -> (RenderTaskId, bool) {
292        let frame_id = self.frame_id;
293        let size = key.size;
294        // Get the texture cache handle for this cache key,
295        // or create one.
296        let cache_entries = &mut self.cache_entries;
297        let entry_handle = self.map.entry(key).or_insert_with(|| {
298            let entry = RenderTaskCacheEntry {
299                handle: TextureCacheHandle::invalid(),
300                user_data: None,
301                target_kind: RenderTargetKind::Color, // will be set below.
302                is_opaque,
303                frame_id,
304                render_task_id: None,
305            };
306            cache_entries.insert(entry)
307        });
308        let cache_entry = cache_entries.get_mut(entry_handle);
309        cache_entry.frame_id = self.frame_id;
310
311        // Check if this texture cache handle is valid.
312        if texture_cache.request(&cache_entry.handle, gpu_cache) {
313            // Invoke user closure to get render task chain
314            // to draw this into the texture cache.
315            let render_task_id = f(rg_builder, gpu_buffer_builder, gpu_cache);
316
317            cache_entry.user_data = None;
318            cache_entry.is_opaque = is_opaque;
319            cache_entry.render_task_id = Some(render_task_id);
320
321            let render_task = rg_builder.get_task_mut(render_task_id);
322            let task_size = render_task.location.size();
323
324            render_task.mark_cached(entry_handle.weak());
325            cache_entry.target_kind = render_task.kind.target_kind();
326
327            RenderTaskCache::alloc_render_task(
328                task_size,
329                render_task,
330                cache_entry,
331                gpu_cache,
332                texture_cache,
333            );
334        }
335
336        if let Some(render_task_id) = cache_entry.render_task_id {
337            return (render_task_id, true);
338        }
339
340        let target_kind = cache_entry.target_kind;
341        let mut task = RenderTask::new(
342            RenderTaskLocation::CacheRequest { size, },
343            RenderTaskKind::Cached(CachedTask {
344                target_kind,
345            }),
346        );
347        task.mark_cached(entry_handle.weak());
348        let render_task_id = rg_builder.add().init(task);
349
350        (render_task_id, false)
351    }
352
353    pub fn get_cache_entry(
354        &self,
355        handle: &RenderTaskCacheEntryHandle,
356    ) -> &RenderTaskCacheEntry {
357        self.cache_entries
358            .get_opt(handle)
359            .expect("bug: invalid render task cache handle")
360    }
361
362    #[allow(dead_code)]
363    pub fn get_cache_item_for_render_task(&self,
364                                          texture_cache: &TextureCache,
365                                          key: &RenderTaskCacheKey)
366                                          -> CacheItem {
367        // Get the texture cache handle for this cache key.
368        let handle = self.map.get(key).unwrap();
369        let cache_entry = self.cache_entries.get(handle);
370        texture_cache.get(&cache_entry.handle)
371    }
372
373    #[allow(dead_code)]
374    pub fn get_allocated_size_for_render_task(&self,
375                                              texture_cache: &TextureCache,
376                                              key: &RenderTaskCacheKey)
377                                              -> Option<usize> {
378        let handle = self.map.get(key).unwrap();
379        let cache_entry = self.cache_entries.get(handle);
380        texture_cache.get_allocated_size(&cache_entry.handle)
381    }
382}
383
384// TODO(gw): Rounding the content rect here to device pixels is not
385// technically correct. Ideally we should ceil() here, and ensure that
386// the extra part pixel in the case of fractional sizes is correctly
387// handled. For now, just use rounding which passes the existing
388// Gecko tests.
389// Note: zero-square tasks are prohibited in WR task graph, so
390// we ensure each dimension to be at least the length of 1 after rounding.
391pub fn to_cache_size(size: LayoutSize, device_pixel_scale: &mut Scale<f32, LayoutPixel, DevicePixel>) -> DeviceIntSize {
392    let mut device_size = (size * *device_pixel_scale).round();
393
394    if device_size.width > MAX_CACHE_TASK_SIZE || device_size.height > MAX_CACHE_TASK_SIZE {
395        let scale = MAX_CACHE_TASK_SIZE / f32::max(device_size.width, device_size.height);
396        *device_pixel_scale = *device_pixel_scale * Scale::new(scale);
397        device_size = (size * *device_pixel_scale).round();
398    }
399
400    DeviceIntSize::new(
401        1.max(device_size.width as i32),
402        1.max(device_size.height as i32),
403    )
404}