Skip to main content

webrender/prim_store/
image.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
5use api::{
6    AlphaType, ColorDepth, ColorF, ColorRange, ColorU, ExternalImageData, ExternalImageType, ImageBufferKind, ImageKey as ApiImageKey, ImageRendering, PremultipliedColorF, RasterSpace, Shadow, YuvColorSpace, YuvFormat
7};
8use api::units::*;
9use euclid::point2;
10use crate::clip::{ClipChainInstance, ClipIntern};
11use crate::command_buffer::CommandBufferIndex;
12use crate::gpu_types::{ImageBrushPrimitiveData, YuvPrimitive};
13use crate::pattern::image::ImagePattern;
14use crate::quad::QuadTransformState;
15use crate::renderer::{GpuBufferAddress, GpuBufferBuilderF, GpuBufferWriterF};
16use crate::scene_building::{CreateShadow, IsVisible};
17use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext};
18use crate::intern::{DataStore, Handle as InternHandle, InternDebug, Internable};
19use crate::internal_types::LayoutPrimitiveInfo;
20use crate::prim_store::{
21    EdgeMask, InternablePrimitive, PrimKey, PrimTemplate, PrimTemplateCommonData, PrimitiveInstanceIndex, PrimitiveKind, PrimitiveOpacity, PrimitiveScratchBuffer, PrimitiveStore, SizeKey
22};
23use crate::prim_store::storage;
24use crate::render_target::RenderTargetKind;
25use crate::render_task_graph::RenderTaskId;
26use crate::render_task::RenderTask;
27use crate::render_task_cache::{
28    RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskParent
29};
30use crate::resource_cache::{ImageRequest, ImageProperties, ResourceCache};
31use crate::visibility::compute_conservative_visible_rect;
32use crate::spatial_tree::SpatialNodeIndex;
33use crate::{image_tiling, quad};
34
35#[derive(Debug)]
36#[cfg_attr(feature = "capture", derive(Serialize))]
37#[cfg_attr(feature = "replay", derive(Deserialize))]
38pub struct VisibleImageTile {
39    pub src_color: RenderTaskId,
40    pub edge_flags: EdgeMask,
41    pub local_rect: LayoutRect,
42    pub local_clip_rect: LayoutRect,
43}
44
45// Key that identifies a unique (partial) image that is being
46// stored in the render task cache.
47#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
48#[cfg_attr(feature = "capture", derive(Serialize))]
49#[cfg_attr(feature = "replay", derive(Deserialize))]
50pub struct ImageCacheKey {
51    pub request: ImageRequest,
52    pub texel_rect: Option<DeviceIntRect>,
53}
54
55/// Per-frame scratch data for an Image primitive. Captures the per-frame
56/// outputs of `ImageData::update`: the source render task (or a Range of
57/// per-tile tasks for tiled images), normalized-uvs flag, image
58/// adjustment from snapshots, and a tight local clip rect derived from
59/// the prim's clip chain. Pushed during prepare and read by batch.
60#[derive(Debug)]
61#[cfg_attr(feature = "capture", derive(Serialize))]
62pub struct ImageScratch {
63    /// Range into `PrimitiveFrameScratch.visible_image_tiles` for tiled
64    /// images. Empty for non-tiled images.
65    pub visible_tiles: storage::Range<VisibleImageTile>,
66    /// Source render task for non-tiled images.
67    pub src_color: Option<RenderTaskId>,
68    /// Whether to render with normalized UVs (set for some external
69    /// images).
70    pub normalized_uvs: bool,
71    /// Adjustment applied when sampling from a wider source (e.g.
72    /// snapshot images).
73    pub adjustment: AdjustedImageSource,
74    /// Tight local clip rect derived from the prim's clip chain. We
75    /// rely on having this in cases where decomposing repeated images
76    /// can produce primitives that partially cover the original image
77    /// rect, and for snapshot images where the snapshot area is
78    /// tighter than the rasterized area.
79    pub tight_local_clip_rect: LayoutRect,
80    /// Whether this draw needs the repetition-capable image shader.
81    /// Set to false when the stretch_size covers the prim (no tiling)
82    /// or when the image was decomposed into per-tile prims at
83    /// scene-build time. Read by batch to choose between brush_image
84    /// and brush_fast_image.
85    pub may_need_repetition: bool,
86    /// Address of the per-instance image-brush GPU block. Lives here
87    /// (rather than on the template's `PrimTemplateCommonData`) because
88    /// the resolved stretch size and adjustment-mapped values vary per
89    /// instance, even when many instances share a single template.
90    pub gpu_address: GpuBufferAddress,
91}
92
93impl ImageScratch {
94    pub fn empty() -> Self {
95        ImageScratch {
96            visible_tiles: storage::Range::empty(),
97            src_color: None,
98            normalized_uvs: false,
99            adjustment: AdjustedImageSource::new(),
100            tight_local_clip_rect: LayoutRect::zero(),
101            may_need_repetition: true,
102            gpu_address: GpuBufferAddress::INVALID,
103        }
104    }
105}
106
107/// How to compute the effective stretch size for an image primitive, per
108/// axis. `FillsPrim` resolves to the (snapped) prim-rect extent at
109/// frame-build so the value sent to the GPU lands on the snapped pixel
110/// grid. `Explicit` keeps the gecko-specified value verbatim. Per-axis
111/// because gecko can specify a background tile that fills the prim on
112/// one axis but tiles on the other (e.g. `background-repeat: repeat-y`
113/// with `background-size: 116.8px 0.8px`).
114#[cfg_attr(feature = "capture", derive(Serialize))]
115#[cfg_attr(feature = "replay", derive(Deserialize))]
116#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, MallocSizeOf)]
117pub struct StretchSizeKey {
118    pub size: SizeKey,
119    pub fills_width: bool,
120    pub fills_height: bool,
121}
122
123impl StretchSizeKey {
124    /// Both axes fill the prim. The stored size is unused; normalised
125    /// to zero so different prim sizes still intern to the same key.
126    pub fn fills_prim() -> Self {
127        StretchSizeKey {
128            size: LayoutSize::zero().into(),
129            fills_width: true,
130            fills_height: true,
131        }
132    }
133}
134
135#[cfg_attr(feature = "capture", derive(Serialize))]
136#[cfg_attr(feature = "replay", derive(Deserialize))]
137#[derive(Debug, Clone, Copy, MallocSizeOf)]
138pub struct StretchSize {
139    pub size: LayoutSize,
140    pub fills_width: bool,
141    pub fills_height: bool,
142}
143
144impl From<StretchSizeKey> for StretchSize {
145    fn from(k: StretchSizeKey) -> Self {
146        StretchSize {
147            size: k.size.into(),
148            fills_width: k.fills_width,
149            fills_height: k.fills_height,
150        }
151    }
152}
153
154impl StretchSize {
155    /// Resolve to the LayoutSize used for the GPU shader and tiling math.
156    /// Per-axis: an axis flagged `fills_*` resolves to the snapped prim
157    /// rect's extent on that axis; the other axis keeps the stored size.
158    pub fn resolve(self, prim_rect: &LayoutRect) -> LayoutSize {
159        let prim_size = prim_rect.size();
160        LayoutSize::new(
161            if self.fills_width { prim_size.width } else { self.size.width },
162            if self.fills_height { prim_size.height } else { self.size.height },
163        )
164    }
165}
166
167#[cfg_attr(feature = "capture", derive(Serialize))]
168#[cfg_attr(feature = "replay", derive(Deserialize))]
169#[derive(Debug, Clone, Eq, PartialEq, MallocSizeOf, Hash)]
170pub struct Image {
171    pub key: ApiImageKey,
172    pub stretch_size: StretchSizeKey,
173    pub tile_spacing: SizeKey,
174    pub color: ColorU,
175    pub image_rendering: ImageRendering,
176    pub alpha_type: AlphaType,
177}
178
179pub type ImageKey = PrimKey<Image>;
180
181impl ImageKey {
182    pub fn new(
183        info: &LayoutPrimitiveInfo,
184        image: Image,
185    ) -> Self {
186        ImageKey {
187            common: info.into(),
188            kind: image,
189        }
190    }
191}
192
193impl InternDebug for ImageKey {}
194
195#[cfg_attr(feature = "capture", derive(Serialize))]
196#[cfg_attr(feature = "replay", derive(Deserialize))]
197#[derive(Debug, MallocSizeOf)]
198pub struct ImageData {
199    pub key: ApiImageKey,
200    pub stretch_size: StretchSize,
201    pub tile_spacing: LayoutSize,
202    pub color: ColorF,
203    pub image_rendering: ImageRendering,
204    pub alpha_type: AlphaType,
205}
206
207impl From<Image> for ImageData {
208    fn from(image: Image) -> Self {
209        ImageData {
210            key: image.key,
211            color: image.color.into(),
212            stretch_size: image.stretch_size.into(),
213            tile_spacing: image.tile_spacing.into(),
214            image_rendering: image.image_rendering,
215            alpha_type: image.alpha_type,
216        }
217    }
218}
219
220impl ImageData {
221    /// Update the GPU cache for a given primitive template. This may be called multiple
222    /// times per frame, by each primitive reference that refers to this interned
223    /// template. The initial request call to the GPU cache ensures that work is only
224    /// done if the cache entry is invalid (due to first use or eviction).
225    pub fn update(
226        &mut self,
227        common: &mut PrimTemplateCommonData,
228        prim_instance_index: PrimitiveInstanceIndex,
229        prim_spatial_node_index: SpatialNodeIndex,
230        frame_state: &mut FrameBuildingState,
231        frame_context: &FrameBuildingContext,
232        prim_rect: LayoutRect,
233        scratch: &mut PrimitiveScratchBuffer,
234    ) -> storage::Index<ImageScratch> {
235
236        let image_properties = frame_state
237            .resource_cache
238            .get_image_properties(self.key);
239
240        common.opacity = match &image_properties {
241            Some(properties) => {
242                if properties.descriptor.is_opaque() {
243                    PrimitiveOpacity::from_alpha(self.color.a)
244                } else {
245                    PrimitiveOpacity::translucent()
246                }
247            }
248            None => PrimitiveOpacity::opaque(),
249        };
250
251        let request = ImageRequest {
252            key: self.key,
253            rendering: self.image_rendering,
254            tile: None,
255        };
256
257        // Tighten the clip rect because decomposing the repeated image can
258        // produce primitives that are partially covering the original image
259        // rect and we want to clip these extra parts out.
260        // We also rely on having a tight clip rect in some cases other than
261        // tiled/repeated images, for example when rendering a snapshot image
262        // where the snapshot area is tighter than the rasterized area.
263        let tight_clip_rect = scratch.frame.draws[prim_instance_index.0 as usize]
264            .clip_chain
265            .local_clip_rect
266            .intersection(&prim_rect).unwrap();
267
268        let effective_stretch_size = self.stretch_size.resolve(&prim_rect);
269
270        let mut image_scratch = ImageScratch::empty();
271        image_scratch.tight_local_clip_rect = tight_clip_rect;
272        if effective_stretch_size.width >= prim_rect.size().width
273            && effective_stretch_size.height >= prim_rect.size().height
274        {
275            image_scratch.may_need_repetition = false;
276        }
277
278        match image_properties {
279            // Non-tiled (most common) path.
280            Some(ImageProperties { tiling: None, ref descriptor, ref external_image, adjustment, .. }) => {
281                image_scratch.adjustment = adjustment;
282
283                let mut size = frame_state.resource_cache.request_image(
284                    request,
285                    &mut frame_state.frame_gpu_data.f32,
286                );
287
288                let mut task_id = frame_state.rg_builder.add().init(
289                    RenderTask::new_image(size, request, false)
290                );
291
292                if let Some(external_image) = external_image {
293                    // On some devices we cannot render from an ImageBufferKind::TextureExternal
294                    // source using most shaders, so must peform a copy to a regular texture first.
295                    let requires_copy = frame_context.fb_config.external_images_require_copy &&
296                        external_image.image_type ==
297                            ExternalImageType::TextureHandle(ImageBufferKind::TextureExternal);
298
299                    if requires_copy {
300                        let target_kind = if descriptor.format.bytes_per_pixel() == 1 {
301                            RenderTargetKind::Alpha
302                        } else {
303                            RenderTargetKind::Color
304                        };
305
306                        task_id = RenderTask::new_scaling(
307                            task_id,
308                            frame_state.rg_builder,
309                            target_kind,
310                            size
311                        );
312
313                        frame_state.surface_builder.add_child_render_task(
314                            task_id,
315                            frame_state.rg_builder,
316                        );
317                    }
318
319                    // Ensure the instance is rendered using normalized_uvs if the external image
320                    // requires so. If we inserted a scale above this is not required as the
321                    // instance is rendered from a render task rather than the external image.
322                    if !requires_copy {
323                        image_scratch.normalized_uvs = external_image.normalized_uvs;
324                    }
325                }
326
327                // Every frame, for cached items, we need to request the render
328                // task cache item. The closure will be invoked on the first
329                // time through, and any time the render task output has been
330                // evicted from the texture cache.
331                if self.tile_spacing == LayoutSize::zero() {
332                    // Most common case.
333                    image_scratch.src_color = Some(task_id);
334                } else {
335                    let padding = DeviceIntSideOffsets::new(
336                        0,
337                        (self.tile_spacing.width * size.width as f32 / effective_stretch_size.width) as i32,
338                        (self.tile_spacing.height * size.height as f32 / effective_stretch_size.height) as i32,
339                        0,
340                    );
341
342                    size.width += padding.horizontal();
343                    size.height += padding.vertical();
344
345                    if padding != DeviceIntSideOffsets::zero() {
346                        common.opacity = PrimitiveOpacity::translucent();
347                    }
348
349                    let image_cache_key = ImageCacheKey {
350                        request,
351                        texel_rect: None,
352                    };
353                    let target_kind = if descriptor.format.bytes_per_pixel() == 1 {
354                        RenderTargetKind::Alpha
355                    } else {
356                        RenderTargetKind::Color
357                    };
358
359                    // Request a pre-rendered image task.
360                    let cached_task_handle = frame_state.resource_cache.request_render_task(
361                        Some(RenderTaskCacheKey {
362                            origin: DeviceIntPoint::zero(),
363                            size,
364                            kind: RenderTaskCacheKeyKind::Image(image_cache_key),
365                        }),
366                        descriptor.is_opaque(),
367                        RenderTaskParent::Surface,
368                        &mut frame_state.frame_gpu_data.f32,
369                        frame_state.rg_builder,
370                        &mut frame_state.surface_builder,
371                        &mut |rg_builder, _| {
372                            // Create a task to blit from the texture cache to
373                            // a normal transient render task surface.
374                            // TODO: figure out if/when we can do a blit instead.
375                            let cache_to_target_task_id = RenderTask::new_scaling_with_padding(
376                                task_id,
377                                rg_builder,
378                                target_kind,
379                                size,
380                                padding,
381                            );
382
383                            // Create a task to blit the rect from the child render
384                            // task above back into the right spot in the persistent
385                            // render target cache.
386                            RenderTask::new_blit(
387                                size,
388                                cache_to_target_task_id,
389                                size.into(),
390                                rg_builder,
391                            )
392                        }
393                    );
394
395                    image_scratch.src_color = Some(cached_task_handle);
396                }
397            }
398            // Tiled image path.
399            Some(ImageProperties { tiling: Some(tile_size), visible_rect, .. }) => {
400                // we'll  have a source handle per visible tile instead.
401                image_scratch.src_color = None;
402
403                // TODO: rename the blob's visible_rect into something that doesn't conflict
404                // with the terminology we use during culling since it's not really the same
405                // thing.
406                let active_rect = visible_rect;
407
408                let visible_rect = compute_conservative_visible_rect(
409                    &scratch.frame.draws[prim_instance_index.0 as usize].clip_chain,
410                    frame_state.current_dirty_region().combined,
411                    frame_state.current_dirty_region().visibility_spatial_node,
412                    prim_spatial_node_index,
413                    frame_context.spatial_tree,
414                );
415
416                let base_edge_flags = edge_flags_for_tile_spacing(&self.tile_spacing);
417
418                let stride = effective_stretch_size + self.tile_spacing;
419
420                // We are performing the decomposition on the CPU here, no need to
421                // have it in the shader.
422                image_scratch.may_need_repetition = false;
423
424                let repetitions = image_tiling::repetitions(
425                    &prim_rect,
426                    &visible_rect,
427                    stride,
428                );
429
430                let tiles_open = scratch.frame.visible_image_tiles.open_range();
431                for image_tiling::Repetition { origin, edge_flags } in repetitions {
432                    let edge_flags = base_edge_flags | edge_flags;
433
434                    let layout_image_rect = LayoutRect::from_origin_and_size(
435                        origin,
436                        effective_stretch_size,
437                    );
438
439                    let tiles = image_tiling::tiles(
440                        &layout_image_rect,
441                        &visible_rect,
442                        &active_rect,
443                        tile_size as i32,
444                    );
445
446                    for tile in tiles {
447                        let request = request.with_tile(tile.offset);
448                        let size = frame_state.resource_cache.request_image(
449                            request,
450                            &mut frame_state.frame_gpu_data.f32,
451                        );
452
453                        let task_id = frame_state.rg_builder.add().init(
454                            RenderTask::new_image(size, request, false)
455                        );
456
457                        scratch.frame.visible_image_tiles.push(VisibleImageTile {
458                            src_color: task_id,
459                            edge_flags: tile.edge_flags & edge_flags,
460                            local_rect: tile.rect,
461                            local_clip_rect: tight_clip_rect,
462                        });
463                    }
464                }
465                image_scratch.visible_tiles = scratch.frame.visible_image_tiles.close_range(tiles_open);
466
467                if image_scratch.visible_tiles.is_empty() {
468                    // Mark as invisible
469                    scratch.frame.draws[prim_instance_index.0 as usize].reset();
470                }
471            }
472            None => {
473                image_scratch.src_color = None;
474            }
475        }
476
477        if let Some(task_id) = frame_state.image_dependencies.get(&self.key) {
478            frame_state.surface_builder.add_child_render_task(
479                *task_id,
480                frame_state.rg_builder
481            );
482        }
483
484        let mut writer = frame_state.frame_gpu_data.f32.write_blocks(3);
485        self.write_prim_gpu_blocks(&image_scratch.adjustment, effective_stretch_size, &mut writer);
486        image_scratch.gpu_address = writer.finish();
487
488        scratch.frame.images.push(image_scratch)
489    }
490
491    pub fn write_prim_gpu_blocks(
492        &self,
493        adjustment: &AdjustedImageSource,
494        stretch_size: LayoutSize,
495        writer: &mut GpuBufferWriterF,
496    ) {
497        let stretch_size = adjustment.map_stretch_size(stretch_size)
498             + self.tile_spacing;
499
500        writer.push(&ImageBrushPrimitiveData {
501            color: self.color.premultiplied(),
502            background_color: PremultipliedColorF::WHITE,
503            stretch_size,
504        });
505    }
506}
507
508pub fn prepare_image_quads(
509    prim_rect: &LayoutRect,
510    common_data: &PrimTemplateCommonData,
511    image_data: &ImageData,
512    clip_chain: &ClipChainInstance,
513    prim_instance_index: PrimitiveInstanceIndex,
514    quad_transform: &mut QuadTransformState,
515    frame_context: &FrameBuildingContext,
516    pic_context: &PictureContext,
517    targets: &[CommandBufferIndex],
518    interned_clips: &DataStore<ClipIntern>,
519    frame_state: &mut FrameBuildingState,
520    scratch: &mut PrimitiveScratchBuffer,
521) {
522    let image_properties = frame_state
523        .resource_cache
524        .get_image_properties(image_data.key);
525
526    let Some(image_properties) = image_properties else {
527        return;
528    };
529
530    let src_is_opaque = image_properties.descriptor.is_opaque()
531        && common_data.opacity.is_opaque
532        && image_data.color.a >= 0.9999;
533
534    let premultiplied = image_data.alpha_type == AlphaType::PremultipliedAlpha;
535
536    // Tighten the clip rect because decomposing the repeated image can
537    // produce primitives that are partially covering the original image
538    // rect and we want to clip these extra parts out.
539    // We also rely on having a tight clip rect in some cases other than
540    // tiled/repeated images, for example when rendering a snapshot image
541    // where the snapshot area is tighter than the rasterized area.
542    let tight_clip_rect = clip_chain
543        .local_clip_rect
544        .intersection(&prim_rect)
545        .unwrap();
546
547    let request = ImageRequest {
548        key: image_data.key,
549        rendering: image_data.image_rendering,
550        tile: None,
551    };
552
553    let mut sampler_kind = ImageBufferKind::Texture2D;
554    if let Some(ExternalImageData { image_type: ExternalImageType::TextureHandle(kind), .. }) = image_properties.external_image {
555        sampler_kind = kind;
556    }
557
558
559    match image_properties.tiling {
560        // Non-tiled (most common) path.
561        None => {
562            let size = frame_state.resource_cache.request_image(
563                request,
564                &mut frame_state.frame_gpu_data.f32,
565            );
566
567            let effective_stretch_size = image_data.stretch_size.resolve(prim_rect);
568            let prim_rect = image_properties.adjustment.map_local_rect(&prim_rect);
569            let stretch_size = image_properties.adjustment.map_stretch_size(effective_stretch_size);
570
571            let mut src_task_id = frame_state.rg_builder.add().init(
572                RenderTask::new_image(size, request, false)
573            );
574
575            if let Some(external_image) = image_properties.external_image {
576                // On some devices we cannot render from an ImageBufferKind::TextureExternal
577                // source using most shaders, so must perform a copy to a regular texture first.
578                let requires_copy = frame_context.fb_config.external_images_require_copy
579                    && external_image.image_type
580                        == ExternalImageType::TextureHandle(ImageBufferKind::TextureExternal);
581
582                if requires_copy {
583                    let target_kind = if image_properties.descriptor.format.bytes_per_pixel() == 1 {
584                        RenderTargetKind::Alpha
585                    } else {
586                        RenderTargetKind::Color
587                    };
588
589                    src_task_id = RenderTask::new_scaling(
590                        src_task_id,
591                        frame_state.rg_builder,
592                        target_kind,
593                        size,
594                    );
595
596                    frame_state.surface_builder.add_child_render_task(
597                        src_task_id,
598                        frame_state.rg_builder,
599                    );
600
601                    sampler_kind = ImageBufferKind::Texture2D;
602                }
603            }
604
605            let image_pattern = ImagePattern {
606                src_task_id,
607                src_is_opaque,
608                premultiplied,
609                sampler_kind,
610                color: image_data.color,
611            };
612
613            quad::prepare_repeatable_quad(
614                &image_pattern,
615                &prim_rect,
616                &tight_clip_rect,
617                stretch_size,
618                image_data.tile_spacing,
619                common_data.aligned_aa_edges,
620                common_data.transformed_aa_edges,
621                prim_instance_index,
622                &None,
623                clip_chain,
624                quad_transform,
625                frame_context,
626                pic_context,
627                targets,
628                interned_clips,
629                frame_state,
630                scratch,
631            );
632        }
633        Some(tile_size) => {
634            // TODO: rename the blob's visible_rect into something that doesn't conflict
635            // with the terminology we use during culling since it's not really the same
636            // thing.
637            let active_rect = image_properties.visible_rect;
638            let visible_rect = compute_conservative_visible_rect(
639                &scratch.frame.draws[prim_instance_index.0 as usize].clip_chain,
640                frame_state.current_dirty_region().combined,
641                frame_state.current_dirty_region().visibility_spatial_node,
642                quad_transform.prim_spatial_node_index(),
643                frame_context.spatial_tree,
644            );
645
646            let effective_stretch_size = image_data.stretch_size.resolve(prim_rect);
647            let stride = effective_stretch_size + image_data.tile_spacing;
648
649            let repetitions = image_tiling::repetitions(
650                prim_rect,
651                &visible_rect,
652                stride,
653            );
654
655            let base_edge_flags = edge_flags_for_tile_spacing(&image_data.tile_spacing);
656
657            for image_tiling::Repetition { origin, edge_flags } in repetitions {
658                let rep_edge_flags = base_edge_flags & edge_flags;
659
660                let layout_image_rect = LayoutRect::from_origin_and_size(
661                    origin,
662                    effective_stretch_size,
663                );
664
665                let tiles = image_tiling::tiles(
666                    &layout_image_rect,
667                    &visible_rect,
668                    &active_rect,
669                    tile_size as i32,
670                );
671
672                for tile in tiles {
673                    let request = request.with_tile(tile.offset);
674                    let size = frame_state.resource_cache.request_image(
675                        request,
676                        &mut frame_state.frame_gpu_data.f32,
677                    );
678
679                    let tile_edge_flags = rep_edge_flags & tile.edge_flags;
680                    let aligned_aa_edges = tile_edge_flags & common_data.aligned_aa_edges;
681                    let transformed_aa_edges = tile_edge_flags & common_data.transformed_aa_edges;
682
683                    let src_task_id = frame_state.rg_builder.add().init(
684                        RenderTask::new_image(size, request, false)
685                    );
686
687                    let image_pattern = ImagePattern {
688                        src_task_id,
689                        src_is_opaque,
690                        premultiplied,
691                        sampler_kind,
692                        color: image_data.color,
693                    };
694
695                    quad::prepare_quad(
696                        &image_pattern,
697                        &tile.rect,
698                        &tight_clip_rect,
699                        aligned_aa_edges,
700                        transformed_aa_edges,
701                        prim_instance_index,
702                        &None,
703                        clip_chain,
704                        quad_transform,
705                        frame_context,
706                        pic_context,
707                        targets,
708                        interned_clips,
709                        frame_state,
710                        scratch,
711                    );
712                }
713            }
714        }
715    }
716}
717
718fn edge_flags_for_tile_spacing(tile_spacing: &LayoutSize) -> EdgeMask {
719    let mut flags = EdgeMask::empty();
720
721    if tile_spacing.width > 0.0 {
722        flags |= EdgeMask::LEFT | EdgeMask::RIGHT;
723    }
724    if tile_spacing.height > 0.0 {
725        flags |= EdgeMask::TOP | EdgeMask::BOTTOM;
726    }
727
728    flags
729}
730
731pub type ImageTemplate = PrimTemplate<ImageData>;
732
733impl From<ImageKey> for ImageTemplate {
734    fn from(image: ImageKey) -> Self {
735        let common = PrimTemplateCommonData::with_key_common(image.common);
736
737        ImageTemplate {
738            common,
739            kind: image.kind.into(),
740        }
741    }
742}
743
744pub type ImageDataHandle = InternHandle<Image>;
745
746impl Internable for Image {
747    type Key = ImageKey;
748    type StoreData = ImageTemplate;
749    type InternData = ();
750    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_IMAGES;
751}
752
753impl InternablePrimitive for Image {
754    fn into_key(
755        self,
756        info: &LayoutPrimitiveInfo,
757    ) -> ImageKey {
758        ImageKey::new(info, self)
759    }
760
761    fn make_instance_kind(
762        _key: ImageKey,
763        data_handle: ImageDataHandle,
764        _prim_store: &mut PrimitiveStore,
765    ) -> PrimitiveKind {
766        PrimitiveKind::Image {
767            data_handle,
768        }
769    }
770}
771
772impl CreateShadow for Image {
773    fn create_shadow(
774        &self,
775        shadow: &Shadow,
776        _: bool,
777        _: RasterSpace,
778    ) -> Self {
779        Image {
780            tile_spacing: self.tile_spacing,
781            stretch_size: self.stretch_size,
782            key: self.key,
783            image_rendering: self.image_rendering,
784            alpha_type: self.alpha_type,
785            color: shadow.color.into(),
786        }
787    }
788}
789
790impl IsVisible for Image {
791    fn is_visible(&self) -> bool {
792        true
793    }
794}
795
796/// Represents an adjustment to apply to an image primitive.
797/// This can be used to compensate for a difference between the bounds of
798/// the images expected by the primitive and the bounds that were actually
799/// drawn in the texture cache.
800///
801/// This happens when rendering snapshot images: A picture is marked so that
802/// a specific reference area in layout space can be rendered as an image.
803/// However, the bounds of the rasterized area of the picture typically differ
804/// from that reference area.
805///
806/// The adjustment is stored as 4 floats (x0, y0, x1, y1) that represent a
807/// transformation of the primitve's local rect such that:
808///
809/// ```ignore
810/// adjusted_rect.min = prim_rect.min + prim_rect.size() * (x0, y0);
811/// adjusted_rect.max = prim_rect.max + prim_rect.size() * (x1, y1);
812/// ```
813#[derive(Copy, Clone, Debug)]
814#[cfg_attr(feature = "capture", derive(Serialize))]
815#[cfg_attr(feature = "replay", derive(Deserialize))]
816pub struct AdjustedImageSource {
817    x0: f32,
818    y0: f32,
819    x1: f32,
820    y1: f32,
821}
822
823impl AdjustedImageSource {
824    /// The "identity" adjustment.
825    pub fn new() -> Self {
826        AdjustedImageSource {
827            x0: 0.0,
828            y0: 0.0,
829            x1: 0.0,
830            y1: 0.0,
831        }
832    }
833
834    /// An adjustment to render an image item defined in function of the `reference`
835    /// rect whereas the `actual` rect was cached instead.
836    pub fn from_rects(reference: &LayoutRect, actual: &LayoutRect) -> Self {
837        let ref_size = reference.size();
838        let min_offset = reference.min.to_vector();
839        let max_offset = reference.max.to_vector();
840        AdjustedImageSource {
841            x0: (actual.min.x - min_offset.x) / ref_size.width,
842            y0: (actual.min.y - min_offset.y) / ref_size.height,
843            x1: (actual.max.x - max_offset.x) / ref_size.width,
844            y1: (actual.max.y - max_offset.y) / ref_size.height,
845        }
846    }
847
848    /// Adjust the primitive's local rect.
849    pub fn map_local_rect(&self, rect: &LayoutRect) -> LayoutRect {
850        let w = rect.width();
851        let h = rect.height();
852        LayoutRect {
853            min: point2(
854                rect.min.x + w * self.x0,
855                rect.min.y + h * self.y0,
856            ),
857            max: point2(
858                rect.max.x + w * self.x1,
859                rect.max.y + h * self.y1,
860            ),
861        }
862    }
863
864    /// The stretch size has to be adjusted as well because it is defined
865    /// using the snapshot area as reference but will stretch the rasterized
866    /// area instead.
867    ///
868    /// It has to be scaled by a factor of (adjusted.size() / prim_rect.size()).
869    /// We derive the formula in function of the adjustment factors:
870    ///
871    /// ```ignore
872    /// factor = (adjusted.max - adjusted.min) / (w, h)
873    ///        = (rect.max + (w, h) * (x1, y1) - (rect.min + (w, h) * (x0, y0))) / (w, h)
874    ///        = ((w, h) + (w, h) * (x1, y1) - (w, h) * (x0, y0)) / (w, h)
875    ///        = (1.0, 1.0) + (x1, y1) - (x0, y0)
876    /// ```
877    pub fn map_stretch_size(&self, size: LayoutSize) -> LayoutSize {
878        LayoutSize::new(
879            size.width * (1.0 + self.x1 - self.x0),
880            size.height * (1.0 + self.y1 - self.y0),
881        )
882    }
883}
884
885////////////////////////////////////////////////////////////////////////////////
886
887#[cfg_attr(feature = "capture", derive(Serialize))]
888#[cfg_attr(feature = "replay", derive(Deserialize))]
889#[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash)]
890pub struct YuvImage {
891    pub color_depth: ColorDepth,
892    pub yuv_key: [ApiImageKey; 3],
893    pub format: YuvFormat,
894    pub color_space: YuvColorSpace,
895    pub color_range: ColorRange,
896    pub image_rendering: ImageRendering,
897}
898
899pub type YuvImageKey = PrimKey<YuvImage>;
900
901impl YuvImageKey {
902    pub fn new(
903        info: &LayoutPrimitiveInfo,
904        yuv_image: YuvImage,
905    ) -> Self {
906        YuvImageKey {
907            common: info.into(),
908            kind: yuv_image,
909        }
910    }
911}
912
913impl InternDebug for YuvImageKey {}
914
915#[cfg_attr(feature = "capture", derive(Serialize))]
916#[cfg_attr(feature = "replay", derive(Deserialize))]
917#[derive(MallocSizeOf)]
918pub struct YuvImageData {
919    pub color_depth: ColorDepth,
920    pub yuv_key: [ApiImageKey; 3],
921    pub src_yuv: [Option<RenderTaskId>; 3],
922    pub format: YuvFormat,
923    pub color_space: YuvColorSpace,
924    pub color_range: ColorRange,
925    pub image_rendering: ImageRendering,
926}
927
928impl From<YuvImage> for YuvImageData {
929    fn from(image: YuvImage) -> Self {
930        YuvImageData {
931            color_depth: image.color_depth,
932            yuv_key: image.yuv_key,
933            src_yuv: [None, None, None],
934            format: image.format,
935            color_space: image.color_space,
936            color_range: image.color_range,
937            image_rendering: image.image_rendering,
938        }
939    }
940}
941
942impl YuvImageData {
943    /// Update the GPU cache for a given primitive template. This may be called multiple
944    /// times per frame, by each primitive reference that refers to this interned
945    /// template. The initial request call to the GPU cache ensures that work is only
946    /// done if the cache entry is invalid (due to first use or eviction).
947    pub fn update(
948        &mut self,
949        common: &mut PrimTemplateCommonData,
950        is_composited: bool,
951        frame_state: &mut FrameBuildingState,
952    ) {
953
954        self.src_yuv = [ None, None, None ];
955
956        let channel_num = self.format.get_plane_num();
957        debug_assert!(channel_num <= 3);
958        for channel in 0 .. channel_num {
959            let request = ImageRequest {
960                key: self.yuv_key[channel],
961                rendering: self.image_rendering,
962                tile: None,
963            };
964
965            let size = frame_state.resource_cache.request_image(
966                request,
967                &mut frame_state.frame_gpu_data.f32,
968            );
969
970            let task_id = frame_state.rg_builder.add().init(
971                RenderTask::new_image(
972                    size,
973                    request,
974                    is_composited,
975                )
976            );
977
978            self.src_yuv[channel] = Some(task_id);
979        }
980
981        let mut writer = frame_state.frame_gpu_data.f32.write_blocks(1);
982        self.write_prim_gpu_blocks(&mut writer);
983        common.gpu_buffer_address = writer.finish();
984
985    // YUV images never have transparency
986        common.opacity = PrimitiveOpacity::opaque();
987    }
988
989    pub fn request_resources(
990        &mut self,
991        resource_cache: &mut ResourceCache,
992        gpu_buffer: &mut GpuBufferBuilderF,
993    ) {
994        let channel_num = self.format.get_plane_num();
995        debug_assert!(channel_num <= 3);
996        for channel in 0 .. channel_num {
997            resource_cache.request_image(
998                ImageRequest {
999                    key: self.yuv_key[channel],
1000                    rendering: self.image_rendering,
1001                    tile: None,
1002                },
1003                gpu_buffer,
1004            );
1005        }
1006    }
1007
1008    pub fn write_prim_gpu_blocks(&self, writer: &mut GpuBufferWriterF) {
1009        writer.push(&YuvPrimitive {
1010            channel_bit_depth: self.color_depth.bit_depth(),
1011            color_space: self.color_space.with_range(self.color_range),
1012            yuv_format: self.format,
1013        });
1014    }
1015}
1016
1017pub type YuvImageTemplate = PrimTemplate<YuvImageData>;
1018
1019impl From<YuvImageKey> for YuvImageTemplate {
1020    fn from(image: YuvImageKey) -> Self {
1021        let common = PrimTemplateCommonData::with_key_common(image.common);
1022
1023        YuvImageTemplate {
1024            common,
1025            kind: image.kind.into(),
1026        }
1027    }
1028}
1029
1030pub type YuvImageDataHandle = InternHandle<YuvImage>;
1031
1032impl Internable for YuvImage {
1033    type Key = YuvImageKey;
1034    type StoreData = YuvImageTemplate;
1035    type InternData = ();
1036    const PROFILE_COUNTER: usize = crate::profiler::INTERNED_YUV_IMAGES;
1037}
1038
1039impl InternablePrimitive for YuvImage {
1040    fn into_key(
1041        self,
1042        info: &LayoutPrimitiveInfo,
1043    ) -> YuvImageKey {
1044        YuvImageKey::new(info, self)
1045    }
1046
1047    fn make_instance_kind(
1048        _key: YuvImageKey,
1049        data_handle: YuvImageDataHandle,
1050        _prim_store: &mut PrimitiveStore,
1051    ) -> PrimitiveKind {
1052        PrimitiveKind::YuvImage {
1053            data_handle,
1054        }
1055    }
1056}
1057
1058impl IsVisible for YuvImage {
1059    fn is_visible(&self) -> bool {
1060        true
1061    }
1062}
1063
1064#[test]
1065#[cfg(target_pointer_width = "64")]
1066fn test_struct_sizes() {
1067    use std::mem;
1068    // The sizes of these structures are critical for performance on a number of
1069    // talos stress tests. If you get a failure here on CI, there's two possibilities:
1070    // (a) You made a structure smaller than it currently is. Great work! Update the
1071    //     test expectations and move on.
1072    // (b) You made a structure larger. This is not necessarily a problem, but should only
1073    //     be done with care, and after checking if talos performance regresses badly.
1074    assert_eq!(mem::size_of::<Image>(), 36, "Image size changed");
1075    assert_eq!(mem::size_of::<ImageTemplate>(), 56, "ImageTemplate size changed");
1076    assert_eq!(mem::size_of::<ImageKey>(), 40, "ImageKey size changed");
1077    assert_eq!(mem::size_of::<YuvImage>(), 32, "YuvImage size changed");
1078    assert_eq!(mem::size_of::<YuvImageTemplate>(), 76, "YuvImageTemplate size changed");
1079    assert_eq!(mem::size_of::<YuvImageKey>(), 36, "YuvImageKey size changed");
1080}