Skip to main content

ferroui_base/rendering/composition/
composition_interop.rs

1//! The interop of a compositor with GPU objects made outside of the
2//! framework.
3//!
4//! Upstream one object serves both threads: the UI thread creates a
5//! `CompositionImportedGpuImage` and keeps it, jobs of the render thread
6//! import, use and dispose what is inside. Here each of these objects has
7//! two parts. The part the caller holds is an object of its thread. What the
8//! render thread works with (the context, the feature of the render
9//! interface, the imported object and the outcome of the import) is the
10//! server part, confined to the compositor lock in a [`LockBound`]: the
11//! jobs reach it there, and this thread enters the lock for the few things
12//! it reads from it.
13
14use super::server::{LockBound, LockedServerCompositor, ServerCompositor};
15use super::{
16    CompositionGpuImportedImageSynchronizationCapabilities, Compositor, ICompositionGpuImportedObject,
17    ICompositionGpuInterop, ICompositionImportableSharedGpuContextImage, ICompositionImportableSharedGpuContextSemaphore,
18    ICompositionImportedGpuImage, ICompositionImportedGpuSemaphore, ServerJobError, ServerJobTask,
19};
20use crate::platform::{
21    IExternalObjectsHandleWrapRenderInterfaceContextFeature, IExternalObjectsRenderInterfaceContextFeature,
22    IExternalObjectsWrappedGpuHandle, IPlatformHandle, IPlatformRenderInterfaceContext,
23    IPlatformRenderInterfaceImportedImage, IPlatformRenderInterfaceImportedSemaphore, PlatformGraphicsContextLostException,
24    PlatformGraphicsDrmFormat, PlatformGraphicsExternalImageProperties, PlatformHandle,
25};
26use std::cell::RefCell;
27use std::rc::Rc;
28use std::sync::Arc;
29
30fn same_context(a: &Rc<dyn IPlatformRenderInterfaceContext>, b: &Rc<dyn IPlatformRenderInterfaceContext>) -> bool {
31    std::ptr::addr_eq(Rc::as_ptr(a), Rc::as_ptr(b))
32}
33
34/// The server part of the interop: the context of the render interface and
35/// its features. Objects of the server side, kept inside the compositor
36/// lock.
37struct InteropState {
38    context: Rc<dyn IPlatformRenderInterfaceContext>,
39    external_objects: Rc<dyn IExternalObjectsRenderInterfaceContextFeature>,
40    external_objects_with_handle_wrap: Option<Rc<dyn IExternalObjectsHandleWrapRenderInterfaceContextFeature>>,
41}
42
43impl InteropState {
44    fn imported_object_base(&self) -> ServerGpuImportedObjectBase {
45        ServerGpuImportedObjectBase {
46            context: self.context.clone(),
47            feature: self.external_objects.clone(),
48            import_result: RefCell::new(None),
49        }
50    }
51}
52
53/// The server part of an interop and what the interop reads from its
54/// feature when it is constructed: made inside the compositor lock, on the
55/// thread that reads the feature, and handed to the thread of the
56/// compositor ([`CompositionInterop::server_part`]).
57pub(crate) struct CompositionInteropServerPart {
58    state: LockBound<InteropState>,
59    device_luid: Option<Vec<u8>>,
60    device_uuid: Option<Vec<u8>>,
61}
62
63/// The interop of a compositor with the GPU objects of its render
64/// interface context.
65pub struct CompositionInterop {
66    compositor: Rc<Compositor>,
67    state: LockBound<InteropState>,
68    device_luid: RefCell<Option<Vec<u8>>>,
69    device_uuid: RefCell<Option<Vec<u8>>>,
70}
71
72impl CompositionInterop {
73    /// Creates the interop over a feature of the render interface.
74    ///
75    /// The feature is an object of the server side and is kept inside the
76    /// compositor lock from here on: a caller that is not inside the lock
77    /// hands it over whole, without keeping a clone. (Upstream the
78    /// constructor runs in a job of the render thread.)
79    ///
80    /// # Panics
81    ///
82    /// When the compositor is confined to its render thread and that thread
83    /// has no context of the render interface: see [`try_new`](Self::try_new).
84    pub fn new(
85        compositor: &Rc<Compositor>,
86        external_objects: Rc<dyn IExternalObjectsRenderInterfaceContextFeature>,
87    ) -> Rc<CompositionInterop> {
88        match Self::try_new(compositor, external_objects) {
89            Some(interop) => interop,
90            None => panic!("the context of the render interface is created by the render thread, which has none"),
91        }
92    }
93
94    /// [`new`](Self::new), for a caller that can do without the interop.
95    ///
96    /// Upstream the constructor takes `RenderInterface.Value`, which creates
97    /// the context when there is none. The thread of a compositor that is
98    /// confined to its render thread may not create it: there the context
99    /// the render thread has is taken as it is, and without one the answer
100    /// is `None`.
101    pub(crate) fn try_new(
102        compositor: &Rc<Compositor>,
103        external_objects: Rc<dyn IExternalObjectsRenderInterfaceContextFeature>,
104    ) -> Option<Rc<CompositionInterop>> {
105        let locked = compositor.locked_server();
106        let may_create_context = !compositor.is_confined_to_render_thread();
107        let part =
108            compositor.with_server(|server| Self::server_part(&locked, server, external_objects, may_create_context))?;
109        Some(Self::from_server_part(compositor, part))
110    }
111
112    /// The server part of the interop over `external_objects`, made inside
113    /// the compositor lock (`server` is the proof): what the constructor
114    /// reads from the render interface upstream. The context is
115    /// `RenderInterface.Value` where the calling thread may create it
116    /// (`may_create_context`: the render thread, and the thread of a
117    /// compositor that is not confined to its render thread), otherwise the
118    /// context the render thread has, and `None` without one.
119    ///
120    /// The part may cross to the thread of the compositor, which makes the
121    /// interop around it ([`from_server_part`](Self::from_server_part)).
122    pub(crate) fn server_part(
123        locked: &Arc<LockedServerCompositor>,
124        server: &ServerCompositor,
125        external_objects: Rc<dyn IExternalObjectsRenderInterfaceContextFeature>,
126        may_create_context: bool,
127    ) -> Option<CompositionInteropServerPart> {
128        let context = if may_create_context {
129            server.render_interface().value()
130        } else {
131            server.render_interface().existing_backend_context()?
132        };
133        let external_objects_with_handle_wrap = {
134            let features: &dyn crate::platform::IOptionalFeatureProvider = &*context;
135            features.try_get::<dyn IExternalObjectsHandleWrapRenderInterfaceContextFeature>()
136        };
137        Some(CompositionInteropServerPart {
138            device_luid: external_objects.device_luid(),
139            device_uuid: external_objects.device_uuid(),
140            state: LockBound::new(
141                locked,
142                server,
143                InteropState { context, external_objects, external_objects_with_handle_wrap },
144            ),
145        })
146    }
147
148    /// The interop of `compositor` around its server part.
149    pub(crate) fn from_server_part(compositor: &Rc<Compositor>, part: CompositionInteropServerPart) -> Rc<Self> {
150        Rc::new(CompositionInterop {
151            compositor: compositor.clone(),
152            device_luid: RefCell::new(part.device_luid),
153            device_uuid: RefCell::new(part.device_uuid),
154            state: part.state,
155        })
156    }
157
158    /// Runs `f` with the server part, inside the compositor lock. Upstream
159    /// reads the feature from the UI thread as it is.
160    fn with_state<R>(&self, f: impl FnOnce(&ServerCompositor, &InteropState) -> R) -> R {
161        self.compositor.with_server(|server| {
162            let server: &ServerCompositor = server;
163            f(server, self.state.get(server))
164        })
165    }
166}
167
168impl ICompositionGpuInterop for CompositionInterop {
169    fn supported_image_handle_types(&self) -> Vec<String> {
170        self.with_state(|_, state| state.external_objects.supported_image_handle_types())
171    }
172
173    fn supported_semaphore_types(&self) -> Vec<String> {
174        self.with_state(|_, state| state.external_objects.supported_semaphore_types())
175    }
176
177    fn supported_dma_buf_formats(&self) -> Option<Vec<PlatformGraphicsDrmFormat>> {
178        self.with_state(|_, state| state.external_objects.supported_dma_buf_formats())
179    }
180
181    fn get_synchronization_capabilities(&self, image_handle_type: &str) -> CompositionGpuImportedImageSynchronizationCapabilities {
182        self.with_state(|_, state| state.external_objects.get_synchronization_capabilities(image_handle_type))
183    }
184
185    fn import_image(
186        &self,
187        handle: Rc<dyn IPlatformHandle>,
188        properties: PlatformGraphicsExternalImageProperties,
189    ) -> Rc<dyn ICompositionImportedGpuImage> {
190        let server = self.with_state(|server, state| {
191            let wrapped = state
192                .external_objects_with_handle_wrap
193                .as_ref()
194                .and_then(|wrap| wrap.wrap_image_handle_on_any_thread(&handle, properties.clone()));
195            let source = ImageSource::Handle(ImportHandle::new(wrapped, &*handle), properties);
196            Arc::new(self.compositor.bind_to_lock(server, ServerImportedGpuImage::new(state, source)))
197        });
198        CompositionImportedGpuImage::new(&self.compositor, server)
199    }
200
201    fn import_shared_image(&self, image: Arc<dyn ICompositionImportableSharedGpuContextImage>) -> Rc<dyn ICompositionImportedGpuImage> {
202        // The image is shared with the caller, who keeps it (see
203        // `ImageSource::Shared`).
204        let source = ImageSource::Shared(image);
205        let server = self.with_state(|server, state| {
206            Arc::new(self.compositor.bind_to_lock(server, ServerImportedGpuImage::new(state, source)))
207        });
208        CompositionImportedGpuImage::new(&self.compositor, server)
209    }
210
211    fn import_semaphore(&self, handle: Rc<dyn IPlatformHandle>) -> Rc<dyn ICompositionImportedGpuSemaphore> {
212        let server = self.with_state(|server, state| {
213            let wrapped = state
214                .external_objects_with_handle_wrap
215                .as_ref()
216                .and_then(|wrap| wrap.wrap_semaphore_handle_on_any_thread(&handle));
217            let handle = ImportHandle::new(wrapped, &*handle);
218            Arc::new(self.compositor.bind_to_lock(server, ServerImportedGpuSemaphore::new(state, handle)))
219        });
220        CompositionImportedGpuSemaphore::new(&self.compositor, server)
221    }
222
223    /// Not supported (`NotSupportedException` upstream).
224    fn import_shared_semaphore(
225        &self,
226        _image: Rc<dyn ICompositionImportableSharedGpuContextSemaphore>,
227    ) -> Rc<dyn ICompositionImportedGpuImage> {
228        panic!("Specified method is not supported.");
229    }
230
231    fn is_lost(&self) -> bool {
232        self.with_state(|_, state| state.context.is_lost())
233    }
234
235    fn device_luid(&self) -> Option<Vec<u8>> {
236        self.device_luid.borrow().clone()
237    }
238
239    fn set_device_luid(&self, value: Option<Vec<u8>>) {
240        *self.device_luid.borrow_mut() = value;
241    }
242
243    fn device_uuid(&self) -> Option<Vec<u8>> {
244        self.device_uuid.borrow().clone()
245    }
246
247    fn set_device_uuid(&self, value: Option<Vec<u8>>) {
248        *self.device_uuid.borrow_mut() = value;
249    }
250}
251
252/// The handle an object is imported from, as the render thread gets it.
253enum ImportHandle {
254    /// The handle the backend wrapped for the import. Made inside the
255    /// compositor lock and never handed to the caller: an object of the
256    /// server side.
257    Wrapped(Rc<dyn IExternalObjectsWrappedGpuHandle>),
258    /// A copy of the value and the descriptor of the caller's handle.
259    /// Upstream the render thread reads the caller's object; here that
260    /// object stays on its thread, and the backend is handed a
261    /// [`PlatformHandle`] with the same contents.
262    Copied(PlatformHandle),
263}
264
265impl ImportHandle {
266    fn new(wrapped: Option<Rc<dyn IExternalObjectsWrappedGpuHandle>>, handle: &dyn IPlatformHandle) -> ImportHandle {
267        match wrapped {
268            Some(wrapped) => ImportHandle::Wrapped(wrapped),
269            None => ImportHandle::Copied(PlatformHandle::new(handle.handle(), handle.handle_descriptor())),
270        }
271    }
272
273    fn platform_handle(&self) -> Rc<dyn IPlatformHandle> {
274        match self {
275            ImportHandle::Wrapped(wrapped) => wrapped.clone().as_platform_handle(),
276            ImportHandle::Copied(handle) => Rc::new(handle.clone()),
277        }
278    }
279
280    /// Releases a wrapped handle: once the import has run.
281    fn dispose(self) {
282        if let ImportHandle::Wrapped(wrapped) = self {
283            wrapped.dispose();
284        }
285    }
286}
287
288/// What an image is imported from.
289enum ImageSource {
290    Handle(ImportHandle, PlatformGraphicsExternalImageProperties),
291    /// An image of a context that shares with the one of the compositor.
292    /// The caller keeps a reference and disposes it, and the render thread
293    /// reads it during the import, as upstream: the image is an object the
294    /// two threads share (`Send + Sync`), not one of the lock.
295    Shared(Arc<dyn ICompositionImportableSharedGpuContextImage>),
296}
297
298impl ImageSource {
299    fn import(
300        &self,
301        feature: &dyn IExternalObjectsRenderInterfaceContextFeature,
302    ) -> Result<Rc<dyn IPlatformRenderInterfaceImportedImage>, ServerJobError> {
303        match self {
304            ImageSource::Handle(handle, properties) => Ok(feature.import_image(handle.platform_handle(), properties.clone())),
305            ImageSource::Shared(image) => Ok(feature.import_shared_image(image.clone())),
306        }
307    }
308
309    fn dispose(self) {
310        if let ImageSource::Handle(handle, _) = self {
311            handle.dispose();
312        }
313    }
314}
315
316/// What the server part of every imported object holds: the context and the
317/// feature the object is imported with, and the outcome of the import.
318pub struct ServerGpuImportedObjectBase {
319    context: Rc<dyn IPlatformRenderInterfaceContext>,
320    feature: Rc<dyn IExternalObjectsRenderInterfaceContextFeature>,
321    /// The outcome of the import job, `None` until it has run: what the
322    /// status of `ImportCompleted` tells the jobs upstream.
323    import_result: RefCell<Option<Result<(), ServerJobError>>>,
324}
325
326impl ServerGpuImportedObjectBase {
327    pub fn context(&self) -> &Rc<dyn IPlatformRenderInterfaceContext> {
328        &self.context
329    }
330
331    pub fn feature(&self) -> &Rc<dyn IExternalObjectsRenderInterfaceContextFeature> {
332        &self.feature
333    }
334
335    /// The outcome of the import, `None` until the import job has run.
336    pub fn import_result(&self) -> Option<Result<(), ServerJobError>> {
337        self.import_result.borrow().clone()
338    }
339
340    /// Whether the context of the server compositor is the one the object
341    /// was imported with.
342    fn is_current_context(&self, server: &ServerCompositor) -> bool {
343        same_context(&server.render_interface().value(), &self.context)
344    }
345}
346
347/// The server part of an imported object: what the jobs of the render
348/// thread import, use and dispose.
349pub trait IServerGpuImportedObject: 'static {
350    fn base(&self) -> &ServerGpuImportedObjectBase;
351
352    /// Imports the object (`Import`).
353    fn import(&self, server: &ServerCompositor) -> Result<(), ServerJobError>;
354
355    /// Releases the imported object (`Dispose`).
356    fn dispose(&self);
357}
358
359/// What upstream's `CompositionGpuImportedObjectBase` holds on the side of
360/// the caller: the compositor, the task of the import and the handle of the
361/// server part.
362pub struct CompositionGpuImportedObjectBase<T: IServerGpuImportedObject> {
363    compositor: Rc<Compositor>,
364    server: Arc<LockBound<T>>,
365    import_completed: ServerJobTask<()>,
366}
367
368impl<T: IServerGpuImportedObject> CompositionGpuImportedObjectBase<T> {
369    /// Schedules the import on the render thread.
370    fn new(compositor: &Rc<Compositor>, server: Arc<LockBound<T>>) -> Self {
371        let object = server.clone();
372        let import_completed = compositor.invoke_server_job_async(
373            move |compositor| {
374                let object = object.get(compositor);
375                let result = object.import(compositor);
376                *object.base().import_result.borrow_mut() = Some(result.clone());
377                result
378            },
379            false,
380        );
381        Self { compositor: compositor.clone(), server, import_completed }
382    }
383
384    pub fn compositor(&self) -> &Rc<Compositor> {
385        &self.compositor
386    }
387
388    /// The server part: for a job of the render thread to capture.
389    pub(crate) fn server(&self) -> Arc<LockBound<T>> {
390        self.server.clone()
391    }
392
393    pub fn import_completed(&self) -> ServerJobTask<()> {
394        self.import_completed.clone()
395    }
396
397    /// Upstream reads the context from the UI thread as it is; here this
398    /// thread enters the compositor lock for it.
399    pub fn is_lost(&self) -> bool {
400        self.compositor.with_server(|server| self.server.get(server).base().context.is_lost())
401    }
402
403    fn dispose_async(&self) -> ServerJobTask<()> {
404        // The job holds the server part, as upstream it captures `this`: the
405        // imported object is released even when the caller drops its object
406        // before the job runs.
407        let object = self.server.clone();
408        self.compositor.invoke_server_job_async(
409            move |compositor| {
410                let object = object.get(compositor);
411                if matches!(object.base().import_result(), Some(Ok(()))) {
412                    object.dispose();
413                }
414                Ok(())
415            },
416            false,
417        )
418    }
419}
420
421/// The server part of an imported GPU image.
422pub struct ServerImportedGpuImage {
423    base: ServerGpuImportedObjectBase,
424    source: RefCell<Option<ImageSource>>,
425    image: RefCell<Option<Rc<dyn IPlatformRenderInterfaceImportedImage>>>,
426}
427
428impl ServerImportedGpuImage {
429    fn new(state: &InteropState, source: ImageSource) -> ServerImportedGpuImage {
430        ServerImportedGpuImage {
431            base: state.imported_object_base(),
432            source: RefCell::new(Some(source)),
433            image: RefCell::new(None),
434        }
435    }
436
437    /// The imported image. Panics once the image has been disposed
438    /// (`ObjectDisposedException`).
439    pub fn image(&self) -> Rc<dyn IPlatformRenderInterfaceImportedImage> {
440        match self.image.borrow().clone() {
441            Some(image) => image,
442            None => panic!("Cannot access a disposed object.\nObject name: 'CompositionImportedGpuImage'."),
443        }
444    }
445
446    pub fn context(&self) -> &Rc<dyn IPlatformRenderInterfaceContext> {
447        &self.base.context
448    }
449
450    pub fn is_usable(&self, server: &ServerCompositor) -> bool {
451        self.image.borrow().is_some() && self.base.is_current_context(server)
452    }
453}
454
455impl IServerGpuImportedObject for ServerImportedGpuImage {
456    fn base(&self) -> &ServerGpuImportedObjectBase {
457        &self.base
458    }
459
460    fn import(&self, server: &ServerCompositor) -> Result<(), ServerJobError> {
461        let source = self.source.borrow_mut().take();
462        let current = server.render_interface().ensure_current();
463        let result = (|| {
464            // The original context was lost and the new one might have different capabilities
465            if !self.base.is_current_context(server) {
466                return Err(Arc::new(PlatformGraphicsContextLostException) as ServerJobError);
467            }
468            if let Some(source) = &source {
469                *self.image.borrow_mut() = Some(source.import(&*self.base.feature)?);
470            }
471            Ok(())
472        })();
473        current.dispose();
474        // A wrapped handle is released once the import has run.
475        if let Some(source) = source {
476            source.dispose();
477        }
478        result
479    }
480
481    fn dispose(&self) {
482        if let Some(image) = self.image.borrow_mut().take() {
483            image.dispose();
484        }
485    }
486}
487
488/// A GPU image imported into the compositor.
489pub struct CompositionImportedGpuImage {
490    base: CompositionGpuImportedObjectBase<ServerImportedGpuImage>,
491}
492
493impl CompositionImportedGpuImage {
494    fn new(compositor: &Rc<Compositor>, server: Arc<LockBound<ServerImportedGpuImage>>) -> Rc<CompositionImportedGpuImage> {
495        Rc::new(CompositionImportedGpuImage { base: CompositionGpuImportedObjectBase::new(compositor, server) })
496    }
497
498    pub fn base(&self) -> &CompositionGpuImportedObjectBase<ServerImportedGpuImage> {
499        &self.base
500    }
501
502    /// The server part of the image: for a job of the render thread.
503    pub(crate) fn server(&self) -> Arc<LockBound<ServerImportedGpuImage>> {
504        self.base.server()
505    }
506}
507
508impl ICompositionGpuImportedObject for CompositionImportedGpuImage {
509    fn import_completed(&self) -> ServerJobTask<()> {
510        self.base.import_completed()
511    }
512
513    fn is_lost(&self) -> bool {
514        self.base.is_lost()
515    }
516
517    fn dispose_async(&self) -> ServerJobTask<()> {
518        self.base.dispose_async()
519    }
520}
521
522impl ICompositionImportedGpuImage for CompositionImportedGpuImage {
523    fn as_imported_gpu_image(&self) -> Option<&CompositionImportedGpuImage> {
524        Some(self)
525    }
526}
527
528/// The server part of an imported GPU semaphore.
529pub struct ServerImportedGpuSemaphore {
530    base: ServerGpuImportedObjectBase,
531    handle: RefCell<Option<ImportHandle>>,
532    semaphore: RefCell<Option<Rc<dyn IPlatformRenderInterfaceImportedSemaphore>>>,
533}
534
535impl ServerImportedGpuSemaphore {
536    fn new(state: &InteropState, handle: ImportHandle) -> ServerImportedGpuSemaphore {
537        ServerImportedGpuSemaphore {
538            base: state.imported_object_base(),
539            handle: RefCell::new(Some(handle)),
540            semaphore: RefCell::new(None),
541        }
542    }
543
544    /// The imported semaphore. Panics once the semaphore has been disposed
545    /// (`ObjectDisposedException`).
546    pub fn semaphore(&self) -> Rc<dyn IPlatformRenderInterfaceImportedSemaphore> {
547        match self.semaphore.borrow().clone() {
548            Some(semaphore) => semaphore,
549            None => panic!("Cannot access a disposed object.\nObject name: 'CompositionImportedGpuSemaphore'."),
550        }
551    }
552
553    pub fn is_usable(&self, server: &ServerCompositor) -> bool {
554        self.semaphore.borrow().is_some() && self.base.is_current_context(server)
555    }
556}
557
558impl IServerGpuImportedObject for ServerImportedGpuSemaphore {
559    fn base(&self) -> &ServerGpuImportedObjectBase {
560        &self.base
561    }
562
563    fn import(&self, _server: &ServerCompositor) -> Result<(), ServerJobError> {
564        let handle = self.handle.borrow_mut().take();
565        if let Some(handle) = handle {
566            *self.semaphore.borrow_mut() = Some(self.base.feature.import_semaphore(handle.platform_handle()));
567            // A wrapped handle is released once the import has run.
568            handle.dispose();
569        }
570        Ok(())
571    }
572
573    fn dispose(&self) {
574        if let Some(semaphore) = self.semaphore.borrow_mut().take() {
575            semaphore.dispose();
576        }
577    }
578}
579
580/// A GPU semaphore imported into the compositor.
581pub struct CompositionImportedGpuSemaphore {
582    base: CompositionGpuImportedObjectBase<ServerImportedGpuSemaphore>,
583}
584
585impl CompositionImportedGpuSemaphore {
586    fn new(
587        compositor: &Rc<Compositor>,
588        server: Arc<LockBound<ServerImportedGpuSemaphore>>,
589    ) -> Rc<CompositionImportedGpuSemaphore> {
590        Rc::new(CompositionImportedGpuSemaphore { base: CompositionGpuImportedObjectBase::new(compositor, server) })
591    }
592
593    pub fn base(&self) -> &CompositionGpuImportedObjectBase<ServerImportedGpuSemaphore> {
594        &self.base
595    }
596
597    /// The server part of the semaphore: for a job of the render thread.
598    pub(crate) fn server(&self) -> Arc<LockBound<ServerImportedGpuSemaphore>> {
599        self.base.server()
600    }
601}
602
603impl ICompositionGpuImportedObject for CompositionImportedGpuSemaphore {
604    fn import_completed(&self) -> ServerJobTask<()> {
605        self.base.import_completed()
606    }
607
608    fn is_lost(&self) -> bool {
609        self.base.is_lost()
610    }
611
612    fn dispose_async(&self) -> ServerJobTask<()> {
613        self.base.dispose_async()
614    }
615}
616
617impl ICompositionImportedGpuSemaphore for CompositionImportedGpuSemaphore {
618    fn as_imported_gpu_semaphore(&self) -> Option<&CompositionImportedGpuSemaphore> {
619        Some(self)
620    }
621}