Skip to main content

webrender/renderer/
composite.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::units::*;
6use api::{ColorF, ImageBufferKind, ImageRendering, PremultipliedColorF};
7use crate::batch::BatchTextures;
8use crate::composite::{
9    CompositeState, CompositeSurfaceFormat, CompositeTileSurface, CompositorConfig,
10    CompositorInputLayer, CompositorSurfaceUsage, CompositorSurfaceTransform,
11    CompositeRoundedCorner, NativeTileId, ResolvedExternalSurface,
12    ResolvedExternalSurfaceColorData, ClipRadius, CompositeFeatures, CompositorKind, TileKind,
13};
14use crate::frame_builder::Frame;
15use crate::{CompositorInputConfig, PictureCacheDebugInfo, debug_colors};
16use api::{ClipMode, DebugFlags};
17use std::collections::HashSet;
18use std::mem;
19use crate::debug_item::DebugItem;
20use crate::segment::EdgeMask;
21use crate::device::DrawTarget;
22use crate::gpu_types::{CompositeInstance, ZBufferId};
23use crate::internal_types::{FastHashMap, TextureSource};
24use crate::picture::ResolvedSurfaceTexture;
25use super::RenderResults;
26use crate::profiler::{self};
27use crate::rectangle_occlusion as occlusion;
28use crate::renderer::{
29    GPU_SAMPLER_TAG_OPAQUE, GPU_SAMPLER_TAG_TRANSPARENT, GPU_TAG_COMPOSITE, PartialPresentMode,
30};
31use crate::renderer::{FramebufferKind, Renderer, RendererStats, VertexArrayKind};
32use crate::segment::SegmentBuilder;
33use crate::tile_cache::TileId;
34use euclid::{Scale, Transform3D, default};
35
36#[derive(Debug, Copy, Clone)]
37pub(super) struct OcclusionItemKey {
38    pub tile_index: usize,
39    pub needs_mask: bool,
40}
41
42pub(super) struct SwapChainLayer {
43    pub occlusion: occlusion::FrontToBackBuilder<OcclusionItemKey>,
44}
45
46pub(super) struct CompositeTileState {
47    pub local_rect: PictureRect,
48    pub local_valid_rect: PictureRect,
49    pub device_clip_rect: DeviceRect,
50    pub z_id: ZBufferId,
51    pub device_tile_box: DeviceRect,
52    pub visible_rects: Vec<DeviceRect>,
53}
54
55impl CompositeTileState {
56    pub fn same_state(&self, other: &CompositeTileState) -> bool {
57        self.local_rect == other.local_rect
58            && self.local_valid_rect == other.local_valid_rect
59            && self.device_clip_rect == other.device_clip_rect
60            && self.z_id == other.z_id
61            && self.device_tile_box == other.device_tile_box
62    }
63}
64
65pub(super) struct LayerCompositorFrameState {
66    pub tile_states: FastHashMap<TileId, CompositeTileState>,
67    pub rects_without_id: Vec<DeviceRect>,
68}
69
70impl Renderer {
71    /// Rasterize any external compositor surfaces that require updating
72    fn update_external_native_surfaces(
73        &mut self,
74        external_surfaces: &[ResolvedExternalSurface],
75        results: &mut RenderResults,
76    ) {
77        if external_surfaces.is_empty() {
78            return;
79        }
80
81        let opaque_sampler = self.gpu_profiler.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
82
83        self.device.disable_depth();
84        self.set_blend(false, FramebufferKind::Main);
85
86        for surface in external_surfaces {
87            // See if this surface needs to be updated
88            let (native_surface_id, surface_size) = match surface.update_params {
89                Some(params) => params,
90                None => continue,
91            };
92
93            // When updating an external surface, the entire surface rect is used
94            // for all of the draw, dirty, valid and clip rect parameters.
95            let surface_rect = surface_size.into();
96
97            // Bind the native compositor surface to update
98            let surface_info = self.compositor_config
99                .compositor()
100                .unwrap()
101                .bind(
102                    &mut self.device,
103                    NativeTileId {
104                        surface_id: native_surface_id,
105                        x: 0,
106                        y: 0,
107                    },
108                    surface_rect,
109                    surface_rect,
110                );
111
112            // Bind the native surface to current FBO target
113            let draw_target = DrawTarget::NativeSurface {
114                offset: surface_info.origin,
115                external_fbo_id: surface_info.fbo_id,
116                dimensions: surface_size,
117            };
118            self.device.bind_draw_target(draw_target);
119
120            let projection = Transform3D::ortho(
121                0.0,
122                surface_size.width as f32,
123                0.0,
124                surface_size.height as f32,
125                self.device.ortho_near_plane(),
126                self.device.ortho_far_plane(),
127            );
128
129            let ( textures, instance ) = match surface.color_data {
130                ResolvedExternalSurfaceColorData::Yuv{
131                        ref planes, color_space, format, channel_bit_depth, .. } => {
132
133                    let textures = BatchTextures::composite_yuv(
134                        planes[0].texture,
135                        planes[1].texture,
136                        planes[2].texture,
137                    );
138
139                    // When the texture is an external texture, the UV rect is not known when
140                    // the external surface descriptor is created, because external textures
141                    // are not resolved until the lock() callback is invoked at the start of
142                    // the frame render. To handle this, query the texture resolver for the
143                    // UV rect if it's an external texture, otherwise use the default UV rect.
144                    let uv_rects = [
145                        self.texture_resolver.get_uv_rect(&textures.input.colors[0], planes[0].uv_rect),
146                        self.texture_resolver.get_uv_rect(&textures.input.colors[1], planes[1].uv_rect),
147                        self.texture_resolver.get_uv_rect(&textures.input.colors[2], planes[2].uv_rect),
148                    ];
149
150                    let instance = CompositeInstance::new_yuv(
151                        surface_rect.to_f32(),
152                        surface_rect.to_f32(),
153                        // z-id is not relevant when updating a native compositor surface.
154                        // TODO(gw): Support compositor surfaces without z-buffer, for memory / perf win here.
155                        color_space,
156                        format,
157                        channel_bit_depth,
158                        uv_rects,
159                        (false, false),
160                        None,
161                    );
162
163                    // Bind an appropriate YUV shader for the texture format kind
164                    self.shaders
165                        .borrow_mut()
166                        .get_composite_shader(
167                            CompositeSurfaceFormat::Yuv,
168                            surface.image_buffer_kind,
169                            instance.get_yuv_features(),
170                        ).bind(
171                            &mut self.device,
172                            &projection,
173                            None,
174                            &mut self.renderer_errors,
175                            &mut self.profile,
176                            &mut self.command_log,
177                        );
178
179                    ( textures, instance )
180                },
181                ResolvedExternalSurfaceColorData::Rgb{ ref plane, .. } => {
182                    let textures = BatchTextures::composite_rgb(plane.texture);
183                    let uv_rect = self.texture_resolver.get_uv_rect(&textures.input.colors[0], plane.uv_rect);
184                    let instance = CompositeInstance::new_rgb(
185                        surface_rect.to_f32(),
186                        surface_rect.to_f32(),
187                        PremultipliedColorF::WHITE,
188                        uv_rect,
189                        plane.texture.uses_normalized_uvs(),
190                        (false, false),
191                        None,
192                    );
193                    let features = instance.get_rgb_features();
194
195                    self.shaders
196                        .borrow_mut()
197                        .get_composite_shader(
198                            CompositeSurfaceFormat::Rgba,
199                            surface.image_buffer_kind,
200                            features,
201                        ).bind(
202                            &mut self.device,
203                            &projection,
204                            None,
205                            &mut self.renderer_errors,
206                            &mut self.profile,
207                            &mut self.command_log,
208                        );
209
210                    ( textures, instance )
211                },
212            };
213
214            self.draw_instanced_batch(
215                &[instance],
216                VertexArrayKind::Composite,
217                &textures,
218                &mut results.stats,
219            );
220
221            self.compositor_config
222                .compositor()
223                .unwrap()
224                .unbind(&mut self.device);
225        }
226
227        self.gpu_profiler.finish_sampler(opaque_sampler);
228    }
229
230    /// Draw a list of tiles to the framebuffer
231    fn draw_tile_list<'a, I: Iterator<Item = &'a occlusion::Item<OcclusionItemKey>>>(
232        &mut self,
233        tiles_iter: I,
234        composite_state: &CompositeState,
235        external_surfaces: &[ResolvedExternalSurface],
236        projection: &default::Transform3D<f32>,
237        stats: &mut RendererStats,
238    ) {
239        let mut current_shader_params = (
240            CompositeSurfaceFormat::Rgba,
241            ImageBufferKind::Texture2D,
242            CompositeFeatures::empty(),
243            None,
244        );
245        let mut current_textures = BatchTextures::empty();
246        let mut instances = Vec::new();
247
248        for item in tiles_iter {
249            let tile = &composite_state.tiles[item.key.tile_index];
250
251            let clip_rect = item.rectangle;
252            let tile_rect = composite_state.get_device_rect(&tile.local_rect, tile.transform_index);
253            let transform = composite_state.get_device_transform(tile.transform_index);
254            let flip = (transform.scale.x < 0.0, transform.scale.y < 0.0);
255
256            let clip = if item.key.needs_mask {
257                tile.clip_index.map(|index| {
258                    composite_state.get_compositor_clip(index)
259                })
260            } else {
261                None
262            };
263
264            // Work out the draw params based on the tile surface
265            let (instance, textures, shader_params) = match tile.surface {
266                CompositeTileSurface::Color { color } => {
267                    let dummy = TextureSource::Dummy;
268                    let image_buffer_kind = dummy.image_buffer_kind();
269                    let instance = CompositeInstance::new(
270                        tile_rect,
271                        clip_rect,
272                        color.premultiplied(),
273                        flip,
274                        clip,
275                    );
276                    let features = instance.get_rgb_features();
277                    (
278                        instance,
279                        BatchTextures::composite_rgb(dummy),
280                        (CompositeSurfaceFormat::Rgba, image_buffer_kind, features, None),
281                    )
282                }
283                CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::TextureCache { texture } } => {
284                    let instance = CompositeInstance::new(
285                        tile_rect,
286                        clip_rect,
287                        PremultipliedColorF::WHITE,
288                        flip,
289                        clip,
290                    );
291                    let features = instance.get_rgb_features();
292                    (
293                        instance,
294                        BatchTextures::composite_rgb(texture),
295                        (
296                            CompositeSurfaceFormat::Rgba,
297                            ImageBufferKind::Texture2D,
298                            features,
299                            None,
300                        ),
301                    )
302                }
303                CompositeTileSurface::ExternalSurface { external_surface_index } => {
304                    let surface = &external_surfaces[external_surface_index.0];
305
306                    match surface.color_data {
307                        ResolvedExternalSurfaceColorData::Yuv{ ref planes, color_space, format, channel_bit_depth, .. } => {
308                            let textures = BatchTextures::composite_yuv(
309                                planes[0].texture,
310                                planes[1].texture,
311                                planes[2].texture,
312                            );
313
314                            // When the texture is an external texture, the UV rect is not known when
315                            // the external surface descriptor is created, because external textures
316                            // are not resolved until the lock() callback is invoked at the start of
317                            // the frame render. To handle this, query the texture resolver for the
318                            // UV rect if it's an external texture, otherwise use the default UV rect.
319                            let uv_rects = [
320                                self.texture_resolver.get_uv_rect(&textures.input.colors[0], planes[0].uv_rect),
321                                self.texture_resolver.get_uv_rect(&textures.input.colors[1], planes[1].uv_rect),
322                                self.texture_resolver.get_uv_rect(&textures.input.colors[2], planes[2].uv_rect),
323                            ];
324
325                            let instance = CompositeInstance::new_yuv(
326                                tile_rect,
327                                clip_rect,
328                                color_space,
329                                format,
330                                channel_bit_depth,
331                                uv_rects,
332                                flip,
333                                clip,
334                            );
335                            let features = instance.get_yuv_features();
336
337                            (
338                                instance,
339                                textures,
340                                (
341                                    CompositeSurfaceFormat::Yuv,
342                                    surface.image_buffer_kind,
343                                    features,
344                                    None
345                                ),
346                            )
347                        },
348                        ResolvedExternalSurfaceColorData::Rgb { ref plane, .. } => {
349                            let uv_rect = self.texture_resolver.get_uv_rect(&plane.texture, plane.uv_rect);
350                            let instance = CompositeInstance::new_rgb(
351                                tile_rect,
352                                clip_rect,
353                                PremultipliedColorF::WHITE,
354                                uv_rect,
355                                plane.texture.uses_normalized_uvs(),
356                                flip,
357                                clip,
358                            );
359                            let features = instance.get_rgb_features();
360                            (
361                                instance,
362                                BatchTextures::composite_rgb(plane.texture),
363                                (
364                                    CompositeSurfaceFormat::Rgba,
365                                    surface.image_buffer_kind,
366                                    features,
367                                    Some(self.texture_resolver.get_texture_size(&plane.texture).to_f32()),
368                                ),
369                            )
370                        },
371                    }
372                }
373                CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::Native { .. } } => {
374                    unreachable!("bug: found native surface in simple composite path");
375                }
376            };
377
378            // Flush batch if shader params or textures changed
379            let flush_batch = !current_textures.is_compatible_with(&textures) ||
380                shader_params != current_shader_params;
381
382            if flush_batch && !instances.is_empty() {
383                self.shaders
384                    .borrow_mut()
385                    .get_composite_shader(
386                        current_shader_params.0,
387                        current_shader_params.1,
388                        current_shader_params.2,
389                    ).bind(
390                        &mut self.device,
391                        projection,
392                        current_shader_params.3,
393                        &mut self.renderer_errors,
394                        &mut self.profile,
395                        &mut self.command_log,
396                    );
397                self.draw_instanced_batch(
398                    &instances,
399                    VertexArrayKind::Composite,
400                    &current_textures,
401                    stats,
402                );
403
404                instances.clear();
405            }
406            current_shader_params = shader_params;
407            current_textures = textures;
408
409            // Add instance to current batch
410            instances.push(instance);
411        }
412
413        // Flush the last batch
414        if !instances.is_empty() {
415            self.shaders
416                .borrow_mut()
417                .get_composite_shader(
418                    current_shader_params.0,
419                    current_shader_params.1,
420                    current_shader_params.2,
421                ).bind(
422                    &mut self.device,
423                    projection,
424                    current_shader_params.3,
425                    &mut self.renderer_errors,
426                    &mut self.profile,
427                    &mut self.command_log,
428                );
429            self.draw_instanced_batch(
430                &instances,
431                VertexArrayKind::Composite,
432                &current_textures,
433                stats,
434            );
435        }
436    }
437
438    // Composite tiles in a swapchain. When using LayerCompositor, we may
439    // split the compositing in to multiple swapchains.
440    fn composite_pass(
441        &mut self,
442        composite_state: &CompositeState,
443        draw_target: DrawTarget,
444        clear_color: ColorF,
445        projection: &default::Transform3D<f32>,
446        results: &mut RenderResults,
447        partial_present_mode: Option<PartialPresentMode>,
448        layer: &SwapChainLayer,
449    ) {
450        self.device.bind_draw_target(draw_target);
451        self.device.disable_depth_write();
452        self.device.disable_depth();
453
454        // If using KHR_partial_update, call eglSetDamageRegion.
455        // This must be called exactly once per frame, and prior to any rendering to the main
456        // framebuffer. Additionally, on Mali-G77 we encountered rendering issues when calling
457        // this earlier in the frame, during offscreen render passes. So call it now, immediately
458        // before rendering to the main framebuffer. See bug 1685276 for details.
459        if let Some(partial_present) = self.compositor_config.partial_present() {
460            if let Some(PartialPresentMode::Single { dirty_rect }) = partial_present_mode {
461                partial_present.set_buffer_damage_region(&[dirty_rect.to_i32()]);
462            }
463        }
464
465        // Clear the framebuffer
466        let clear_color = Some(clear_color.to_array());
467
468        match partial_present_mode {
469            Some(PartialPresentMode::Single { dirty_rect }) => {
470                // There is no need to clear if the dirty rect is occluded. Additionally,
471                // on Mali-G77 we have observed artefacts when calling glClear (even with
472                // the empty scissor rect set) after calling eglSetDamageRegion with an
473                // empty damage region. So avoid clearing in that case. See bug 1709548.
474                if !dirty_rect.is_empty() && layer.occlusion.test(&dirty_rect) {
475                    // We have a single dirty rect, so clear only that
476                    self.device.clear_target(clear_color,
477                                             None,
478                                             Some(draw_target.to_framebuffer_rect(dirty_rect.to_i32())));
479                }
480            }
481            None => {
482                // Partial present is disabled, so clear the entire framebuffer
483                self.device.clear_target(clear_color,
484                                         None,
485                                         None);
486            }
487        }
488
489        // Draw opaque tiles
490        let opaque_items = layer.occlusion.opaque_items();
491        if !opaque_items.is_empty() {
492            let opaque_sampler = self.gpu_profiler.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
493            self.set_blend(false, FramebufferKind::Main);
494            self.draw_tile_list(
495                opaque_items.iter(),
496                &composite_state,
497                &composite_state.external_surfaces,
498                projection,
499                &mut results.stats,
500            );
501            self.gpu_profiler.finish_sampler(opaque_sampler);
502        }
503
504        // Draw alpha tiles
505        let alpha_items = layer.occlusion.alpha_items();
506        if !alpha_items.is_empty() {
507            let transparent_sampler = self.gpu_profiler.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
508            self.set_blend(true, FramebufferKind::Main);
509            self.set_blend_mode_premultiplied_alpha(FramebufferKind::Main);
510            self.draw_tile_list(
511                alpha_items.iter().rev(),
512                &composite_state,
513                &composite_state.external_surfaces,
514                projection,
515                &mut results.stats,
516            );
517            self.gpu_profiler.finish_sampler(transparent_sampler);
518        }
519    }
520
521    /// Composite picture cache tiles into the framebuffer. This is currently
522    /// the only way that picture cache tiles get drawn. In future, the tiles
523    /// will often be handed to the OS compositor, and this method will be
524    /// rarely used.
525    fn composite_simple(
526        &mut self,
527        composite_state: &CompositeState,
528        frame_device_size: DeviceIntSize,
529        fb_draw_target: DrawTarget,
530        projection: &default::Transform3D<f32>,
531        results: &mut RenderResults,
532        partial_present_mode: Option<PartialPresentMode>,
533        device_size: DeviceIntSize,
534    ) {
535        let _gm = self.gpu_profiler.start_marker("framebuffer");
536        let _timer = self.gpu_profiler.start_timer(GPU_TAG_COMPOSITE);
537
538        let num_tiles = composite_state.tiles.len();
539        self.profile.set(profiler::PICTURE_TILES, num_tiles);
540
541        let (window_is_opaque, enable_screenshot)  = match self.compositor_config.layer_compositor() {
542            Some(ref compositor) => {
543                let props = compositor.get_window_properties();
544                (props.is_opaque, props.enable_screenshot)
545            }
546            None => (true, true)
547        };
548
549        let mut input_layers: Vec<CompositorInputLayer> = Vec::new();
550        let mut swapchain_layers = Vec::new();
551        let cap = composite_state.tiles.len();
552        let mut segment_builder = SegmentBuilder::new();
553        let mut tile_index_to_layer_index = vec![None; composite_state.tiles.len()];
554        let mut full_render_occlusion = occlusion::FrontToBackBuilder::with_capacity(cap, cap);
555        let mut layer_compositor_frame_state = LayerCompositorFrameState{
556            tile_states: FastHashMap::default(),
557            rects_without_id: Vec::new(),
558        };
559
560        // Calculate layers with full device rect
561
562        // Add a debug overlay request if enabled
563        if self.debug_overlay_state.is_enabled {
564            self.debug_overlay_state.layer_index = input_layers.len();
565
566            input_layers.push(CompositorInputLayer {
567                usage: CompositorSurfaceUsage::DebugOverlay,
568                is_opaque: false,
569                offset: DeviceIntPoint::zero(),
570                clip_rect: device_size.into(),
571                rounded_clip_rect: device_size.into(),
572                rounded_clip_radii: ClipRadius::EMPTY,
573            });
574
575            swapchain_layers.push(SwapChainLayer {
576                occlusion: occlusion::FrontToBackBuilder::with_capacity(cap, cap),
577            });
578        }
579
580        // NOTE: Tiles here are being iterated in front-to-back order by
581        //       z-id, due to the sort in composite_state.end_frame()
582        for (idx, tile) in composite_state.tiles.iter().enumerate() {
583            let device_tile_box = composite_state.get_device_rect(
584                &tile.local_rect,
585                tile.transform_index
586            );
587
588            if let Some(ref _compositor) = self.compositor_config.layer_compositor() {
589                match tile.tile_id {
590                    Some(tile_id) => {
591                        layer_compositor_frame_state.
592                            tile_states
593                            .insert(
594                            tile_id,
595                            CompositeTileState {
596                                local_rect: tile.local_rect,
597                                local_valid_rect: tile.local_valid_rect,
598                                device_clip_rect: tile.device_clip_rect,
599                                z_id: tile.z_id,
600                                device_tile_box: device_tile_box,
601                                visible_rects: Vec::new(),
602                            },
603                        );
604                    }
605                    None => {}
606                }
607            }
608
609            // Simple compositor needs the valid rect in device space to match clip rect
610            let device_valid_rect = composite_state
611                .get_device_rect(&tile.local_valid_rect, tile.transform_index);
612
613            let rect = device_tile_box
614                .intersection_unchecked(&tile.device_clip_rect)
615                .intersection_unchecked(&device_valid_rect);
616
617            if rect.is_empty() {
618                continue;
619            }
620
621            let mut disable_external_composite = enable_screenshot;
622            if let CompositeTileSurface::ExternalSurface { .. } = tile.surface {
623                let transformed_rect = composite_state.get_device_rect(
624                    &tile.local_rect,
625                    tile.transform_index
626                );
627                if let None = transformed_rect.try_cast::<i16>() {
628                    // Disable external composite when rect is big.
629                    disable_external_composite = true;
630                }
631            }
632
633            // Determine if the tile is an external surface or content
634            let usage = match tile.surface {
635                CompositeTileSurface::Texture { .. } |
636                CompositeTileSurface::Color { .. } => {
637                    CompositorSurfaceUsage::Content
638                }
639                CompositeTileSurface::ExternalSurface { external_surface_index } => {
640                    match (self.current_compositor_kind, disable_external_composite) {
641                        (CompositorKind::Native { .. }, _) | (CompositorKind::Draw { .. }, _) => {
642                            CompositorSurfaceUsage::Content
643                        }
644                        (CompositorKind::Layer { .. }, true) => {
645                            CompositorSurfaceUsage::Content
646                        }
647                        (CompositorKind::Layer { .. }, false) => {
648                            let surface = &composite_state.external_surfaces[external_surface_index.0];
649
650                            // TODO(gwc): For now, we only select a hardware overlay swapchain if we
651                            // have an external image, but it may make sense to do for compositor
652                            // surfaces without in future.
653                            match surface.external_image_id {
654                                Some(external_image_id) => {
655                                    let image_key = match surface.color_data {
656                                        ResolvedExternalSurfaceColorData::Rgb { image_dependency, .. } => image_dependency.key,
657                                        ResolvedExternalSurfaceColorData::Yuv { image_dependencies, .. } => image_dependencies[0].key,
658                                    };
659
660                                    CompositorSurfaceUsage::External {
661                                        image_key,
662                                        external_image_id,
663                                        transform_index: tile.transform_index,
664                                    }
665                                }
666                                None => {
667                                    CompositorSurfaceUsage::Content
668                                }
669                            }
670                        }
671                    }
672                }
673            };
674
675            if let Some(ref _compositor) = self.compositor_config.layer_compositor() {
676                if let CompositeTileSurface::ExternalSurface { .. } = tile.surface {
677                    assert!(tile.tile_id.is_none());
678                    // ExternalSurface is not promoted to external composite.
679                    if let CompositorSurfaceUsage::Content = usage {
680                        layer_compositor_frame_state.rects_without_id.push(rect);
681                    }
682                } else {
683                    assert!(tile.tile_id.is_some());
684                }
685            }
686
687            // Determine whether we need a new layer, and if so, what kind
688            let new_layer_kind = match input_layers.last() {
689                Some(curr_layer) => {
690                    match (curr_layer.usage, usage) {
691                        // Content -> content, composite in to same layer
692                        (CompositorSurfaceUsage::Content, CompositorSurfaceUsage::Content) => None,
693                        (CompositorSurfaceUsage::External { .. }, CompositorSurfaceUsage::Content) => Some(usage),
694
695                        // Switch of layer type, or video -> video, need new swapchain
696                        (CompositorSurfaceUsage::Content, CompositorSurfaceUsage::External { .. }) |
697                        (CompositorSurfaceUsage::External { .. }, CompositorSurfaceUsage::External { .. }) => {
698                            // Only create a new layer if we're using LayerCompositor
699                            match self.compositor_config {
700                                CompositorConfig::Draw { .. } | CompositorConfig::Native { .. } => None,
701                                CompositorConfig::Layer { .. } => {
702                                    Some(usage)
703                                }
704                            }
705                        }
706                        (CompositorSurfaceUsage::DebugOverlay, _) => {
707                            Some(usage)
708                        }
709                        // Should not encounter debug layers as new layer
710                        (_, CompositorSurfaceUsage::DebugOverlay) => {
711                            unreachable!();
712                        }
713                    }
714                }
715                None => {
716                    // No layers yet, so we need a new one
717                    Some(usage)
718                }
719            };
720
721            if let Some(new_layer_kind) = new_layer_kind {
722                let (offset, clip_rect, is_opaque, rounded_clip_rect, rounded_clip_radii) = match usage {
723                    CompositorSurfaceUsage::Content => {
724                        (
725                            DeviceIntPoint::zero(),
726                            device_size.into(),
727                            false,      // Assume not opaque, we'll calculate this later
728                            device_size.into(),
729                            ClipRadius::EMPTY,
730                        )
731                    }
732                    CompositorSurfaceUsage::External { .. } => {
733                        let rect = composite_state.get_device_rect(
734                            &tile.local_rect,
735                            tile.transform_index
736                        );
737
738                        let clip_rect = tile.device_clip_rect.to_i32();
739                        let is_opaque = tile.kind != TileKind::Alpha;
740
741                        if self.debug_flags.contains(DebugFlags::EXTERNAL_COMPOSITE_BORDERS) {
742                            self.external_composite_debug_items.push(DebugItem::Rect {
743                                outer_color: debug_colors::ORANGERED,
744                                inner_color: ColorF { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
745                                rect: tile.device_clip_rect,
746                                thickness: 10,
747                            });
748                        }
749
750                        let (rounded_clip_rect, rounded_clip_radii) = match tile.clip_index {
751                            Some(clip_index) => {
752                                let clip = composite_state.get_compositor_clip(clip_index);
753                                let radius = ClipRadius {
754                                    top_left: clip.radius.top_left.width.round() as i32,
755                                    top_right: clip.radius.top_right.width.round() as i32,
756                                    bottom_left: clip.radius.bottom_left.width.round() as i32,
757                                    bottom_right: clip.radius.bottom_right.width.round() as i32,
758                                };
759                                (clip.rect.to_i32(), radius)
760                            }
761                            None => {
762                                (clip_rect, ClipRadius::EMPTY)
763                            }
764                        };
765
766                        (
767                            rect.min.to_i32(),
768                            clip_rect,
769                            is_opaque,
770                            rounded_clip_rect,
771                            rounded_clip_radii,
772                        )
773                    }
774                    CompositorSurfaceUsage::DebugOverlay => unreachable!(),
775                };
776
777                input_layers.push(CompositorInputLayer {
778                    usage: new_layer_kind,
779                    is_opaque,
780                    offset,
781                    clip_rect,
782                    rounded_clip_rect,
783                    rounded_clip_radii,
784                });
785
786                swapchain_layers.push(SwapChainLayer {
787                    occlusion: occlusion::FrontToBackBuilder::with_capacity(cap, cap),
788                })
789            }
790            tile_index_to_layer_index[idx] = Some(input_layers.len() - 1);
791
792            // Caluclate actual visible tile's rects
793
794            let is_opaque = tile.kind == TileKind::Opaque;
795
796            match tile.clip_index {
797                Some(clip_index) => {
798                    let clip = composite_state.get_compositor_clip(clip_index);
799
800                    // TODO(gw): Make segment builder generic on unit to avoid casts below.
801                    segment_builder.initialize(
802                        rect.cast_unit(),
803                        None,
804                        rect.cast_unit(),
805                    );
806                    segment_builder.push_clip_rect(
807                        clip.rect.cast_unit(),
808                        Some(clip.radius),
809                        ClipMode::Clip,
810                    );
811                    segment_builder.build(|segment| {
812                        let key = OcclusionItemKey { tile_index: idx, needs_mask: segment.has_mask };
813
814                        full_render_occlusion.add(
815                            &segment.rect.cast_unit(),
816                            is_opaque && !segment.has_mask,
817                            key,
818                        );
819                    });
820                }
821                None => {
822                    full_render_occlusion.add(&rect, is_opaque, OcclusionItemKey {
823                        tile_index: idx,
824                        needs_mask: false,
825                    });
826                }
827            }
828        }
829
830        assert_eq!(swapchain_layers.len(), input_layers.len());
831
832        if window_is_opaque {
833            match input_layers.last_mut() {
834                Some(_layer) => {
835                    // If the window is opaque, and the last(back) layer is
836                    //  a content layer then mark that as opaque.
837                    // TODO: This causes talos performance regressions.
838                    // if let CompositorSurfaceUsage::Content = layer.usage {
839                    //     layer.is_opaque = true;
840                    // }
841                }
842                None => {
843                    // If no tiles were present, and we expect an opaque window,
844                    // add an empty layer to force a composite that clears the screen,
845                    // to match existing semantics.
846                    input_layers.push(CompositorInputLayer {
847                        usage: CompositorSurfaceUsage::Content,
848                        is_opaque: true,
849                        offset: DeviceIntPoint::zero(),
850                        clip_rect: device_size.into(),
851                        rounded_clip_rect: device_size.into(),
852                        rounded_clip_radii: ClipRadius::EMPTY,
853                    });
854
855                    swapchain_layers.push(SwapChainLayer {
856                        occlusion: occlusion::FrontToBackBuilder::with_capacity(cap, cap),
857                    });
858                }
859            }
860        }
861
862        let mut full_render = self.debug_overlay_state.is_enabled;
863
864        // Start compositing if using OS compositor
865        if let Some(ref mut compositor) = self.compositor_config.layer_compositor() {
866            let input = CompositorInputConfig {
867                enable_screenshot,
868                layers: &input_layers,
869            };
870            full_render |= compositor.begin_frame(&input);
871        }
872
873        // Full render is requested when layer tree is updated.
874        let mut partial_present_mode = if full_render {
875            None
876        } else {
877            partial_present_mode
878        };
879
880        assert_eq!(swapchain_layers.len(), input_layers.len());
881
882        // Recalculate dirty rect for layer compositor
883        if let Some(ref _compositor) = self.compositor_config.layer_compositor() {
884            // Set visible rests of current frame to each tile's CompositeTileState.
885            for item in full_render_occlusion
886            .opaque_items()
887            .iter()
888            .chain(full_render_occlusion.alpha_items().iter()) {
889                let tile = &composite_state.tiles[item.key.tile_index];
890                match tile.tile_id {
891                    Some(tile_id) => {
892                        if let Some(tile_state) = layer_compositor_frame_state.tile_states.get_mut(&tile_id) {
893                            tile_state.visible_rects.push(item.rectangle);
894                        } else {
895                            unreachable!();
896                        }
897                    }
898                    None => {}
899                }
900            }
901
902            let can_use_partial_present =
903                !self.force_redraw && !full_render &&
904                self.layer_compositor_frame_state_in_prev_frame.is_some();
905
906            if can_use_partial_present {
907                let mut combined_dirty_rect = DeviceRect::zero();
908
909                for tile in composite_state.tiles.iter() {
910                    if tile.tile_id.is_none() {
911                        match tile.surface {
912                            CompositeTileSurface::ExternalSurface { .. } => {}
913                            CompositeTileSurface::Texture { .. }  |
914                            CompositeTileSurface::Color { .. } => {
915                                unreachable!();
916                            },
917                        }
918                        continue;
919                    }
920
921                    assert!(tile.tile_id.is_some());
922
923                    let tiles_exists_in_prev_frame =
924                        self.layer_compositor_frame_state_in_prev_frame
925                        .as_ref()
926                        .unwrap()
927                        .tile_states
928                        .contains_key(&tile.tile_id.unwrap());
929                    let tile_id = tile.tile_id.unwrap();
930                    let tile_state = layer_compositor_frame_state.tile_states.get(&tile_id).unwrap();
931
932                    if tiles_exists_in_prev_frame {
933                        let prev_tile_state = self.layer_compositor_frame_state_in_prev_frame
934                            .as_ref()
935                            .unwrap()
936                            .tile_states
937                            .get(&tile_id)
938                            .unwrap();
939
940                        if tile_state.same_state(prev_tile_state) {
941                            // Case that tile is same state in previous frame and current frame.
942                            // Intersection of tile's dirty rect and tile's visible rects are actual dirty rects.
943                            let dirty_rect = composite_state.get_device_rect(
944                                &tile.local_dirty_rect,
945                                tile.transform_index,
946                            );
947                            for rect in tile_state.visible_rects.iter()  {
948                                let visible_dirty_rect = rect.intersection(&dirty_rect);
949                                if visible_dirty_rect.is_some() {
950                                    combined_dirty_rect = combined_dirty_rect.union(&visible_dirty_rect.unwrap());
951                                }
952                            }
953                        } else {
954                            // If tile is rendered in previous frame, but its state is different,
955                            // both visible rects in previous frame and current frame are dirty rects.
956                            for rect in tile_state.visible_rects
957                                .iter()
958                                .chain(prev_tile_state.visible_rects.iter())  {
959                                combined_dirty_rect = combined_dirty_rect.union(&rect);
960                            }
961                        }
962                    } else {
963                        // If tile is not rendered in previous frame, its all visible rects are dirty rects.
964                        for rect in &tile_state.visible_rects {
965                            combined_dirty_rect = combined_dirty_rect.union(&rect);
966                        }
967                    }
968                }
969
970                // Case that tile is rendered in pervious frame, but not in current frame.
971                for (tile_id, tile_state) in self.layer_compositor_frame_state_in_prev_frame
972                    .as_ref()
973                    .unwrap()
974                    .tile_states
975                    .iter() {
976                    if !layer_compositor_frame_state.tile_states.contains_key(&tile_id) {
977                        for rect in tile_state.visible_rects.iter()  {
978                            combined_dirty_rect = combined_dirty_rect.union(&rect);
979                        }
980                    }
981                }
982
983                // Case that ExternalSurface is not promoted to external composite.
984                for rect in layer_compositor_frame_state
985                    .rects_without_id
986                    .iter()
987                    .chain(self.layer_compositor_frame_state_in_prev_frame.as_ref().unwrap().rects_without_id.iter())  {
988                    combined_dirty_rect = combined_dirty_rect.union(&rect);
989                }
990
991                let device_rect = DeviceRect::from_size(device_size.to_f32());
992                let clipped_dirty_rect = combined_dirty_rect.intersection_unchecked(&device_rect);
993
994                partial_present_mode = Some(PartialPresentMode::Single {
995                    dirty_rect: clipped_dirty_rect,
996                });
997            } else {
998                partial_present_mode = None;
999            }
1000
1001            self.layer_compositor_frame_state_in_prev_frame = Some(layer_compositor_frame_state);
1002        }
1003
1004        // Check tiles handling with partial_present_mode
1005
1006        let mut opaque_rounded_corners: HashSet<CompositeRoundedCorner> = HashSet::new();
1007
1008        // NOTE: Tiles here are being iterated in front-to-back order by
1009        //       z-id, due to the sort in composite_state.end_frame()
1010        for (idx, tile) in composite_state.tiles.iter().enumerate() {
1011            let device_tile_box = composite_state.get_device_rect(
1012                &tile.local_rect,
1013                tile.transform_index
1014            );
1015
1016            // Determine a clip rect to apply to this tile, depending on what
1017            // the partial present mode is.
1018            let partial_clip_rect = match partial_present_mode {
1019                Some(PartialPresentMode::Single { dirty_rect }) => dirty_rect,
1020                None => device_tile_box,
1021            };
1022
1023            // Simple compositor needs the valid rect in device space to match clip rect
1024            let device_valid_rect = composite_state
1025                .get_device_rect(&tile.local_valid_rect, tile.transform_index);
1026
1027            let rect = device_tile_box
1028                .intersection_unchecked(&tile.device_clip_rect)
1029                .intersection_unchecked(&partial_clip_rect)
1030                .intersection_unchecked(&device_valid_rect);
1031
1032            if rect.is_empty() {
1033                continue;
1034            }
1035
1036            let layer_index = match tile_index_to_layer_index[idx] {
1037                None => {
1038                    // The rect of partial present should be subset of the rect of full render.
1039                    error!("rect {:?} should have valid layer index", rect);
1040                    continue;
1041                }
1042                Some(layer_index) => layer_index,
1043            };
1044
1045            // For normal tiles, add to occlusion tracker
1046            let layer = &mut swapchain_layers[layer_index];
1047
1048            let is_opaque = tile.kind == TileKind::Opaque;
1049
1050            match tile.clip_index {
1051                Some(clip_index) => {
1052                    let clip = composite_state.get_compositor_clip(clip_index);
1053
1054                        // TODO(gw): Make segment builder generic on unit to avoid casts below.
1055                    segment_builder.initialize(
1056                        rect.cast_unit(),
1057                        None,
1058                        rect.cast_unit(),
1059                    );
1060                    segment_builder.push_clip_rect(
1061                        clip.rect.cast_unit(),
1062                        Some(clip.radius),
1063                        ClipMode::Clip,
1064                    );
1065                    segment_builder.build(|segment| {
1066                        let key = OcclusionItemKey { tile_index: idx, needs_mask: segment.has_mask };
1067
1068                        let radius = if segment.edge_flags ==
1069                            EdgeMask::TOP | EdgeMask::LEFT &&
1070                            !clip.radius.top_left.is_empty() {
1071                            Some(clip.radius.top_left)
1072                        } else if segment.edge_flags ==
1073                            EdgeMask::TOP | EdgeMask::RIGHT &&
1074                            !clip.radius.top_right.is_empty() {
1075                            Some(clip.radius.top_right)
1076                        } else if segment.edge_flags ==
1077                            EdgeMask::BOTTOM | EdgeMask::LEFT &&
1078                            !clip.radius.bottom_left.is_empty() {
1079                            Some(clip.radius.bottom_left)
1080                        } else if segment.edge_flags ==
1081                            EdgeMask::BOTTOM | EdgeMask::RIGHT &&
1082                            !clip.radius.bottom_right.is_empty() {
1083                            Some(clip.radius.bottom_right)
1084                        } else {
1085                            None
1086                        };
1087
1088                        if let Some(radius) = radius {
1089                            let rounded_corner = CompositeRoundedCorner {
1090                                    rect: segment.rect.cast_unit(),
1091                                    radius: radius,
1092                                    edge_flags: segment.edge_flags,
1093                            };
1094
1095                            // Drop overdraw rounded rect
1096                            if opaque_rounded_corners.contains(&rounded_corner) {
1097                                return;
1098                            }
1099
1100                            if is_opaque {
1101                                opaque_rounded_corners.insert(rounded_corner);
1102                            }
1103                        }
1104
1105                        layer.occlusion.add(
1106                            &segment.rect.cast_unit(),
1107                            is_opaque && !segment.has_mask,
1108                            key,
1109                        );
1110                    });
1111                }
1112                None => {
1113                    layer.occlusion.add(&rect, is_opaque, OcclusionItemKey {
1114                        tile_index: idx,
1115                        needs_mask: false,
1116                    });
1117                }
1118            }
1119        }
1120
1121        assert_eq!(swapchain_layers.len(), input_layers.len());
1122        let mut content_clear_color = Some(self.clear_color);
1123
1124        for (layer_index, (layer, swapchain_layer)) in input_layers.iter().zip(swapchain_layers.iter()).enumerate() {
1125            self.device.reset_state();
1126
1127            // Skip compositing external images or debug layers here
1128            match layer.usage {
1129                CompositorSurfaceUsage::Content => {}
1130                CompositorSurfaceUsage::External { .. } | CompositorSurfaceUsage::DebugOverlay => {
1131                    continue;
1132                }
1133            }
1134
1135            // Only use supplied clear color for first content layer we encounter
1136            let clear_color = content_clear_color.take().unwrap_or(ColorF::TRANSPARENT);
1137
1138            if let Some(ref mut _compositor) = self.compositor_config.layer_compositor() {
1139                if let Some(PartialPresentMode::Single { dirty_rect }) = partial_present_mode {
1140                    let device_rect = DeviceRect::from_size(device_size.to_f32());
1141                    let clipped_dirty_rect = dirty_rect.intersection_unchecked(&device_rect);
1142                    if clipped_dirty_rect.is_empty() {
1143                        continue;
1144                    }
1145                }
1146            }
1147
1148            let draw_target = match self.compositor_config {
1149                CompositorConfig::Layer { ref mut compositor } => {
1150                    match partial_present_mode {
1151                        Some(PartialPresentMode::Single { dirty_rect }) => {
1152                            compositor.bind_layer(layer_index, &[dirty_rect.to_i32()]);
1153                        }
1154                        None => {
1155                            compositor.bind_layer(layer_index, &[]);
1156                        }
1157                    };
1158
1159                    DrawTarget::NativeSurface {
1160                        offset: -layer.offset,
1161                        external_fbo_id: 0,
1162                        dimensions: frame_device_size,
1163                    }
1164                }
1165                // Native can be hit when switching compositors (disable when using Layer)
1166                CompositorConfig::Draw { .. } | CompositorConfig::Native { .. } => {
1167                    fb_draw_target
1168                }
1169            };
1170
1171            // TODO(gwc): When supporting external attached swapchains, need to skip the composite pass here
1172
1173            // Draw each compositing pass in to a swap chain
1174            self.composite_pass(
1175                composite_state,
1176                draw_target,
1177                clear_color,
1178                projection,
1179                results,
1180                partial_present_mode,
1181                swapchain_layer,
1182            );
1183
1184            if let Some(ref mut compositor) = self.compositor_config.layer_compositor() {
1185                match partial_present_mode {
1186                    Some(PartialPresentMode::Single { dirty_rect }) => {
1187                        compositor.present_layer(layer_index, &[dirty_rect.to_i32()]);
1188                    }
1189                    None => {
1190                        compositor.present_layer(layer_index, &[]);
1191                    }
1192                };
1193            }
1194        }
1195
1196        // End frame notify for experimental compositor
1197        if let Some(ref mut compositor) = self.compositor_config.layer_compositor() {
1198            for (layer_index, layer) in input_layers.iter().enumerate() {
1199                // External surfaces need transform applied, but content
1200                // surfaces are always at identity
1201                let transform = match layer.usage {
1202                    CompositorSurfaceUsage::Content => CompositorSurfaceTransform::identity(),
1203                    CompositorSurfaceUsage::External { transform_index, .. } => composite_state.get_compositor_transform(transform_index),
1204                    CompositorSurfaceUsage::DebugOverlay => CompositorSurfaceTransform::identity(),
1205                };
1206
1207                compositor.add_surface(
1208                    layer_index,
1209                    transform,
1210                    layer.clip_rect,
1211                    ImageRendering::Auto,
1212                    layer.rounded_clip_rect,
1213                    layer.rounded_clip_radii,
1214                );
1215            }
1216        }
1217    }
1218
1219    pub(super) fn composite_frame(
1220        &mut self,
1221        frame: &mut Frame,
1222        device_size: Option<DeviceIntSize>,
1223        results: &mut RenderResults,
1224        present_mode: Option<PartialPresentMode>,
1225    ) {
1226        profile_scope!("main target");
1227        if let Some(device_size) = device_size {
1228            if let Some(history) = &mut self.command_log {
1229                history.begin_render_target("Window", device_size);
1230            }
1231
1232            results.stats.color_target_count += 1;
1233            results.picture_cache_debug = mem::replace(
1234                &mut frame.composite_state.picture_cache_debug,
1235                PictureCacheDebugInfo::new(),
1236            );
1237            results.did_rasterize_any_tile = frame.composite_state.did_rasterize_any_tile;
1238
1239            let size = frame.device_rect.size().to_f32();
1240            let surface_origin_is_top_left = self.device.surface_origin_is_top_left();
1241            let (bottom, top) = if surface_origin_is_top_left {
1242                (0.0, size.height)
1243            } else {
1244                (size.height, 0.0)
1245            };
1246
1247            let projection = Transform3D::ortho(
1248                0.0,
1249                size.width,
1250                bottom,
1251                top,
1252                self.device.ortho_near_plane(),
1253                self.device.ortho_far_plane(),
1254            );
1255
1256            let fb_scale = Scale::<_, _, FramebufferPixel>::new(1i32);
1257            let mut fb_rect = frame.device_rect * fb_scale;
1258
1259            if !surface_origin_is_top_left {
1260                let h = fb_rect.height();
1261                fb_rect.min.y = device_size.height - fb_rect.max.y;
1262                fb_rect.max.y = fb_rect.min.y + h;
1263            }
1264
1265            let draw_target = DrawTarget::Default {
1266                rect: fb_rect,
1267                total_size: device_size * fb_scale,
1268                surface_origin_is_top_left,
1269            };
1270
1271            // If we have a native OS compositor, then make use of that interface
1272            // to specify how to composite each of the picture cache surfaces.
1273            match self.current_compositor_kind {
1274                CompositorKind::Native { .. } => {
1275                    // We have already queued surfaces for early native composition by this point.
1276                    // All that is left is to finally update any external native surfaces that were
1277                    // invalidated so that composition can complete.
1278                    self.update_external_native_surfaces(
1279                        &frame.composite_state.external_surfaces,
1280                        results,
1281                    );
1282                }
1283                CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => {
1284                    self.composite_simple(
1285                        &frame.composite_state,
1286                        frame.device_rect.size(),
1287                        draw_target,
1288                        &projection,
1289                        results,
1290                        present_mode,
1291                        device_size,
1292                    );
1293                }
1294            }
1295            // Reset force_redraw. It was used in composite_simple() with layer compositor.
1296            self.force_redraw = false;
1297        } else {
1298            // Rendering a frame without presenting it will confuse the partial
1299            // present logic, so force a full present for the next frame.
1300            self.force_redraw = true;
1301        }
1302    }
1303
1304    /// Update the dirty rects based on current compositing mode and config
1305    // TODO(gw): This can be tidied up significantly once the Draw compositor
1306    //           is implemented in terms of the compositor trait.
1307    pub(super) fn calculate_dirty_rects(
1308        &mut self,
1309        buffer_age: usize,
1310        composite_state: &CompositeState,
1311        draw_target_dimensions: DeviceIntSize,
1312        results: &mut RenderResults,
1313    ) -> Option<PartialPresentMode> {
1314        if let Some(ref _compositor) = self.compositor_config.layer_compositor() {
1315            // Calculate dirty rects of layer compositor in composite_simple()
1316            return None;
1317        }
1318
1319        let mut partial_present_mode = None;
1320
1321        let (max_partial_present_rects, draw_previous_partial_present_regions) =
1322            match self.current_compositor_kind {
1323                CompositorKind::Native { .. } => {
1324                    // Assume that we can return a single dirty rect for native
1325                    // compositor for now, and that there is no buffer-age functionality.
1326                    // These params can be exposed by the compositor capabilities struct
1327                    // as the Draw compositor is ported to use it.
1328                    (1, false)
1329                }
1330                CompositorKind::Draw {
1331                    draw_previous_partial_present_regions,
1332                    max_partial_present_rects,
1333                } => (
1334                    max_partial_present_rects,
1335                    draw_previous_partial_present_regions,
1336                ),
1337                CompositorKind::Layer { .. } => {
1338                    unreachable!();
1339                }
1340            };
1341
1342        if max_partial_present_rects > 0 {
1343            let prev_frames_damage_rect = if let Some(..) = self.compositor_config.partial_present()
1344            {
1345                self.buffer_damage_tracker
1346                    .get_damage_rect(buffer_age)
1347                    .or_else(|| Some(DeviceRect::from_size(draw_target_dimensions.to_f32())))
1348            } else {
1349                None
1350            };
1351
1352            let can_use_partial_present = composite_state.dirty_rects_are_valid
1353                && !self.force_redraw
1354                && !(prev_frames_damage_rect.is_none() && draw_previous_partial_present_regions)
1355                && !self.debug_overlay_state.is_enabled;
1356
1357            if can_use_partial_present {
1358                let mut combined_dirty_rect = DeviceRect::zero();
1359                let fb_rect = DeviceRect::from_size(draw_target_dimensions.to_f32());
1360
1361                // Work out how many dirty rects WR produced, and if that's more than
1362                // what the device supports.
1363                for tile in &composite_state.tiles {
1364                    let dirty_rect = composite_state
1365                        .get_device_rect(&tile.local_dirty_rect, tile.transform_index);
1366
1367                    // In pathological cases where a tile is extremely zoomed, it
1368                    // may end up with device coords outside the range of an i32,
1369                    // so clamp it to the frame buffer rect here, before it gets
1370                    // casted to an i32 rect below.
1371                    if let Some(dirty_rect) = dirty_rect.intersection(&fb_rect) {
1372                        combined_dirty_rect = combined_dirty_rect.union(&dirty_rect);
1373                    }
1374                }
1375
1376                let combined_dirty_rect = combined_dirty_rect.round();
1377                let combined_dirty_rect_i32 = combined_dirty_rect.to_i32();
1378                // Return this frame's dirty region. If nothing has changed, don't return any dirty
1379                // rects at all (the client can use this as a signal to skip present completely).
1380                if !combined_dirty_rect.is_empty() {
1381                    results.dirty_rects.push(combined_dirty_rect_i32);
1382                }
1383
1384                // Track this frame's dirty region, for calculating subsequent frames' damage.
1385                if draw_previous_partial_present_regions {
1386                    self.buffer_damage_tracker
1387                        .push_dirty_rect(&combined_dirty_rect);
1388                }
1389
1390                // If the implementation requires manually keeping the buffer consistent,
1391                // then we must combine this frame's dirty region with that of previous frames
1392                // to determine the total_dirty_rect. The is used to determine what region we
1393                // render to, and is what we send to the compositor as the buffer damage region
1394                // (eg for KHR_partial_update).
1395                let total_dirty_rect = if draw_previous_partial_present_regions {
1396                    combined_dirty_rect.union(&prev_frames_damage_rect.unwrap())
1397                } else {
1398                    combined_dirty_rect
1399                };
1400
1401                partial_present_mode = Some(PartialPresentMode::Single {
1402                    dirty_rect: total_dirty_rect,
1403                });
1404            } else {
1405                // If we don't have a valid partial present scenario, return a single
1406                // dirty rect to the client that covers the entire framebuffer.
1407                let fb_rect = DeviceIntRect::from_size(draw_target_dimensions);
1408                results.dirty_rects.push(fb_rect);
1409
1410                if draw_previous_partial_present_regions {
1411                    self.buffer_damage_tracker
1412                        .push_dirty_rect(&fb_rect.to_f32());
1413                }
1414            }
1415        }
1416
1417        partial_present_mode
1418    }
1419}