Skip to main content

maplibre_native/
render.rs

1use std::cell::{Cell, RefCell};
2use std::fmt;
3use std::marker::PhantomData;
4use std::mem;
5use std::ptr::NonNull;
6use std::rc::Rc;
7
8pub use maplibre_core::{PremultipliedRgba8Image, TextureImageInfo};
9use maplibre_native_core as maplibre_core;
10use maplibre_native_sys as sys;
11
12use crate::handle::{ThreadAffineNativeHandle, closed_handle_error, out_handle};
13use crate::map::{MapHandle, MapState};
14#[cfg(test)]
15use crate::{Feature, JsonValue};
16use crate::{HandleOperationError, Result};
17
18/// Borrowed opaque native address used for backend interop handles.
19///
20/// The value does not own, retain, dereference, or validate the pointed-to
21/// object. Passing it to MapLibre Native transfers no ownership and grants the
22/// Rust binding no memory access.
23#[derive(Clone, Copy, PartialEq, Eq, Hash)]
24pub struct NativePointer {
25    address: usize,
26    _thread_affine: PhantomData<Rc<()>>,
27}
28
29impl NativePointer {
30    /// Null backend handle value.
31    pub const NULL: Self = Self {
32        address: 0,
33        _thread_affine: PhantomData,
34    };
35
36    /// Creates an opaque borrowed pointer value from a native address.
37    ///
38    /// # Safety
39    ///
40    /// The caller must ensure the address has the correct backend-native type
41    /// for every API it is passed to, and that the native object stays valid for
42    /// the complete borrow required by that API. This wrapper does not validate
43    /// provenance, alignment, lifetime, thread ownership, or backend type.
44    pub unsafe fn from_address(address: usize) -> Self {
45        if address == 0 {
46            Self::NULL
47        } else {
48            Self {
49                address,
50                _thread_affine: PhantomData,
51            }
52        }
53    }
54
55    /// Creates an opaque borrowed pointer value from a raw pointer.
56    ///
57    /// # Safety
58    ///
59    /// The pointer must satisfy the same requirements as
60    /// [`NativePointer::from_address`].
61    pub unsafe fn from_ptr<T>(ptr: *mut T) -> Self {
62        // SAFETY: The caller upholds the native pointer lifetime and type
63        // requirements documented above; this conversion only stores the address.
64        unsafe { Self::from_address(ptr as usize) }
65    }
66
67    /// Returns this opaque value as an integer address.
68    pub fn address(self) -> usize {
69        self.address
70    }
71
72    /// Returns whether this value is null.
73    pub fn is_null(self) -> bool {
74        self.address == 0
75    }
76
77    /// Reconstructs a raw pointer for a backend interop call.
78    ///
79    /// # Safety
80    ///
81    /// The caller must choose the correct pointer type and uphold the lifetime,
82    /// thread-affinity, synchronization, and aliasing requirements of the
83    /// backend API that will receive the pointer.
84    pub unsafe fn as_ptr<T>(self) -> *mut T {
85        self.address as *mut T
86    }
87
88    fn as_void_ptr(self) -> *mut std::ffi::c_void {
89        self.address as *mut std::ffi::c_void
90    }
91}
92
93impl fmt::Debug for NativePointer {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "NativePointer(0x{:x})", self.address)
96    }
97}
98
99/// Borrowed opaque native address whose validity is tied to an active texture frame.
100///
101/// The value does not own, retain, dereference, or validate the pointed-to
102/// object. It exists so backend pointers returned from acquired frame handles
103/// carry the frame borrow in their Rust type instead of escaping as plain
104/// [`NativePointer`] values.
105#[derive(Clone, Copy, PartialEq, Eq, Hash)]
106pub struct FrameNativePointer<'frame> {
107    address: usize,
108    _frame: PhantomData<&'frame ()>,
109    _thread_affine: PhantomData<Rc<()>>,
110}
111
112impl<'frame> FrameNativePointer<'frame> {
113    unsafe fn from_ptr<T>(ptr: *mut T) -> Self {
114        Self {
115            address: ptr as usize,
116            _frame: PhantomData,
117            _thread_affine: PhantomData,
118        }
119    }
120
121    /// Returns this opaque value as an integer address.
122    ///
123    /// # Safety
124    ///
125    /// The returned integer no longer carries this value's frame lifetime. The
126    /// caller must use it only while the borrowed frame remains open and must
127    /// satisfy the backend API's type, synchronization, and thread-affinity
128    /// requirements.
129    pub unsafe fn address(self) -> usize {
130        self.address
131    }
132
133    /// Returns whether this value is null.
134    pub fn is_null(self) -> bool {
135        self.address == 0
136    }
137
138    /// Reconstructs a raw pointer for a backend interop call.
139    ///
140    /// # Safety
141    ///
142    /// The caller must choose the correct pointer type and uphold the lifetime,
143    /// thread-affinity, synchronization, and aliasing requirements of the
144    /// backend API that will receive the pointer.
145    pub unsafe fn as_ptr<T>(self) -> *mut T {
146        self.address as *mut T
147    }
148}
149
150impl fmt::Debug for FrameNativePointer<'_> {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        write!(f, "FrameNativePointer(0x{:x})", self.address)
153    }
154}
155
156/// Borrowed OpenGL texture object name tied to an active texture frame.
157#[derive(Clone, Copy, PartialEq, Eq, Hash)]
158pub struct FrameOpenGLTextureName<'frame> {
159    name: u32,
160    _frame: PhantomData<&'frame ()>,
161    _thread_affine: PhantomData<Rc<()>>,
162}
163
164impl<'frame> FrameOpenGLTextureName<'frame> {
165    fn new(name: u32) -> Self {
166        Self {
167            name,
168            _frame: PhantomData,
169            _thread_affine: PhantomData,
170        }
171    }
172
173    /// Returns whether this OpenGL texture object name is zero.
174    pub fn is_zero(self) -> bool {
175        self.name == 0
176    }
177
178    /// Returns the OpenGL texture object name.
179    ///
180    /// # Safety
181    ///
182    /// The returned integer no longer carries this value's frame lifetime. Use
183    /// it only while the borrowed frame remains open and satisfy OpenGL
184    /// synchronization and context-share-group requirements.
185    pub unsafe fn value(self) -> u32 {
186        self.name
187    }
188}
189
190impl fmt::Debug for FrameOpenGLTextureName<'_> {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        write!(f, "FrameOpenGLTextureName({})", self.name)
193    }
194}
195
196mod query;
197pub use query::{
198    FeatureExtensionResult, FeatureStateSelector, QueriedFeature, RenderedFeatureQueryOptions,
199    RenderedQueryGeometry, SourceFeatureQueryOptions,
200};
201#[derive(Debug, Clone, PartialEq)]
202#[non_exhaustive]
203pub struct RenderTargetExtent {
204    pub width: u32,
205    pub height: u32,
206    pub scale_factor: f64,
207}
208
209impl RenderTargetExtent {
210    pub fn new(width: u32, height: u32, scale_factor: f64) -> Self {
211        Self {
212            width,
213            height,
214            scale_factor,
215        }
216    }
217
218    pub(crate) fn to_core(&self) -> maplibre_core::render::RenderTargetExtentFields {
219        maplibre_core::render::RenderTargetExtentFields {
220            width: self.width,
221            height: self.height,
222            scale_factor: self.scale_factor,
223        }
224    }
225}
226
227impl Default for RenderTargetExtent {
228    fn default() -> Self {
229        Self::new(256, 256, 1.0)
230    }
231}
232
233#[derive(Debug, Clone, PartialEq)]
234#[non_exhaustive]
235pub struct MetalContextDescriptor {
236    pub device: NativePointer,
237}
238
239impl MetalContextDescriptor {
240    pub fn new(device: NativePointer) -> Self {
241        Self { device }
242    }
243
244    pub(crate) fn to_core(&self) -> maplibre_core::render::MetalContextDescriptorFields {
245        maplibre_core::render::MetalContextDescriptorFields {
246            device: self.device.as_void_ptr(),
247        }
248    }
249}
250
251impl Default for MetalContextDescriptor {
252    fn default() -> Self {
253        Self::new(NativePointer::NULL)
254    }
255}
256
257#[derive(Debug, Clone, PartialEq)]
258#[non_exhaustive]
259pub struct VulkanContextDescriptor {
260    pub instance: NativePointer,
261    pub physical_device: NativePointer,
262    pub device: NativePointer,
263    pub graphics_queue: NativePointer,
264    pub graphics_queue_family_index: u32,
265    pub get_instance_proc_addr: NativePointer,
266    pub get_device_proc_addr: NativePointer,
267}
268
269impl VulkanContextDescriptor {
270    #[allow(clippy::too_many_arguments)]
271    pub fn new(
272        instance: NativePointer,
273        physical_device: NativePointer,
274        device: NativePointer,
275        graphics_queue: NativePointer,
276        graphics_queue_family_index: u32,
277    ) -> Self {
278        Self {
279            instance,
280            physical_device,
281            device,
282            graphics_queue,
283            graphics_queue_family_index,
284            get_instance_proc_addr: NativePointer::NULL,
285            get_device_proc_addr: NativePointer::NULL,
286        }
287    }
288
289    pub(crate) fn to_core(&self) -> maplibre_core::render::VulkanContextDescriptorFields {
290        maplibre_core::render::VulkanContextDescriptorFields {
291            instance: self.instance.as_void_ptr(),
292            physical_device: self.physical_device.as_void_ptr(),
293            device: self.device.as_void_ptr(),
294            graphics_queue: self.graphics_queue.as_void_ptr(),
295            graphics_queue_family_index: self.graphics_queue_family_index,
296            get_instance_proc_addr: self.get_instance_proc_addr.as_void_ptr(),
297            get_device_proc_addr: self.get_device_proc_addr.as_void_ptr(),
298        }
299    }
300}
301
302impl Default for VulkanContextDescriptor {
303    fn default() -> Self {
304        Self::new(
305            NativePointer::NULL,
306            NativePointer::NULL,
307            NativePointer::NULL,
308            NativePointer::NULL,
309            0,
310        )
311    }
312}
313
314#[derive(Debug, Clone, PartialEq)]
315#[non_exhaustive]
316pub struct WglContextDescriptor {
317    pub device_context: NativePointer,
318    pub share_context: NativePointer,
319    pub get_proc_address: NativePointer,
320}
321
322impl WglContextDescriptor {
323    pub fn new(device_context: NativePointer, share_context: NativePointer) -> Self {
324        Self {
325            device_context,
326            share_context,
327            get_proc_address: NativePointer::NULL,
328        }
329    }
330
331    pub(crate) fn to_core(&self) -> maplibre_core::render::WglContextDescriptorFields {
332        maplibre_core::render::WglContextDescriptorFields {
333            device_context: self.device_context.as_void_ptr(),
334            share_context: self.share_context.as_void_ptr(),
335            get_proc_address: self.get_proc_address.as_void_ptr(),
336        }
337    }
338}
339
340impl Default for WglContextDescriptor {
341    fn default() -> Self {
342        Self::new(NativePointer::NULL, NativePointer::NULL)
343    }
344}
345
346#[derive(Debug, Clone, PartialEq)]
347#[non_exhaustive]
348pub struct EglContextDescriptor {
349    pub display: NativePointer,
350    pub config: NativePointer,
351    pub share_context: NativePointer,
352    pub get_proc_address: NativePointer,
353}
354
355impl EglContextDescriptor {
356    pub fn new(
357        display: NativePointer,
358        config: NativePointer,
359        share_context: NativePointer,
360    ) -> Self {
361        Self {
362            display,
363            config,
364            share_context,
365            get_proc_address: NativePointer::NULL,
366        }
367    }
368
369    pub(crate) fn to_core(&self) -> maplibre_core::render::EglContextDescriptorFields {
370        maplibre_core::render::EglContextDescriptorFields {
371            display: self.display.as_void_ptr(),
372            config: self.config.as_void_ptr(),
373            share_context: self.share_context.as_void_ptr(),
374            get_proc_address: self.get_proc_address.as_void_ptr(),
375        }
376    }
377}
378
379impl Default for EglContextDescriptor {
380    fn default() -> Self {
381        Self::new(
382            NativePointer::NULL,
383            NativePointer::NULL,
384            NativePointer::NULL,
385        )
386    }
387}
388
389#[derive(Debug, Clone, PartialEq)]
390#[non_exhaustive]
391pub enum OpenGLContextDescriptor {
392    Wgl(WglContextDescriptor),
393    Egl(EglContextDescriptor),
394}
395
396impl OpenGLContextDescriptor {
397    pub(crate) fn to_core(&self) -> maplibre_core::render::OpenGLContextDescriptorFields {
398        match self {
399            Self::Wgl(descriptor) => {
400                maplibre_core::render::OpenGLContextDescriptorFields::Wgl(descriptor.to_core())
401            }
402            Self::Egl(descriptor) => {
403                maplibre_core::render::OpenGLContextDescriptorFields::Egl(descriptor.to_core())
404            }
405        }
406    }
407}
408
409#[derive(Debug, Clone, PartialEq)]
410#[non_exhaustive]
411pub struct MetalSurfaceDescriptor {
412    pub extent: RenderTargetExtent,
413    pub context: MetalContextDescriptor,
414    pub layer: NativePointer,
415}
416
417impl MetalSurfaceDescriptor {
418    pub fn new(
419        extent: RenderTargetExtent,
420        context: MetalContextDescriptor,
421        layer: NativePointer,
422    ) -> Self {
423        Self {
424            extent,
425            context,
426            layer,
427        }
428    }
429
430    pub(crate) fn to_native(&self) -> sys::mln_metal_surface_descriptor {
431        maplibre_core::render::metal_surface_descriptor_to_native(
432            maplibre_core::render::MetalSurfaceDescriptorFields {
433                extent: self.extent.to_core(),
434                context: self.context.to_core(),
435                layer: self.layer.as_void_ptr(),
436            },
437        )
438    }
439}
440
441#[derive(Debug, Clone, PartialEq)]
442#[non_exhaustive]
443pub struct VulkanSurfaceDescriptor {
444    pub extent: RenderTargetExtent,
445    pub context: VulkanContextDescriptor,
446    pub surface: NativePointer,
447}
448
449impl VulkanSurfaceDescriptor {
450    pub fn new(
451        extent: RenderTargetExtent,
452        context: VulkanContextDescriptor,
453        surface: NativePointer,
454    ) -> Self {
455        Self {
456            extent,
457            context,
458            surface,
459        }
460    }
461
462    pub(crate) fn to_native(&self) -> sys::mln_vulkan_surface_descriptor {
463        maplibre_core::render::vulkan_surface_descriptor_to_native(
464            maplibre_core::render::VulkanSurfaceDescriptorFields {
465                extent: self.extent.to_core(),
466                context: self.context.to_core(),
467                surface: self.surface.as_void_ptr(),
468            },
469        )
470    }
471}
472
473#[derive(Debug, Clone, PartialEq)]
474#[non_exhaustive]
475pub struct OpenGLSurfaceDescriptor {
476    pub extent: RenderTargetExtent,
477    pub context: OpenGLContextDescriptor,
478    pub surface: NativePointer,
479}
480
481impl OpenGLSurfaceDescriptor {
482    pub fn new(
483        extent: RenderTargetExtent,
484        context: OpenGLContextDescriptor,
485        surface: NativePointer,
486    ) -> Self {
487        Self {
488            extent,
489            context,
490            surface,
491        }
492    }
493
494    pub(crate) fn to_native(&self) -> sys::mln_opengl_surface_descriptor {
495        maplibre_core::render::opengl_surface_descriptor_to_native(
496            maplibre_core::render::OpenGLSurfaceDescriptorFields {
497                extent: self.extent.to_core(),
498                context: self.context.to_core(),
499                surface: self.surface.as_void_ptr(),
500            },
501        )
502    }
503}
504
505#[derive(Debug, Clone, PartialEq)]
506#[non_exhaustive]
507pub struct MetalOwnedTextureDescriptor {
508    pub extent: RenderTargetExtent,
509    pub context: MetalContextDescriptor,
510}
511
512impl MetalOwnedTextureDescriptor {
513    pub fn new(extent: RenderTargetExtent, context: MetalContextDescriptor) -> Self {
514        Self { extent, context }
515    }
516
517    pub(crate) fn to_native(&self) -> sys::mln_metal_owned_texture_descriptor {
518        maplibre_core::render::metal_owned_texture_descriptor_to_native(
519            maplibre_core::render::MetalOwnedTextureDescriptorFields {
520                extent: self.extent.to_core(),
521                context: self.context.to_core(),
522            },
523        )
524    }
525}
526
527#[derive(Debug, Clone, PartialEq)]
528#[non_exhaustive]
529pub struct MetalBorrowedTextureDescriptor {
530    pub extent: RenderTargetExtent,
531    pub texture: NativePointer,
532}
533
534impl MetalBorrowedTextureDescriptor {
535    pub fn new(extent: RenderTargetExtent, texture: NativePointer) -> Self {
536        Self { extent, texture }
537    }
538
539    pub(crate) fn to_native(&self) -> sys::mln_metal_borrowed_texture_descriptor {
540        maplibre_core::render::metal_borrowed_texture_descriptor_to_native(
541            maplibre_core::render::MetalBorrowedTextureDescriptorFields {
542                extent: self.extent.to_core(),
543                texture: self.texture.as_void_ptr(),
544            },
545        )
546    }
547}
548
549#[derive(Debug, Clone, PartialEq)]
550#[non_exhaustive]
551pub struct VulkanOwnedTextureDescriptor {
552    pub extent: RenderTargetExtent,
553    pub context: VulkanContextDescriptor,
554}
555
556impl VulkanOwnedTextureDescriptor {
557    pub fn new(extent: RenderTargetExtent, context: VulkanContextDescriptor) -> Self {
558        Self { extent, context }
559    }
560
561    pub(crate) fn to_native(&self) -> sys::mln_vulkan_owned_texture_descriptor {
562        maplibre_core::render::vulkan_owned_texture_descriptor_to_native(
563            maplibre_core::render::VulkanOwnedTextureDescriptorFields {
564                extent: self.extent.to_core(),
565                context: self.context.to_core(),
566            },
567        )
568    }
569}
570
571#[derive(Debug, Clone, PartialEq)]
572#[non_exhaustive]
573pub struct VulkanBorrowedTextureDescriptor {
574    pub extent: RenderTargetExtent,
575    pub context: VulkanContextDescriptor,
576    pub image: NativePointer,
577    pub image_view: NativePointer,
578    pub format: u32,
579    pub initial_layout: u32,
580    pub final_layout: u32,
581}
582
583impl VulkanBorrowedTextureDescriptor {
584    #[allow(clippy::too_many_arguments)]
585    pub fn new(
586        extent: RenderTargetExtent,
587        context: VulkanContextDescriptor,
588        image: NativePointer,
589        image_view: NativePointer,
590        format: u32,
591        initial_layout: u32,
592        final_layout: u32,
593    ) -> Self {
594        Self {
595            extent,
596            context,
597            image,
598            image_view,
599            format,
600            initial_layout,
601            final_layout,
602        }
603    }
604
605    pub(crate) fn to_native(&self) -> sys::mln_vulkan_borrowed_texture_descriptor {
606        maplibre_core::render::vulkan_borrowed_texture_descriptor_to_native(
607            maplibre_core::render::VulkanBorrowedTextureDescriptorFields {
608                extent: self.extent.to_core(),
609                context: self.context.to_core(),
610                image: self.image.as_void_ptr(),
611                image_view: self.image_view.as_void_ptr(),
612                format: self.format,
613                initial_layout: self.initial_layout,
614                final_layout: self.final_layout,
615            },
616        )
617    }
618}
619
620#[derive(Debug, Clone, PartialEq)]
621#[non_exhaustive]
622pub struct OpenGLOwnedTextureDescriptor {
623    pub extent: RenderTargetExtent,
624    pub context: OpenGLContextDescriptor,
625}
626
627impl OpenGLOwnedTextureDescriptor {
628    pub fn new(extent: RenderTargetExtent, context: OpenGLContextDescriptor) -> Self {
629        Self { extent, context }
630    }
631
632    pub(crate) fn to_native(&self) -> sys::mln_opengl_owned_texture_descriptor {
633        maplibre_core::render::opengl_owned_texture_descriptor_to_native(
634            maplibre_core::render::OpenGLOwnedTextureDescriptorFields {
635                extent: self.extent.to_core(),
636                context: self.context.to_core(),
637            },
638        )
639    }
640}
641
642#[derive(Debug, Clone, PartialEq)]
643#[non_exhaustive]
644pub struct OpenGLBorrowedTextureDescriptor {
645    pub extent: RenderTargetExtent,
646    pub context: OpenGLContextDescriptor,
647    pub texture: u32,
648    pub target: u32,
649}
650
651impl OpenGLBorrowedTextureDescriptor {
652    pub fn new(
653        extent: RenderTargetExtent,
654        context: OpenGLContextDescriptor,
655        texture: u32,
656        target: u32,
657    ) -> Self {
658        Self {
659            extent,
660            context,
661            texture,
662            target,
663        }
664    }
665
666    pub(crate) fn to_native(&self) -> sys::mln_opengl_borrowed_texture_descriptor {
667        maplibre_core::render::opengl_borrowed_texture_descriptor_to_native(
668            maplibre_core::render::OpenGLBorrowedTextureDescriptorFields {
669                extent: self.extent.to_core(),
670                context: self.context.to_core(),
671                texture: self.texture,
672                target: self.target,
673            },
674        )
675    }
676}
677
678#[derive(Debug)]
679struct RenderSessionState {
680    handle: ThreadAffineNativeHandle<sys::mln_render_session>,
681    map: RefCell<Option<Rc<MapState>>>,
682    detached: Cell<bool>,
683    frame_acquired: Cell<bool>,
684}
685
686impl RenderSessionState {
687    fn new(ptr: NonNull<sys::mln_render_session>, map: Rc<MapState>) -> Self {
688        // SAFETY: ptr came from a successful render-session attach call and is
689        // paired with the matching render-session destroy function.
690        let handle = unsafe {
691            ThreadAffineNativeHandle::from_raw(
692                ptr,
693                sys::mln_render_session_destroy,
694                "mln_render_session",
695            )
696        };
697        Self {
698            handle,
699            map: RefCell::new(Some(map)),
700            detached: Cell::new(false),
701            frame_acquired: Cell::new(false),
702        }
703    }
704
705    fn ensure_no_frame_acquired(&self) -> Result<()> {
706        if self.frame_acquired.get() {
707            Err(frame_acquired_error())
708        } else {
709            Ok(())
710        }
711    }
712
713    fn as_ptr(&self) -> Result<*mut sys::mln_render_session> {
714        let ptr = self.handle.as_ptr();
715        if ptr.is_null() {
716            Err(closed_handle_error("RenderSessionHandle"))
717        } else {
718            Ok(ptr)
719        }
720    }
721
722    fn close(&self) -> Result<()> {
723        self.handle.close()?;
724        self.map.borrow_mut().take();
725        Ok(())
726    }
727}
728
729/// Owner-thread render session handle bound to a retained map.
730pub struct RenderSessionHandle {
731    inner: Rc<RenderSessionState>,
732}
733
734impl fmt::Debug for RenderSessionHandle {
735    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736        f.debug_struct("RenderSessionHandle")
737            .field("closed", &self.inner.handle.is_closed())
738            .field("detached", &self.inner.detached.get())
739            .finish()
740    }
741}
742
743/// Render session after backend resources have been detached.
744pub struct DetachedRenderSessionHandle {
745    inner: Rc<RenderSessionState>,
746}
747
748impl fmt::Debug for DetachedRenderSessionHandle {
749    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
750        f.debug_struct("DetachedRenderSessionHandle")
751            .field("closed", &self.inner.handle.is_closed())
752            .finish()
753    }
754}
755
756impl DetachedRenderSessionHandle {
757    /// Explicitly destroys the detached render session.
758    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
759        if let Err(error) = self.inner.close() {
760            return Err(HandleOperationError::new(error, self));
761        }
762        Ok(())
763    }
764}
765
766fn frame_acquired_error() -> crate::Error {
767    crate::Error::new(
768        crate::ErrorKind::InvalidState,
769        None,
770        "render session has an acquired texture frame",
771    )
772}
773
774/// Copied metadata for an acquired Metal session-owned texture frame.
775///
776/// Backend pointers are exposed by [`MetalOwnedTextureFrameHandle`] so their
777/// lifetime stays tied to the open frame handle.
778#[derive(Debug, Clone, Copy, PartialEq)]
779#[non_exhaustive]
780pub struct MetalOwnedTextureFrame {
781    pub generation: u64,
782    pub width: u32,
783    pub height: u32,
784    pub scale_factor: f64,
785    pub frame_id: u64,
786    pub pixel_format: u64,
787}
788
789impl MetalOwnedTextureFrame {
790    fn from_native(raw: &sys::mln_metal_owned_texture_frame) -> Self {
791        Self {
792            generation: raw.generation,
793            width: raw.width,
794            height: raw.height,
795            scale_factor: raw.scale_factor,
796            frame_id: raw.frame_id,
797            pixel_format: raw.pixel_format,
798        }
799    }
800}
801
802/// Copied metadata for an acquired Vulkan session-owned texture frame.
803///
804/// Backend pointers are exposed by [`VulkanOwnedTextureFrameHandle`] so their
805/// lifetime stays tied to the open frame handle.
806#[derive(Debug, Clone, Copy, PartialEq)]
807#[non_exhaustive]
808pub struct VulkanOwnedTextureFrame {
809    pub generation: u64,
810    pub width: u32,
811    pub height: u32,
812    pub scale_factor: f64,
813    pub frame_id: u64,
814    pub format: u32,
815    pub layout: u32,
816}
817
818impl VulkanOwnedTextureFrame {
819    fn from_native(raw: &sys::mln_vulkan_owned_texture_frame) -> Self {
820        Self {
821            generation: raw.generation,
822            width: raw.width,
823            height: raw.height,
824            scale_factor: raw.scale_factor,
825            frame_id: raw.frame_id,
826            format: raw.format,
827            layout: raw.layout,
828        }
829    }
830}
831
832/// Copied metadata for an acquired OpenGL session-owned texture frame.
833///
834/// The texture object name is exposed by [`OpenGLOwnedTextureFrameHandle`] so
835/// its lifetime stays tied to the open frame handle.
836#[derive(Debug, Clone, Copy, PartialEq)]
837#[non_exhaustive]
838pub struct OpenGLOwnedTextureFrame {
839    pub generation: u64,
840    pub width: u32,
841    pub height: u32,
842    pub scale_factor: f64,
843    pub frame_id: u64,
844    pub target: u32,
845    pub internal_format: u32,
846    pub format: u32,
847    pub type_: u32,
848}
849
850impl OpenGLOwnedTextureFrame {
851    fn from_native(raw: &sys::mln_opengl_owned_texture_frame) -> Self {
852        Self {
853            generation: raw.generation,
854            width: raw.width,
855            height: raw.height,
856            scale_factor: raw.scale_factor,
857            frame_id: raw.frame_id,
858            target: raw.target,
859            internal_format: raw.internal_format,
860            format: raw.format,
861            type_: raw.type_,
862        }
863    }
864}
865
866/// RAII guard for an acquired Metal session-owned texture frame.
867///
868/// Releasing the guard ends the borrow of the backend Metal texture and device.
869pub struct MetalOwnedTextureFrameHandle {
870    session: Rc<RenderSessionState>,
871    raw: sys::mln_metal_owned_texture_frame,
872    frame: MetalOwnedTextureFrame,
873    closed: Cell<bool>,
874    _thread_affine: PhantomData<Rc<()>>,
875}
876
877impl fmt::Debug for MetalOwnedTextureFrameHandle {
878    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
879        f.debug_struct("MetalOwnedTextureFrameHandle")
880            .field("closed", &self.closed.get())
881            .field("frame", &self.frame)
882            .finish()
883    }
884}
885
886impl MetalOwnedTextureFrameHandle {
887    /// Returns copied metadata for this acquired frame.
888    pub fn frame(&self) -> Result<&MetalOwnedTextureFrame> {
889        if self.closed.get() {
890            Err(closed_handle_error("MetalOwnedTextureFrameHandle"))
891        } else {
892            Ok(&self.frame)
893        }
894    }
895    /// Returns the borrowed Metal texture pointer for backend interop.
896    ///
897    /// # Safety
898    ///
899    /// The returned pointer is valid only while this frame handle remains open.
900    /// The caller must not store or use it after frame release and must satisfy
901    /// Metal synchronization and thread-affinity requirements.
902    pub unsafe fn texture(&self) -> Result<FrameNativePointer<'_>> {
903        if self.closed.get() {
904            Err(closed_handle_error("MetalOwnedTextureFrameHandle"))
905        } else {
906            // SAFETY: The active native frame owns the validity contract for
907            // this borrowed backend handle until release.
908            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.texture) })
909        }
910    }
911
912    /// Returns the borrowed Metal device pointer for backend interop.
913    ///
914    /// # Safety
915    ///
916    /// The returned pointer has the same lifetime and synchronization
917    /// requirements as [`MetalOwnedTextureFrameHandle::texture`].
918    pub unsafe fn device(&self) -> Result<FrameNativePointer<'_>> {
919        if self.closed.get() {
920            Err(closed_handle_error("MetalOwnedTextureFrameHandle"))
921        } else {
922            // SAFETY: See texture above.
923            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.device) })
924        }
925    }
926
927    /// Explicitly releases this frame.
928    #[allow(clippy::result_large_err)]
929    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
930        self.close_with_release(sys::mln_metal_owned_texture_release_frame)
931    }
932
933    #[allow(clippy::result_large_err)]
934    fn close_with_release(
935        self,
936        release: unsafe extern "C" fn(
937            *mut sys::mln_render_session,
938            *const sys::mln_metal_owned_texture_frame,
939        ) -> sys::mln_status,
940    ) -> std::result::Result<(), HandleOperationError<Self>> {
941        if self.closed.get() {
942            return Ok(());
943        }
944        let session = match self.session.as_ptr() {
945            Ok(session) => session,
946            Err(error) => return Err(HandleOperationError::new(error, self)),
947        };
948        // SAFETY: session is live, and raw is the active frame returned by a
949        // successful acquire for this session until release succeeds.
950        if let Err(error) = maplibre_core::check(unsafe { release(session, &self.raw) }) {
951            return Err(HandleOperationError::new(error, self));
952        }
953        self.closed.set(true);
954        self.session.frame_acquired.set(false);
955        Ok(())
956    }
957}
958
959impl Drop for MetalOwnedTextureFrameHandle {
960    fn drop(&mut self) {
961        if self.closed.get() {
962            return;
963        }
964        if let Ok(session) = self.session.as_ptr() {
965            // SAFETY: Best-effort release of the active frame. Drop cannot
966            // report errors and never panics.
967            let status = unsafe { sys::mln_metal_owned_texture_release_frame(session, &self.raw) };
968            if status == sys::MLN_STATUS_OK {
969                self.closed.set(true);
970                self.session.frame_acquired.set(false);
971            }
972        }
973    }
974}
975
976/// RAII guard for an acquired Vulkan session-owned texture frame.
977///
978/// Releasing the guard ends the borrow of the backend Vulkan image, image view,
979/// and device.
980pub struct VulkanOwnedTextureFrameHandle {
981    session: Rc<RenderSessionState>,
982    raw: sys::mln_vulkan_owned_texture_frame,
983    frame: VulkanOwnedTextureFrame,
984    closed: Cell<bool>,
985    _thread_affine: PhantomData<Rc<()>>,
986}
987
988impl fmt::Debug for VulkanOwnedTextureFrameHandle {
989    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
990        f.debug_struct("VulkanOwnedTextureFrameHandle")
991            .field("closed", &self.closed.get())
992            .field("frame", &self.frame)
993            .finish()
994    }
995}
996
997impl VulkanOwnedTextureFrameHandle {
998    /// Returns copied metadata for this acquired frame.
999    pub fn frame(&self) -> Result<&VulkanOwnedTextureFrame> {
1000        if self.closed.get() {
1001            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1002        } else {
1003            Ok(&self.frame)
1004        }
1005    }
1006    /// Returns the borrowed Vulkan image pointer for backend interop.
1007    ///
1008    /// # Safety
1009    ///
1010    /// The returned pointer is valid only while this frame handle remains open.
1011    /// The caller must not store or use it after frame release and must satisfy
1012    /// Vulkan synchronization and thread-affinity requirements.
1013    pub unsafe fn image(&self) -> Result<FrameNativePointer<'_>> {
1014        if self.closed.get() {
1015            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1016        } else {
1017            // SAFETY: The active native frame owns the validity contract for
1018            // this borrowed backend handle until release.
1019            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.image) })
1020        }
1021    }
1022
1023    /// Returns the borrowed Vulkan image view pointer for backend interop.
1024    ///
1025    /// # Safety
1026    ///
1027    /// The returned pointer has the same lifetime and synchronization
1028    /// requirements as [`VulkanOwnedTextureFrameHandle::image`].
1029    pub unsafe fn image_view(&self) -> Result<FrameNativePointer<'_>> {
1030        if self.closed.get() {
1031            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1032        } else {
1033            // SAFETY: See image above.
1034            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.image_view) })
1035        }
1036    }
1037
1038    /// Returns the borrowed Vulkan device pointer for backend interop.
1039    ///
1040    /// # Safety
1041    ///
1042    /// The returned pointer has the same lifetime and synchronization
1043    /// requirements as [`VulkanOwnedTextureFrameHandle::image`].
1044    pub unsafe fn device(&self) -> Result<FrameNativePointer<'_>> {
1045        if self.closed.get() {
1046            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1047        } else {
1048            // SAFETY: See image above.
1049            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.device) })
1050        }
1051    }
1052
1053    /// Explicitly releases this frame.
1054    #[allow(clippy::result_large_err)]
1055    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1056        if self.closed.get() {
1057            return Ok(());
1058        }
1059        let session = match self.session.as_ptr() {
1060            Ok(session) => session,
1061            Err(error) => return Err(HandleOperationError::new(error, self)),
1062        };
1063        // SAFETY: session is live, and raw is the active frame returned by a
1064        // successful acquire for this session until release succeeds.
1065        if let Err(error) = maplibre_core::check(unsafe {
1066            sys::mln_vulkan_owned_texture_release_frame(session, &self.raw)
1067        }) {
1068            return Err(HandleOperationError::new(error, self));
1069        }
1070        self.closed.set(true);
1071        self.session.frame_acquired.set(false);
1072        Ok(())
1073    }
1074}
1075
1076impl Drop for VulkanOwnedTextureFrameHandle {
1077    fn drop(&mut self) {
1078        if self.closed.get() {
1079            return;
1080        }
1081        if let Ok(session) = self.session.as_ptr() {
1082            // SAFETY: Best-effort release of the active frame. Drop cannot
1083            // report errors and never panics.
1084            let status = unsafe { sys::mln_vulkan_owned_texture_release_frame(session, &self.raw) };
1085            if status == sys::MLN_STATUS_OK {
1086                self.closed.set(true);
1087                self.session.frame_acquired.set(false);
1088            }
1089        }
1090    }
1091}
1092
1093/// RAII guard for an acquired OpenGL session-owned texture frame.
1094///
1095/// Releasing the guard ends the borrow of the backend OpenGL texture object.
1096pub struct OpenGLOwnedTextureFrameHandle {
1097    session: Rc<RenderSessionState>,
1098    raw: sys::mln_opengl_owned_texture_frame,
1099    frame: OpenGLOwnedTextureFrame,
1100    closed: Cell<bool>,
1101    _thread_affine: PhantomData<Rc<()>>,
1102}
1103
1104impl fmt::Debug for OpenGLOwnedTextureFrameHandle {
1105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1106        f.debug_struct("OpenGLOwnedTextureFrameHandle")
1107            .field("closed", &self.closed.get())
1108            .field("frame", &self.frame)
1109            .finish()
1110    }
1111}
1112
1113impl OpenGLOwnedTextureFrameHandle {
1114    /// Returns copied metadata for this acquired frame.
1115    pub fn frame(&self) -> Result<&OpenGLOwnedTextureFrame> {
1116        if self.closed.get() {
1117            Err(closed_handle_error("OpenGLOwnedTextureFrameHandle"))
1118        } else {
1119            Ok(&self.frame)
1120        }
1121    }
1122    /// Returns the borrowed OpenGL texture object name for backend interop.
1123    pub fn texture(&self) -> Result<FrameOpenGLTextureName<'_>> {
1124        if self.closed.get() {
1125            Err(closed_handle_error("OpenGLOwnedTextureFrameHandle"))
1126        } else {
1127            Ok(FrameOpenGLTextureName::new(self.raw.texture))
1128        }
1129    }
1130
1131    /// Explicitly releases this frame.
1132    #[allow(clippy::result_large_err)]
1133    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1134        if self.closed.get() {
1135            return Ok(());
1136        }
1137        let session = match self.session.as_ptr() {
1138            Ok(session) => session,
1139            Err(error) => return Err(HandleOperationError::new(error, self)),
1140        };
1141        // SAFETY: session is live, and raw is the active frame returned by a
1142        // successful acquire for this session until release succeeds.
1143        if let Err(error) = maplibre_core::check(unsafe {
1144            sys::mln_opengl_owned_texture_release_frame(session, &self.raw)
1145        }) {
1146            return Err(HandleOperationError::new(error, self));
1147        }
1148        self.closed.set(true);
1149        self.session.frame_acquired.set(false);
1150        Ok(())
1151    }
1152}
1153
1154impl Drop for OpenGLOwnedTextureFrameHandle {
1155    fn drop(&mut self) {
1156        if self.closed.get() {
1157            return;
1158        }
1159        if let Ok(session) = self.session.as_ptr() {
1160            // SAFETY: Best-effort release of the active frame. Drop cannot
1161            // report errors and never panics.
1162            let status = unsafe { sys::mln_opengl_owned_texture_release_frame(session, &self.raw) };
1163            if status == sys::MLN_STATUS_OK {
1164                self.closed.set(true);
1165                self.session.frame_acquired.set(false);
1166            }
1167        }
1168    }
1169}
1170
1171impl RenderSessionHandle {
1172    pub(crate) fn attach<F>(map: &MapHandle, attach: F) -> Result<Self>
1173    where
1174        F: FnOnce(*mut sys::mln_map, *mut *mut sys::mln_render_session) -> sys::mln_status,
1175    {
1176        let map_ptr = map.inner.as_ptr()?;
1177        let mut out = maplibre_core::ptr::OutPtr::<sys::mln_render_session>::new();
1178        let status = attach(map_ptr, out.as_mut_ptr());
1179        maplibre_core::check(status)?;
1180        let ptr = out_handle(out, "mln_render_session")?;
1181        Ok(Self {
1182            inner: Rc::new(RenderSessionState::new(ptr, Rc::clone(&map.inner))),
1183        })
1184    }
1185
1186    /// Explicitly destroys the render session.
1187    ///
1188    /// Native destruction errors are returned. When destruction fails, the
1189    /// underlying native handle remains live so a later `close` can retry.
1190    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1191        if let Err(error) = self.inner.ensure_no_frame_acquired() {
1192            return Err(HandleOperationError::new(error, self));
1193        }
1194        if let Err(error) = self.inner.close() {
1195            return Err(HandleOperationError::new(error, self));
1196        }
1197        Ok(())
1198    }
1199    /// Resizes this attached render session.
1200    pub fn resize(&self, width: u32, height: u32, scale_factor: f64) -> Result<()> {
1201        self.inner.ensure_no_frame_acquired()?;
1202        let session = self.inner.as_ptr()?;
1203        // SAFETY: session is a live render session handle owned by this wrapper.
1204        maplibre_core::check(unsafe {
1205            sys::mln_render_session_resize(session, width, height, scale_factor)
1206        })
1207    }
1208
1209    /// Processes the latest map render update for this render target.
1210    pub fn render_update(&self) -> Result<()> {
1211        self.inner.ensure_no_frame_acquired()?;
1212        let session = self.inner.as_ptr()?;
1213        // SAFETY: session is a live render session handle owned by this wrapper.
1214        maplibre_core::check(unsafe { sys::mln_render_session_render_update(session) })
1215    }
1216
1217    /// Detaches backend-bound render resources from the map.
1218    ///
1219    /// The native session remains live only for destruction, so successful
1220    /// detach consumes this handle and returns a detached close-only handle.
1221    pub fn detach(
1222        self,
1223    ) -> std::result::Result<DetachedRenderSessionHandle, HandleOperationError<Self>> {
1224        if let Err(error) = self.inner.ensure_no_frame_acquired() {
1225            return Err(HandleOperationError::new(error, self));
1226        }
1227        let session = match self.inner.as_ptr() {
1228            Ok(session) => session,
1229            Err(error) => return Err(HandleOperationError::new(error, self)),
1230        };
1231        // SAFETY: session is a live render session handle owned by this wrapper.
1232        if let Err(error) = maplibre_core::check(unsafe { sys::mln_render_session_detach(session) })
1233        {
1234            return Err(HandleOperationError::new(error, self));
1235        }
1236        self.inner.detached.set(true);
1237        Ok(DetachedRenderSessionHandle {
1238            inner: Rc::clone(&self.inner),
1239        })
1240    }
1241
1242    /// Asks the session renderer to release cached resources where possible.
1243    pub fn reduce_memory_use(&self) -> Result<()> {
1244        self.inner.ensure_no_frame_acquired()?;
1245        let session = self.inner.as_ptr()?;
1246        // SAFETY: session is a live render session handle owned by this wrapper.
1247        maplibre_core::check(unsafe { sys::mln_render_session_reduce_memory_use(session) })
1248    }
1249
1250    /// Clears renderer data for the session.
1251    pub fn clear_data(&self) -> Result<()> {
1252        self.inner.ensure_no_frame_acquired()?;
1253        let session = self.inner.as_ptr()?;
1254        // SAFETY: session is a live render session handle owned by this wrapper.
1255        maplibre_core::check(unsafe { sys::mln_render_session_clear_data(session) })
1256    }
1257
1258    /// Dumps renderer debug logs through MapLibre Native logging.
1259    pub fn dump_debug_logs(&self) -> Result<()> {
1260        self.inner.ensure_no_frame_acquired()?;
1261        let session = self.inner.as_ptr()?;
1262        // SAFETY: session is a live render session handle owned by this wrapper.
1263        maplibre_core::check(unsafe { sys::mln_render_session_dump_debug_logs(session) })
1264    }
1265
1266    /// Returns CPU readback metadata for the most recently rendered texture frame.
1267    pub fn texture_image_info(&self) -> Result<TextureImageInfo> {
1268        self.inner.ensure_no_frame_acquired()?;
1269        let session = self.inner.as_ptr()?;
1270        // SAFETY: Default constructor takes no arguments and initializes size.
1271        let mut info = unsafe { sys::mln_texture_image_info_default() };
1272        // SAFETY: session is live. Passing a null buffer with zero capacity is
1273        // the documented metadata probe path; out_info points to initialized storage.
1274        let status = unsafe {
1275            sys::mln_texture_read_premultiplied_rgba8(session, std::ptr::null_mut(), 0, &mut info)
1276        };
1277        if status == sys::MLN_STATUS_OK
1278            || (status == sys::MLN_STATUS_INVALID_ARGUMENT && info.byte_length > 0)
1279        {
1280            Ok(maplibre_core::values::texture_image_info_from_native(&info))
1281        } else {
1282            Err(crate::Error::from_status(status))
1283        }
1284    }
1285
1286    /// Reads the most recently rendered texture frame as premultiplied RGBA8.
1287    pub fn read_premultiplied_rgba8_into(&self, data: &mut [u8]) -> Result<TextureImageInfo> {
1288        self.inner.ensure_no_frame_acquired()?;
1289        let session = self.inner.as_ptr()?;
1290        // SAFETY: Default constructor takes no arguments and initializes size.
1291        let mut info = unsafe { sys::mln_texture_image_info_default() };
1292        let data_ptr = if data.is_empty() {
1293            std::ptr::null_mut()
1294        } else {
1295            data.as_mut_ptr()
1296        };
1297        // SAFETY: session is live, data_ptr either points to data's mutable
1298        // storage for data.len() bytes or is null for an empty buffer, and info
1299        // points to initialized writable storage.
1300        maplibre_core::check(unsafe {
1301            sys::mln_texture_read_premultiplied_rgba8(session, data_ptr, data.len(), &mut info)
1302        })?;
1303        Ok(maplibre_core::values::texture_image_info_from_native(&info))
1304    }
1305
1306    /// Acquires a borrowed Metal frame from a session-owned texture target.
1307    pub fn acquire_metal_owned_texture_frame(&self) -> Result<MetalOwnedTextureFrameHandle> {
1308        self.inner.ensure_no_frame_acquired()?;
1309        let session = self.inner.as_ptr()?;
1310        let mut raw = empty_metal_owned_texture_frame();
1311        // SAFETY: session is live and raw points to initialized writable frame storage.
1312        maplibre_core::check(unsafe {
1313            sys::mln_metal_owned_texture_acquire_frame(session, &mut raw)
1314        })?;
1315        self.inner.frame_acquired.set(true);
1316        Ok(MetalOwnedTextureFrameHandle {
1317            session: Rc::clone(&self.inner),
1318            frame: MetalOwnedTextureFrame::from_native(&raw),
1319            raw,
1320            closed: Cell::new(false),
1321            _thread_affine: PhantomData,
1322        })
1323    }
1324
1325    /// Acquires a borrowed Vulkan frame from a session-owned texture target.
1326    pub fn acquire_vulkan_owned_texture_frame(&self) -> Result<VulkanOwnedTextureFrameHandle> {
1327        self.inner.ensure_no_frame_acquired()?;
1328        let session = self.inner.as_ptr()?;
1329        let mut raw = empty_vulkan_owned_texture_frame();
1330        // SAFETY: session is live and raw points to initialized writable frame storage.
1331        maplibre_core::check(unsafe {
1332            sys::mln_vulkan_owned_texture_acquire_frame(session, &mut raw)
1333        })?;
1334        self.inner.frame_acquired.set(true);
1335        Ok(VulkanOwnedTextureFrameHandle {
1336            session: Rc::clone(&self.inner),
1337            frame: VulkanOwnedTextureFrame::from_native(&raw),
1338            raw,
1339            closed: Cell::new(false),
1340            _thread_affine: PhantomData,
1341        })
1342    }
1343
1344    /// Acquires a borrowed OpenGL frame from a session-owned texture target.
1345    pub fn acquire_opengl_owned_texture_frame(&self) -> Result<OpenGLOwnedTextureFrameHandle> {
1346        self.inner.ensure_no_frame_acquired()?;
1347        let session = self.inner.as_ptr()?;
1348        let mut raw = empty_opengl_owned_texture_frame();
1349        // SAFETY: session is live and raw points to initialized writable frame storage.
1350        maplibre_core::check(unsafe {
1351            sys::mln_opengl_owned_texture_acquire_frame(session, &mut raw)
1352        })?;
1353        self.inner.frame_acquired.set(true);
1354        Ok(OpenGLOwnedTextureFrameHandle {
1355            session: Rc::clone(&self.inner),
1356            frame: OpenGLOwnedTextureFrame::from_native(&raw),
1357            raw,
1358            closed: Cell::new(false),
1359            _thread_affine: PhantomData,
1360        })
1361    }
1362}
1363
1364fn empty_metal_owned_texture_frame() -> sys::mln_metal_owned_texture_frame {
1365    sys::mln_metal_owned_texture_frame {
1366        size: mem::size_of::<sys::mln_metal_owned_texture_frame>() as u32,
1367        generation: 0,
1368        width: 0,
1369        height: 0,
1370        scale_factor: 0.0,
1371        frame_id: 0,
1372        texture: std::ptr::null_mut(),
1373        device: std::ptr::null_mut(),
1374        pixel_format: 0,
1375    }
1376}
1377
1378fn empty_vulkan_owned_texture_frame() -> sys::mln_vulkan_owned_texture_frame {
1379    sys::mln_vulkan_owned_texture_frame {
1380        size: mem::size_of::<sys::mln_vulkan_owned_texture_frame>() as u32,
1381        generation: 0,
1382        width: 0,
1383        height: 0,
1384        scale_factor: 0.0,
1385        frame_id: 0,
1386        image: std::ptr::null_mut(),
1387        image_view: std::ptr::null_mut(),
1388        device: std::ptr::null_mut(),
1389        format: 0,
1390        layout: 0,
1391    }
1392}
1393
1394fn empty_opengl_owned_texture_frame() -> sys::mln_opengl_owned_texture_frame {
1395    sys::mln_opengl_owned_texture_frame {
1396        size: mem::size_of::<sys::mln_opengl_owned_texture_frame>() as u32,
1397        generation: 0,
1398        width: 0,
1399        height: 0,
1400        scale_factor: 0.0,
1401        frame_id: 0,
1402        texture: 0,
1403        target: 0,
1404        internal_format: 0,
1405        format: 0,
1406        type_: 0,
1407    }
1408}
1409
1410#[cfg(test)]
1411mod tests;