smithay/backend/drm/compositor/
frame_result.rs

1use std::collections::HashSet;
2
3use crate::{
4    backend::{
5        allocator::{
6            dmabuf::{AsDmabuf, Dmabuf},
7            Buffer, Slot,
8        },
9        drm::Framebuffer,
10        renderer::{
11            damage::OutputDamageTracker,
12            element::{Element, Id, RenderElement, RenderElementStates},
13            sync::SyncPoint,
14            utils::{CommitCounter, DamageSet, DamageSnapshot, OpaqueRegions},
15            Bind, Blit, Color32F, Frame, Renderer,
16        },
17    },
18    output::OutputNoMode,
19    utils::{Buffer as BufferCoords, Physical, Point, Rectangle, Scale, Size, Transform},
20};
21
22use super::{DrmScanoutBuffer, ScanoutBuffer};
23
24/// Result for [`DrmCompositor::render_frame`][super::DrmCompositor::render_frame]
25///
26/// **Note**: This struct may contain a reference to the composited buffer
27/// of the primary display plane. Dropping it will remove said reference and
28/// allows the buffer to be reused.
29///
30/// Keeping the buffer longer may cause the following issues:
31/// - **Too much damage** - until the buffer is marked free it is not considered
32///   submitted by the swapchain, causing the age value of newly queried buffers
33///   to be lower than necessary, potentially resulting in more rendering than necessary.
34///   To avoid this make sure the buffer is dropped before starting the next render.
35/// - **Exhaustion of swapchain images** - Continuing rendering while holding on
36///   to too many buffers may cause the swapchain to run out of images, returning errors
37///   on rendering until buffers are freed again. The exact amount of images in a
38///   swapchain is an implementation detail, but should generally be expect to be
39///   large enough to hold onto at least one `RenderFrameResult`.
40pub struct RenderFrameResult<'a, B: Buffer, F: Framebuffer, E> {
41    /// If this frame contains any changes and should be submitted
42    pub is_empty: bool,
43    /// The render element states of this frame
44    pub states: RenderElementStates,
45    /// Element for the primary plane
46    pub primary_element: PrimaryPlaneElement<'a, B, F, E>,
47    /// Overlay elements in front to back order
48    pub overlay_elements: Vec<&'a E>,
49    /// Optional cursor plane element
50    ///
51    /// If set always above all other elements
52    pub cursor_element: Option<&'a E>,
53
54    pub(super) primary_plane_element_id: Id,
55    pub(super) supports_fencing: bool,
56}
57
58impl<B: Buffer, F: Framebuffer, E> RenderFrameResult<'_, B, F, E> {
59    /// Returns if synchronization with kms submission can't be guaranteed through the available apis.
60    pub fn needs_sync(&self) -> bool {
61        if let PrimaryPlaneElement::Swapchain(ref element) = self.primary_element {
62            !self.supports_fencing || !element.sync.is_exportable()
63        } else {
64            false
65        }
66    }
67}
68
69struct SwapchainElement<'a, 'b, B: Buffer> {
70    id: Id,
71    slot: &'a Slot<B>,
72    transform: Transform,
73    damage: &'b DamageSnapshot<i32, BufferCoords>,
74}
75
76impl<B: Buffer> Element for SwapchainElement<'_, '_, B> {
77    fn id(&self) -> &Id {
78        &self.id
79    }
80
81    fn current_commit(&self) -> CommitCounter {
82        self.damage.current_commit()
83    }
84
85    fn src(&self) -> Rectangle<f64, BufferCoords> {
86        Rectangle::from_size(self.slot.size()).to_f64()
87    }
88
89    fn geometry(&self, _scale: Scale<f64>) -> Rectangle<i32, Physical> {
90        Rectangle::from_size(self.slot.size().to_logical(1, self.transform).to_physical(1))
91    }
92
93    fn transform(&self) -> Transform {
94        self.transform
95    }
96
97    fn damage_since(&self, scale: Scale<f64>, commit: Option<CommitCounter>) -> DamageSet<i32, Physical> {
98        self.damage
99            .damage_since(commit)
100            .map(|d| {
101                d.into_iter()
102                    .map(|d| d.to_logical(1, self.transform, &self.slot.size()).to_physical(1))
103                    .collect()
104            })
105            .unwrap_or_else(|| DamageSet::from_slice(&[self.geometry(scale)]))
106    }
107
108    fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
109        OpaqueRegions::from_slice(&[self.geometry(scale)])
110    }
111}
112
113enum FrameResultDamageElement<'a, 'b, E, B: Buffer> {
114    Element(&'a E),
115    Swapchain(SwapchainElement<'a, 'b, B>),
116}
117
118impl<E, B> Element for FrameResultDamageElement<'_, '_, E, B>
119where
120    E: Element,
121    B: Buffer,
122{
123    fn id(&self) -> &Id {
124        match self {
125            FrameResultDamageElement::Element(e) => e.id(),
126            FrameResultDamageElement::Swapchain(e) => e.id(),
127        }
128    }
129
130    fn current_commit(&self) -> CommitCounter {
131        match self {
132            FrameResultDamageElement::Element(e) => e.current_commit(),
133            FrameResultDamageElement::Swapchain(e) => e.current_commit(),
134        }
135    }
136
137    fn src(&self) -> Rectangle<f64, BufferCoords> {
138        match self {
139            FrameResultDamageElement::Element(e) => e.src(),
140            FrameResultDamageElement::Swapchain(e) => e.src(),
141        }
142    }
143
144    fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
145        match self {
146            FrameResultDamageElement::Element(e) => e.geometry(scale),
147            FrameResultDamageElement::Swapchain(e) => e.geometry(scale),
148        }
149    }
150
151    fn location(&self, scale: Scale<f64>) -> Point<i32, Physical> {
152        match self {
153            FrameResultDamageElement::Element(e) => e.location(scale),
154            FrameResultDamageElement::Swapchain(e) => e.location(scale),
155        }
156    }
157
158    fn transform(&self) -> Transform {
159        match self {
160            FrameResultDamageElement::Element(e) => e.transform(),
161            FrameResultDamageElement::Swapchain(e) => e.transform(),
162        }
163    }
164
165    fn damage_since(&self, scale: Scale<f64>, commit: Option<CommitCounter>) -> DamageSet<i32, Physical> {
166        match self {
167            FrameResultDamageElement::Element(e) => e.damage_since(scale, commit),
168            FrameResultDamageElement::Swapchain(e) => e.damage_since(scale, commit),
169        }
170    }
171
172    fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
173        match self {
174            FrameResultDamageElement::Element(e) => e.opaque_regions(scale),
175            FrameResultDamageElement::Swapchain(e) => e.opaque_regions(scale),
176        }
177    }
178}
179
180#[derive(Debug)]
181/// Defines the element for the primary plane
182pub enum PrimaryPlaneElement<'a, B: Buffer, F: Framebuffer, E> {
183    /// A slot from the swapchain was used for rendering
184    /// the primary plane
185    Swapchain(PrimarySwapchainElement<B, F>),
186    /// An element has been assigned for direct scan-out
187    Element(&'a E),
188}
189
190/// Error for [`RenderFrameResult::blit_frame_result`]
191#[derive(Debug, thiserror::Error)]
192pub enum BlitFrameResultError<R: std::error::Error, E: std::error::Error> {
193    /// A render error occurred
194    #[error(transparent)]
195    Rendering(R),
196    /// A error occurred during exporting the buffer
197    #[error(transparent)]
198    Export(E),
199}
200
201impl<B, F, E> RenderFrameResult<'_, B, F, E>
202where
203    B: Buffer,
204    F: Framebuffer,
205{
206    /// Get the damage of this frame for the specified dtr and age
207    pub fn damage_from_age<'d>(
208        &self,
209        damage_tracker: &'d mut OutputDamageTracker,
210        age: usize,
211        filter: impl IntoIterator<Item = Id>,
212    ) -> Result<(Option<&'d Vec<Rectangle<i32, Physical>>>, RenderElementStates), OutputNoMode>
213    where
214        E: Element,
215    {
216        #[allow(clippy::mutable_key_type)]
217        let filter_ids: HashSet<Id> = filter.into_iter().collect();
218
219        let mut elements: Vec<FrameResultDamageElement<'_, '_, E, B>> =
220            Vec::with_capacity(usize::from(self.cursor_element.is_some()) + self.overlay_elements.len() + 1);
221        if let Some(cursor) = self.cursor_element {
222            if !filter_ids.contains(cursor.id()) {
223                elements.push(FrameResultDamageElement::Element(cursor));
224            }
225        }
226
227        elements.extend(
228            self.overlay_elements
229                .iter()
230                .filter(|e| !filter_ids.contains(e.id()))
231                .map(|e| FrameResultDamageElement::Element(*e)),
232        );
233
234        let primary_render_element = match &self.primary_element {
235            PrimaryPlaneElement::Swapchain(PrimarySwapchainElement {
236                slot,
237                transform,
238                damage,
239                ..
240            }) => FrameResultDamageElement::Swapchain(SwapchainElement {
241                id: self.primary_plane_element_id.clone(),
242                transform: *transform,
243                slot: match &slot.buffer {
244                    ScanoutBuffer::Swapchain(slot) => slot,
245                    _ => unreachable!(),
246                },
247                damage,
248            }),
249            PrimaryPlaneElement::Element(e) => FrameResultDamageElement::Element(*e),
250        };
251
252        elements.push(primary_render_element);
253
254        damage_tracker.damage_output(age, &elements)
255    }
256}
257
258impl<'a, B, F, E> RenderFrameResult<'a, B, F, E>
259where
260    B: Buffer + AsDmabuf,
261    <B as AsDmabuf>::Error: std::fmt::Debug,
262    F: Framebuffer,
263{
264    /// Blit the frame result
265    #[allow(clippy::too_many_arguments)]
266    pub fn blit_frame_result<R>(
267        &self,
268        size: impl Into<Size<i32, Physical>>,
269        transform: Transform,
270        scale: impl Into<Scale<f64>>,
271        renderer: &mut R,
272        framebuffer: &mut R::Framebuffer<'_>,
273        damage: impl IntoIterator<Item = Rectangle<i32, Physical>>,
274        filter: impl IntoIterator<Item = Id>,
275    ) -> Result<SyncPoint, BlitFrameResultError<R::Error, <B as AsDmabuf>::Error>>
276    where
277        R: Renderer + Bind<Dmabuf> + Blit,
278        R::TextureId: 'static,
279        E: Element + RenderElement<R>,
280    {
281        let size = size.into();
282        let scale = scale.into();
283        #[allow(clippy::mutable_key_type)]
284        let filter_ids: HashSet<Id> = filter.into_iter().collect();
285        let damage = damage.into_iter().collect::<Vec<_>>();
286
287        // If we have no damage we can exit early
288        if damage.is_empty() {
289            return Ok(SyncPoint::signaled());
290        }
291
292        let mut opaque_regions: Vec<Rectangle<i32, Physical>> = Vec::new();
293
294        let mut elements_to_render: Vec<&'a E> =
295            Vec::with_capacity(usize::from(self.cursor_element.is_some()) + self.overlay_elements.len() + 1);
296
297        if let Some(cursor_element) = self.cursor_element.as_ref() {
298            if !filter_ids.contains(cursor_element.id()) {
299                elements_to_render.push(*cursor_element);
300                opaque_regions.extend(cursor_element.opaque_regions(scale));
301            }
302        }
303
304        for element in self
305            .overlay_elements
306            .iter()
307            .filter(|e| !filter_ids.contains(e.id()))
308        {
309            elements_to_render.push(element);
310            opaque_regions.extend(element.opaque_regions(scale));
311        }
312
313        let primary_dmabuf = match &self.primary_element {
314            PrimaryPlaneElement::Swapchain(PrimarySwapchainElement { slot, sync, .. }) => {
315                let dmabuf = match &slot.buffer {
316                    ScanoutBuffer::Swapchain(slot) => slot.export().map_err(BlitFrameResultError::Export)?,
317                    _ => unreachable!(),
318                };
319                let size = dmabuf.size();
320                let geometry = Rectangle::from_size(size.to_logical(1, Transform::Normal).to_physical(1));
321                opaque_regions.push(geometry);
322                Some((sync.clone(), dmabuf, geometry))
323            }
324            PrimaryPlaneElement::Element(e) => {
325                elements_to_render.push(*e);
326                opaque_regions.extend(e.opaque_regions(scale));
327                None
328            }
329        };
330
331        let clear_damage =
332            Rectangle::subtract_rects_many_in_place(damage.clone(), opaque_regions.iter().copied());
333
334        let mut sync: Option<SyncPoint> = None;
335        if !clear_damage.is_empty() {
336            tracing::trace!("clearing frame damage {:#?}", clear_damage);
337
338            let mut frame = renderer
339                .render(framebuffer, size, transform)
340                .map_err(BlitFrameResultError::Rendering)?;
341
342            frame
343                .clear(Color32F::BLACK, &clear_damage)
344                .map_err(BlitFrameResultError::Rendering)?;
345
346            sync = Some(frame.finish().map_err(BlitFrameResultError::Rendering)?);
347        }
348
349        // first do the potential blit
350        if let Some((sync, mut dmabuf, geometry)) = primary_dmabuf {
351            let blit_damage = damage
352                .iter()
353                .filter_map(|d| d.intersection(geometry))
354                .collect::<Vec<_>>();
355
356            tracing::trace!("blitting frame with damage: {:#?}", blit_damage);
357
358            renderer.wait(&sync).map_err(BlitFrameResultError::Rendering)?;
359            let fb = renderer
360                .bind(&mut dmabuf)
361                .map_err(BlitFrameResultError::Rendering)?;
362            for rect in blit_damage {
363                renderer
364                    .blit(
365                        &fb,
366                        framebuffer,
367                        rect,
368                        rect,
369                        crate::backend::renderer::TextureFilter::Linear,
370                    )
371                    .map_err(BlitFrameResultError::Rendering)?;
372            }
373        }
374
375        // then render the remaining elements if any
376        if !elements_to_render.is_empty() {
377            tracing::trace!("drawing {} frame element(s)", elements_to_render.len());
378
379            let mut frame = renderer
380                .render(framebuffer, size, transform)
381                .map_err(BlitFrameResultError::Rendering)?;
382
383            for element in elements_to_render.iter().rev() {
384                let src = element.src();
385                let dst = element.geometry(scale);
386                let element_damage = damage
387                    .iter()
388                    .filter_map(|d| {
389                        d.intersection(dst).map(|mut d| {
390                            d.loc -= dst.loc;
391                            d
392                        })
393                    })
394                    .collect::<Vec<_>>();
395
396                // no need to render without damage
397                if element_damage.is_empty() {
398                    continue;
399                }
400
401                tracing::trace!("drawing frame element with damage: {:#?}", element_damage);
402
403                element
404                    .draw(&mut frame, src, dst, &element_damage, &[])
405                    .map_err(BlitFrameResultError::Rendering)?;
406            }
407
408            Ok(frame.finish().map_err(BlitFrameResultError::Rendering)?)
409        } else {
410            Ok(sync.unwrap_or_default())
411        }
412    }
413}
414
415impl<B: Buffer + std::fmt::Debug, F: Framebuffer + std::fmt::Debug, E: std::fmt::Debug> std::fmt::Debug
416    for RenderFrameResult<'_, B, F, E>
417{
418    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
419        f.debug_struct("RenderFrameResult")
420            .field("is_empty", &self.is_empty)
421            .field("states", &self.states)
422            .field("primary_element", &self.primary_element)
423            .field("overlay_elements", &self.overlay_elements)
424            .field("cursor_element", &self.cursor_element)
425            .finish()
426    }
427}
428
429#[derive(Debug)]
430/// Defines the element for the primary plane in cases where a composited buffer was used.
431pub struct PrimarySwapchainElement<B: Buffer, F: Framebuffer> {
432    /// The slot from the swapchain
433    pub(super) slot: DrmScanoutBuffer<B, F>,
434    /// Sync point
435    pub sync: SyncPoint,
436    /// The transform applied during rendering
437    pub transform: Transform,
438    /// The damage on the primary plane
439    pub damage: DamageSnapshot<i32, BufferCoords>,
440}
441
442impl<B: Buffer, F: Framebuffer> PrimarySwapchainElement<B, F> {
443    /// Access the underlying swapchain buffer
444    #[inline]
445    pub fn buffer(&self) -> &B {
446        match &self.slot.buffer {
447            ScanoutBuffer::Swapchain(slot) => slot,
448            _ => unreachable!(),
449        }
450    }
451}