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 fn with_proc_addresses(
290        mut self,
291        get_instance_proc_addr: NativePointer,
292        get_device_proc_addr: NativePointer,
293    ) -> Self {
294        self.get_instance_proc_addr = get_instance_proc_addr;
295        self.get_device_proc_addr = get_device_proc_addr;
296        self
297    }
298
299    pub(crate) fn to_core(&self) -> maplibre_core::render::VulkanContextDescriptorFields {
300        maplibre_core::render::VulkanContextDescriptorFields {
301            instance: self.instance.as_void_ptr(),
302            physical_device: self.physical_device.as_void_ptr(),
303            device: self.device.as_void_ptr(),
304            graphics_queue: self.graphics_queue.as_void_ptr(),
305            graphics_queue_family_index: self.graphics_queue_family_index,
306            get_instance_proc_addr: self.get_instance_proc_addr.as_void_ptr(),
307            get_device_proc_addr: self.get_device_proc_addr.as_void_ptr(),
308        }
309    }
310}
311
312impl Default for VulkanContextDescriptor {
313    fn default() -> Self {
314        Self::new(
315            NativePointer::NULL,
316            NativePointer::NULL,
317            NativePointer::NULL,
318            NativePointer::NULL,
319            0,
320        )
321    }
322}
323
324#[derive(Debug, Clone, PartialEq)]
325#[non_exhaustive]
326pub struct WglContextDescriptor {
327    pub device_context: NativePointer,
328    pub share_context: NativePointer,
329    pub get_proc_address: NativePointer,
330}
331
332impl WglContextDescriptor {
333    pub fn new(device_context: NativePointer, share_context: NativePointer) -> Self {
334        Self {
335            device_context,
336            share_context,
337            get_proc_address: NativePointer::NULL,
338        }
339    }
340
341    pub fn with_proc_address(mut self, get_proc_address: NativePointer) -> Self {
342        self.get_proc_address = get_proc_address;
343        self
344    }
345
346    pub(crate) fn to_core(&self) -> maplibre_core::render::WglContextDescriptorFields {
347        maplibre_core::render::WglContextDescriptorFields {
348            device_context: self.device_context.as_void_ptr(),
349            share_context: self.share_context.as_void_ptr(),
350            get_proc_address: self.get_proc_address.as_void_ptr(),
351        }
352    }
353}
354
355impl Default for WglContextDescriptor {
356    fn default() -> Self {
357        Self::new(NativePointer::NULL, NativePointer::NULL)
358    }
359}
360
361#[derive(Debug, Clone, PartialEq)]
362#[non_exhaustive]
363pub struct EglContextDescriptor {
364    pub display: NativePointer,
365    pub config: NativePointer,
366    pub share_context: NativePointer,
367    pub get_proc_address: NativePointer,
368}
369
370impl EglContextDescriptor {
371    pub fn new(
372        display: NativePointer,
373        config: NativePointer,
374        share_context: NativePointer,
375    ) -> Self {
376        Self {
377            display,
378            config,
379            share_context,
380            get_proc_address: NativePointer::NULL,
381        }
382    }
383
384    pub fn with_proc_address(mut self, get_proc_address: NativePointer) -> Self {
385        self.get_proc_address = get_proc_address;
386        self
387    }
388
389    pub(crate) fn to_core(&self) -> maplibre_core::render::EglContextDescriptorFields {
390        maplibre_core::render::EglContextDescriptorFields {
391            display: self.display.as_void_ptr(),
392            config: self.config.as_void_ptr(),
393            share_context: self.share_context.as_void_ptr(),
394            get_proc_address: self.get_proc_address.as_void_ptr(),
395        }
396    }
397}
398
399impl Default for EglContextDescriptor {
400    fn default() -> Self {
401        Self::new(
402            NativePointer::NULL,
403            NativePointer::NULL,
404            NativePointer::NULL,
405        )
406    }
407}
408
409#[derive(Debug, Clone, PartialEq)]
410#[non_exhaustive]
411pub enum OpenGLContextDescriptor {
412    Wgl(WglContextDescriptor),
413    Egl(EglContextDescriptor),
414}
415
416impl OpenGLContextDescriptor {
417    pub fn wgl(descriptor: WglContextDescriptor) -> Self {
418        Self::Wgl(descriptor)
419    }
420
421    pub fn egl(descriptor: EglContextDescriptor) -> Self {
422        Self::Egl(descriptor)
423    }
424
425    pub(crate) fn to_core(&self) -> maplibre_core::render::OpenGLContextDescriptorFields {
426        match self {
427            Self::Wgl(descriptor) => {
428                maplibre_core::render::OpenGLContextDescriptorFields::Wgl(descriptor.to_core())
429            }
430            Self::Egl(descriptor) => {
431                maplibre_core::render::OpenGLContextDescriptorFields::Egl(descriptor.to_core())
432            }
433        }
434    }
435}
436
437#[derive(Debug, Clone, PartialEq)]
438#[non_exhaustive]
439pub struct MetalSurfaceDescriptor {
440    pub extent: RenderTargetExtent,
441    pub context: MetalContextDescriptor,
442    pub layer: NativePointer,
443}
444
445impl MetalSurfaceDescriptor {
446    pub fn new(
447        extent: RenderTargetExtent,
448        context: MetalContextDescriptor,
449        layer: NativePointer,
450    ) -> Self {
451        Self {
452            extent,
453            context,
454            layer,
455        }
456    }
457
458    pub(crate) fn to_native(&self) -> sys::mln_metal_surface_descriptor {
459        maplibre_core::render::metal_surface_descriptor_to_native(
460            maplibre_core::render::MetalSurfaceDescriptorFields {
461                extent: self.extent.to_core(),
462                context: self.context.to_core(),
463                layer: self.layer.as_void_ptr(),
464            },
465        )
466    }
467}
468
469#[derive(Debug, Clone, PartialEq)]
470#[non_exhaustive]
471pub struct VulkanSurfaceDescriptor {
472    pub extent: RenderTargetExtent,
473    pub context: VulkanContextDescriptor,
474    pub surface: NativePointer,
475}
476
477impl VulkanSurfaceDescriptor {
478    pub fn new(
479        extent: RenderTargetExtent,
480        context: VulkanContextDescriptor,
481        surface: NativePointer,
482    ) -> Self {
483        Self {
484            extent,
485            context,
486            surface,
487        }
488    }
489
490    pub(crate) fn to_native(&self) -> sys::mln_vulkan_surface_descriptor {
491        maplibre_core::render::vulkan_surface_descriptor_to_native(
492            maplibre_core::render::VulkanSurfaceDescriptorFields {
493                extent: self.extent.to_core(),
494                context: self.context.to_core(),
495                surface: self.surface.as_void_ptr(),
496            },
497        )
498    }
499}
500
501#[derive(Debug, Clone, PartialEq)]
502#[non_exhaustive]
503pub struct OpenGLSurfaceDescriptor {
504    pub extent: RenderTargetExtent,
505    pub context: OpenGLContextDescriptor,
506    pub surface: NativePointer,
507}
508
509impl OpenGLSurfaceDescriptor {
510    pub fn new(
511        extent: RenderTargetExtent,
512        context: OpenGLContextDescriptor,
513        surface: NativePointer,
514    ) -> Self {
515        Self {
516            extent,
517            context,
518            surface,
519        }
520    }
521
522    pub(crate) fn to_native(&self) -> sys::mln_opengl_surface_descriptor {
523        maplibre_core::render::opengl_surface_descriptor_to_native(
524            maplibre_core::render::OpenGLSurfaceDescriptorFields {
525                extent: self.extent.to_core(),
526                context: self.context.to_core(),
527                surface: self.surface.as_void_ptr(),
528            },
529        )
530    }
531}
532
533#[derive(Debug, Clone, PartialEq)]
534#[non_exhaustive]
535pub struct MetalOwnedTextureDescriptor {
536    pub extent: RenderTargetExtent,
537    pub context: MetalContextDescriptor,
538}
539
540impl MetalOwnedTextureDescriptor {
541    pub fn new(extent: RenderTargetExtent, context: MetalContextDescriptor) -> Self {
542        Self { extent, context }
543    }
544
545    pub(crate) fn to_native(&self) -> sys::mln_metal_owned_texture_descriptor {
546        maplibre_core::render::metal_owned_texture_descriptor_to_native(
547            maplibre_core::render::MetalOwnedTextureDescriptorFields {
548                extent: self.extent.to_core(),
549                context: self.context.to_core(),
550            },
551        )
552    }
553}
554
555#[derive(Debug, Clone, PartialEq)]
556#[non_exhaustive]
557pub struct MetalBorrowedTextureDescriptor {
558    pub extent: RenderTargetExtent,
559    pub texture: NativePointer,
560}
561
562impl MetalBorrowedTextureDescriptor {
563    pub fn new(extent: RenderTargetExtent, texture: NativePointer) -> Self {
564        Self { extent, texture }
565    }
566
567    pub(crate) fn to_native(&self) -> sys::mln_metal_borrowed_texture_descriptor {
568        maplibre_core::render::metal_borrowed_texture_descriptor_to_native(
569            maplibre_core::render::MetalBorrowedTextureDescriptorFields {
570                extent: self.extent.to_core(),
571                texture: self.texture.as_void_ptr(),
572            },
573        )
574    }
575}
576
577#[derive(Debug, Clone, PartialEq)]
578#[non_exhaustive]
579pub struct VulkanOwnedTextureDescriptor {
580    pub extent: RenderTargetExtent,
581    pub context: VulkanContextDescriptor,
582}
583
584impl VulkanOwnedTextureDescriptor {
585    pub fn new(extent: RenderTargetExtent, context: VulkanContextDescriptor) -> Self {
586        Self { extent, context }
587    }
588
589    pub(crate) fn to_native(&self) -> sys::mln_vulkan_owned_texture_descriptor {
590        maplibre_core::render::vulkan_owned_texture_descriptor_to_native(
591            maplibre_core::render::VulkanOwnedTextureDescriptorFields {
592                extent: self.extent.to_core(),
593                context: self.context.to_core(),
594            },
595        )
596    }
597}
598
599#[derive(Debug, Clone, PartialEq)]
600#[non_exhaustive]
601pub struct VulkanBorrowedTextureDescriptor {
602    pub extent: RenderTargetExtent,
603    pub context: VulkanContextDescriptor,
604    pub image: NativePointer,
605    pub image_view: NativePointer,
606    pub format: u32,
607    pub initial_layout: u32,
608    pub final_layout: u32,
609}
610
611impl VulkanBorrowedTextureDescriptor {
612    #[allow(clippy::too_many_arguments)]
613    pub fn new(
614        extent: RenderTargetExtent,
615        context: VulkanContextDescriptor,
616        image: NativePointer,
617        image_view: NativePointer,
618        format: u32,
619        initial_layout: u32,
620        final_layout: u32,
621    ) -> Self {
622        Self {
623            extent,
624            context,
625            image,
626            image_view,
627            format,
628            initial_layout,
629            final_layout,
630        }
631    }
632
633    pub(crate) fn to_native(&self) -> sys::mln_vulkan_borrowed_texture_descriptor {
634        maplibre_core::render::vulkan_borrowed_texture_descriptor_to_native(
635            maplibre_core::render::VulkanBorrowedTextureDescriptorFields {
636                extent: self.extent.to_core(),
637                context: self.context.to_core(),
638                image: self.image.as_void_ptr(),
639                image_view: self.image_view.as_void_ptr(),
640                format: self.format,
641                initial_layout: self.initial_layout,
642                final_layout: self.final_layout,
643            },
644        )
645    }
646}
647
648#[derive(Debug, Clone, PartialEq)]
649#[non_exhaustive]
650pub struct OpenGLOwnedTextureDescriptor {
651    pub extent: RenderTargetExtent,
652    pub context: OpenGLContextDescriptor,
653}
654
655impl OpenGLOwnedTextureDescriptor {
656    pub fn new(extent: RenderTargetExtent, context: OpenGLContextDescriptor) -> Self {
657        Self { extent, context }
658    }
659
660    pub(crate) fn to_native(&self) -> sys::mln_opengl_owned_texture_descriptor {
661        maplibre_core::render::opengl_owned_texture_descriptor_to_native(
662            maplibre_core::render::OpenGLOwnedTextureDescriptorFields {
663                extent: self.extent.to_core(),
664                context: self.context.to_core(),
665            },
666        )
667    }
668}
669
670#[derive(Debug, Clone, PartialEq)]
671#[non_exhaustive]
672pub struct OpenGLBorrowedTextureDescriptor {
673    pub extent: RenderTargetExtent,
674    pub context: OpenGLContextDescriptor,
675    pub texture: u32,
676    pub target: u32,
677}
678
679impl OpenGLBorrowedTextureDescriptor {
680    pub fn new(
681        extent: RenderTargetExtent,
682        context: OpenGLContextDescriptor,
683        texture: u32,
684        target: u32,
685    ) -> Self {
686        Self {
687            extent,
688            context,
689            texture,
690            target,
691        }
692    }
693
694    pub(crate) fn to_native(&self) -> sys::mln_opengl_borrowed_texture_descriptor {
695        maplibre_core::render::opengl_borrowed_texture_descriptor_to_native(
696            maplibre_core::render::OpenGLBorrowedTextureDescriptorFields {
697                extent: self.extent.to_core(),
698                context: self.context.to_core(),
699                texture: self.texture,
700                target: self.target,
701            },
702        )
703    }
704}
705
706#[derive(Debug)]
707struct RenderSessionState {
708    handle: ThreadAffineNativeHandle<sys::mln_render_session>,
709    map: RefCell<Option<Rc<MapState>>>,
710    detached: Cell<bool>,
711    frame_acquired: Cell<bool>,
712}
713
714impl RenderSessionState {
715    fn new(ptr: NonNull<sys::mln_render_session>, map: Rc<MapState>) -> Self {
716        // SAFETY: ptr came from a successful render-session attach call and is
717        // paired with the matching render-session destroy function.
718        let handle = unsafe {
719            ThreadAffineNativeHandle::from_raw(
720                ptr,
721                sys::mln_render_session_destroy,
722                "mln_render_session",
723            )
724        };
725        Self {
726            handle,
727            map: RefCell::new(Some(map)),
728            detached: Cell::new(false),
729            frame_acquired: Cell::new(false),
730        }
731    }
732
733    fn ensure_no_frame_acquired(&self) -> Result<()> {
734        if self.frame_acquired.get() {
735            Err(frame_acquired_error())
736        } else {
737            Ok(())
738        }
739    }
740
741    fn as_ptr(&self) -> Result<*mut sys::mln_render_session> {
742        let ptr = self.handle.as_ptr();
743        if ptr.is_null() {
744            Err(closed_handle_error("RenderSessionHandle"))
745        } else {
746            Ok(ptr)
747        }
748    }
749
750    fn close(&self) -> Result<()> {
751        self.handle.close()?;
752        self.map.borrow_mut().take();
753        Ok(())
754    }
755}
756
757/// Owner-thread render session handle bound to a retained map.
758pub struct RenderSessionHandle {
759    inner: Rc<RenderSessionState>,
760}
761
762impl fmt::Debug for RenderSessionHandle {
763    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
764        f.debug_struct("RenderSessionHandle")
765            .field("closed", &self.is_closed())
766            .field("detached", &self.inner.detached.get())
767            .finish()
768    }
769}
770
771/// Render session after backend resources have been detached.
772pub struct DetachedRenderSessionHandle {
773    inner: Rc<RenderSessionState>,
774}
775
776impl fmt::Debug for DetachedRenderSessionHandle {
777    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
778        f.debug_struct("DetachedRenderSessionHandle")
779            .field("closed", &self.is_closed())
780            .finish()
781    }
782}
783
784impl DetachedRenderSessionHandle {
785    /// Explicitly destroys the detached render session.
786    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
787        if let Err(error) = self.inner.close() {
788            return Err(HandleOperationError::new(error, self));
789        }
790        Ok(())
791    }
792
793    pub fn is_closed(&self) -> bool {
794        self.inner.handle.is_closed()
795    }
796}
797
798fn frame_acquired_error() -> crate::Error {
799    crate::Error::new(
800        crate::ErrorKind::InvalidState,
801        None,
802        "render session has an acquired texture frame",
803    )
804}
805
806/// Copied metadata for an acquired Metal session-owned texture frame.
807///
808/// Backend pointers are exposed by [`MetalOwnedTextureFrameHandle`] so their
809/// lifetime stays tied to the open frame handle.
810#[derive(Debug, Clone, Copy, PartialEq)]
811#[non_exhaustive]
812pub struct MetalOwnedTextureFrame {
813    pub generation: u64,
814    pub width: u32,
815    pub height: u32,
816    pub scale_factor: f64,
817    pub frame_id: u64,
818    pub pixel_format: u64,
819}
820
821impl MetalOwnedTextureFrame {
822    fn from_native(raw: &sys::mln_metal_owned_texture_frame) -> Self {
823        Self {
824            generation: raw.generation,
825            width: raw.width,
826            height: raw.height,
827            scale_factor: raw.scale_factor,
828            frame_id: raw.frame_id,
829            pixel_format: raw.pixel_format,
830        }
831    }
832}
833
834/// Copied metadata for an acquired Vulkan session-owned texture frame.
835///
836/// Backend pointers are exposed by [`VulkanOwnedTextureFrameHandle`] so their
837/// lifetime stays tied to the open frame handle.
838#[derive(Debug, Clone, Copy, PartialEq)]
839#[non_exhaustive]
840pub struct VulkanOwnedTextureFrame {
841    pub generation: u64,
842    pub width: u32,
843    pub height: u32,
844    pub scale_factor: f64,
845    pub frame_id: u64,
846    pub format: u32,
847    pub layout: u32,
848}
849
850impl VulkanOwnedTextureFrame {
851    fn from_native(raw: &sys::mln_vulkan_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            format: raw.format,
859            layout: raw.layout,
860        }
861    }
862}
863
864/// Copied metadata for an acquired OpenGL session-owned texture frame.
865///
866/// The texture object name is exposed by [`OpenGLOwnedTextureFrameHandle`] so
867/// its lifetime stays tied to the open frame handle.
868#[derive(Debug, Clone, Copy, PartialEq)]
869#[non_exhaustive]
870pub struct OpenGLOwnedTextureFrame {
871    pub generation: u64,
872    pub width: u32,
873    pub height: u32,
874    pub scale_factor: f64,
875    pub frame_id: u64,
876    pub target: u32,
877    pub internal_format: u32,
878    pub format: u32,
879    pub type_: u32,
880}
881
882impl OpenGLOwnedTextureFrame {
883    fn from_native(raw: &sys::mln_opengl_owned_texture_frame) -> Self {
884        Self {
885            generation: raw.generation,
886            width: raw.width,
887            height: raw.height,
888            scale_factor: raw.scale_factor,
889            frame_id: raw.frame_id,
890            target: raw.target,
891            internal_format: raw.internal_format,
892            format: raw.format,
893            type_: raw.type_,
894        }
895    }
896}
897
898/// RAII guard for an acquired Metal session-owned texture frame.
899///
900/// Releasing the guard ends the borrow of the backend Metal texture and device.
901pub struct MetalOwnedTextureFrameHandle {
902    session: Rc<RenderSessionState>,
903    raw: sys::mln_metal_owned_texture_frame,
904    frame: MetalOwnedTextureFrame,
905    closed: Cell<bool>,
906    _thread_affine: PhantomData<Rc<()>>,
907}
908
909impl fmt::Debug for MetalOwnedTextureFrameHandle {
910    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
911        f.debug_struct("MetalOwnedTextureFrameHandle")
912            .field("closed", &self.is_closed())
913            .field("frame", &self.frame)
914            .finish()
915    }
916}
917
918impl MetalOwnedTextureFrameHandle {
919    /// Returns copied metadata for this acquired frame.
920    pub fn frame(&self) -> Result<&MetalOwnedTextureFrame> {
921        if self.closed.get() {
922            Err(closed_handle_error("MetalOwnedTextureFrameHandle"))
923        } else {
924            Ok(&self.frame)
925        }
926    }
927
928    pub fn is_closed(&self) -> bool {
929        self.closed.get()
930    }
931
932    /// Returns the borrowed Metal texture pointer for backend interop.
933    ///
934    /// # Safety
935    ///
936    /// The returned pointer is valid only while this frame handle remains open.
937    /// The caller must not store or use it after frame release and must satisfy
938    /// Metal synchronization and thread-affinity requirements.
939    pub unsafe fn texture(&self) -> Result<FrameNativePointer<'_>> {
940        if self.closed.get() {
941            Err(closed_handle_error("MetalOwnedTextureFrameHandle"))
942        } else {
943            // SAFETY: The active native frame owns the validity contract for
944            // this borrowed backend handle until release.
945            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.texture) })
946        }
947    }
948
949    /// Returns the borrowed Metal device pointer for backend interop.
950    ///
951    /// # Safety
952    ///
953    /// The returned pointer has the same lifetime and synchronization
954    /// requirements as [`MetalOwnedTextureFrameHandle::texture`].
955    pub unsafe fn device(&self) -> Result<FrameNativePointer<'_>> {
956        if self.closed.get() {
957            Err(closed_handle_error("MetalOwnedTextureFrameHandle"))
958        } else {
959            // SAFETY: See texture above.
960            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.device) })
961        }
962    }
963
964    /// Explicitly releases this frame.
965    #[allow(clippy::result_large_err)]
966    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
967        if self.closed.get() {
968            return Ok(());
969        }
970        let session = match self.session.as_ptr() {
971            Ok(session) => session,
972            Err(error) => return Err(HandleOperationError::new(error, self)),
973        };
974        // SAFETY: session is live, and raw is the active frame returned by a
975        // successful acquire for this session until release succeeds.
976        if let Err(error) = maplibre_core::check(unsafe {
977            sys::mln_metal_owned_texture_release_frame(session, &self.raw)
978        }) {
979            return Err(HandleOperationError::new(error, self));
980        }
981        self.closed.set(true);
982        self.session.frame_acquired.set(false);
983        Ok(())
984    }
985}
986
987impl Drop for MetalOwnedTextureFrameHandle {
988    fn drop(&mut self) {
989        if self.closed.get() {
990            return;
991        }
992        if let Ok(session) = self.session.as_ptr() {
993            // SAFETY: Best-effort release of the active frame. Drop cannot
994            // report errors and never panics.
995            let status = unsafe { sys::mln_metal_owned_texture_release_frame(session, &self.raw) };
996            if status == sys::MLN_STATUS_OK {
997                self.closed.set(true);
998                self.session.frame_acquired.set(false);
999            }
1000        }
1001    }
1002}
1003
1004/// RAII guard for an acquired Vulkan session-owned texture frame.
1005///
1006/// Releasing the guard ends the borrow of the backend Vulkan image, image view,
1007/// and device.
1008pub struct VulkanOwnedTextureFrameHandle {
1009    session: Rc<RenderSessionState>,
1010    raw: sys::mln_vulkan_owned_texture_frame,
1011    frame: VulkanOwnedTextureFrame,
1012    closed: Cell<bool>,
1013    _thread_affine: PhantomData<Rc<()>>,
1014}
1015
1016impl fmt::Debug for VulkanOwnedTextureFrameHandle {
1017    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1018        f.debug_struct("VulkanOwnedTextureFrameHandle")
1019            .field("closed", &self.is_closed())
1020            .field("frame", &self.frame)
1021            .finish()
1022    }
1023}
1024
1025impl VulkanOwnedTextureFrameHandle {
1026    /// Returns copied metadata for this acquired frame.
1027    pub fn frame(&self) -> Result<&VulkanOwnedTextureFrame> {
1028        if self.closed.get() {
1029            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1030        } else {
1031            Ok(&self.frame)
1032        }
1033    }
1034
1035    pub fn is_closed(&self) -> bool {
1036        self.closed.get()
1037    }
1038
1039    /// Returns the borrowed Vulkan image pointer for backend interop.
1040    ///
1041    /// # Safety
1042    ///
1043    /// The returned pointer is valid only while this frame handle remains open.
1044    /// The caller must not store or use it after frame release and must satisfy
1045    /// Vulkan synchronization and thread-affinity requirements.
1046    pub unsafe fn image(&self) -> Result<FrameNativePointer<'_>> {
1047        if self.closed.get() {
1048            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1049        } else {
1050            // SAFETY: The active native frame owns the validity contract for
1051            // this borrowed backend handle until release.
1052            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.image) })
1053        }
1054    }
1055
1056    /// Returns the borrowed Vulkan image view pointer for backend interop.
1057    ///
1058    /// # Safety
1059    ///
1060    /// The returned pointer has the same lifetime and synchronization
1061    /// requirements as [`VulkanOwnedTextureFrameHandle::image`].
1062    pub unsafe fn image_view(&self) -> Result<FrameNativePointer<'_>> {
1063        if self.closed.get() {
1064            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1065        } else {
1066            // SAFETY: See image above.
1067            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.image_view) })
1068        }
1069    }
1070
1071    /// Returns the borrowed Vulkan device pointer for backend interop.
1072    ///
1073    /// # Safety
1074    ///
1075    /// The returned pointer has the same lifetime and synchronization
1076    /// requirements as [`VulkanOwnedTextureFrameHandle::image`].
1077    pub unsafe fn device(&self) -> Result<FrameNativePointer<'_>> {
1078        if self.closed.get() {
1079            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1080        } else {
1081            // SAFETY: See image above.
1082            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.device) })
1083        }
1084    }
1085
1086    /// Explicitly releases this frame.
1087    #[allow(clippy::result_large_err)]
1088    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1089        if self.closed.get() {
1090            return Ok(());
1091        }
1092        let session = match self.session.as_ptr() {
1093            Ok(session) => session,
1094            Err(error) => return Err(HandleOperationError::new(error, self)),
1095        };
1096        // SAFETY: session is live, and raw is the active frame returned by a
1097        // successful acquire for this session until release succeeds.
1098        if let Err(error) = maplibre_core::check(unsafe {
1099            sys::mln_vulkan_owned_texture_release_frame(session, &self.raw)
1100        }) {
1101            return Err(HandleOperationError::new(error, self));
1102        }
1103        self.closed.set(true);
1104        self.session.frame_acquired.set(false);
1105        Ok(())
1106    }
1107}
1108
1109impl Drop for VulkanOwnedTextureFrameHandle {
1110    fn drop(&mut self) {
1111        if self.closed.get() {
1112            return;
1113        }
1114        if let Ok(session) = self.session.as_ptr() {
1115            // SAFETY: Best-effort release of the active frame. Drop cannot
1116            // report errors and never panics.
1117            let status = unsafe { sys::mln_vulkan_owned_texture_release_frame(session, &self.raw) };
1118            if status == sys::MLN_STATUS_OK {
1119                self.closed.set(true);
1120                self.session.frame_acquired.set(false);
1121            }
1122        }
1123    }
1124}
1125
1126/// RAII guard for an acquired OpenGL session-owned texture frame.
1127///
1128/// Releasing the guard ends the borrow of the backend OpenGL texture object.
1129pub struct OpenGLOwnedTextureFrameHandle {
1130    session: Rc<RenderSessionState>,
1131    raw: sys::mln_opengl_owned_texture_frame,
1132    frame: OpenGLOwnedTextureFrame,
1133    closed: Cell<bool>,
1134    _thread_affine: PhantomData<Rc<()>>,
1135}
1136
1137impl fmt::Debug for OpenGLOwnedTextureFrameHandle {
1138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1139        f.debug_struct("OpenGLOwnedTextureFrameHandle")
1140            .field("closed", &self.is_closed())
1141            .field("frame", &self.frame)
1142            .finish()
1143    }
1144}
1145
1146impl OpenGLOwnedTextureFrameHandle {
1147    /// Returns copied metadata for this acquired frame.
1148    pub fn frame(&self) -> Result<&OpenGLOwnedTextureFrame> {
1149        if self.closed.get() {
1150            Err(closed_handle_error("OpenGLOwnedTextureFrameHandle"))
1151        } else {
1152            Ok(&self.frame)
1153        }
1154    }
1155
1156    pub fn is_closed(&self) -> bool {
1157        self.closed.get()
1158    }
1159
1160    /// Returns the borrowed OpenGL texture object name for backend interop.
1161    pub fn texture(&self) -> Result<FrameOpenGLTextureName<'_>> {
1162        if self.closed.get() {
1163            Err(closed_handle_error("OpenGLOwnedTextureFrameHandle"))
1164        } else {
1165            Ok(FrameOpenGLTextureName::new(self.raw.texture))
1166        }
1167    }
1168
1169    /// Explicitly releases this frame.
1170    #[allow(clippy::result_large_err)]
1171    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1172        if self.closed.get() {
1173            return Ok(());
1174        }
1175        let session = match self.session.as_ptr() {
1176            Ok(session) => session,
1177            Err(error) => return Err(HandleOperationError::new(error, self)),
1178        };
1179        // SAFETY: session is live, and raw is the active frame returned by a
1180        // successful acquire for this session until release succeeds.
1181        if let Err(error) = maplibre_core::check(unsafe {
1182            sys::mln_opengl_owned_texture_release_frame(session, &self.raw)
1183        }) {
1184            return Err(HandleOperationError::new(error, self));
1185        }
1186        self.closed.set(true);
1187        self.session.frame_acquired.set(false);
1188        Ok(())
1189    }
1190}
1191
1192impl Drop for OpenGLOwnedTextureFrameHandle {
1193    fn drop(&mut self) {
1194        if self.closed.get() {
1195            return;
1196        }
1197        if let Ok(session) = self.session.as_ptr() {
1198            // SAFETY: Best-effort release of the active frame. Drop cannot
1199            // report errors and never panics.
1200            let status = unsafe { sys::mln_opengl_owned_texture_release_frame(session, &self.raw) };
1201            if status == sys::MLN_STATUS_OK {
1202                self.closed.set(true);
1203                self.session.frame_acquired.set(false);
1204            }
1205        }
1206    }
1207}
1208
1209impl RenderSessionHandle {
1210    pub(crate) fn attach<F>(map: &MapHandle, attach: F) -> Result<Self>
1211    where
1212        F: FnOnce(*mut sys::mln_map, *mut *mut sys::mln_render_session) -> sys::mln_status,
1213    {
1214        let map_ptr = map.inner.as_ptr()?;
1215        let mut out = maplibre_core::ptr::OutPtr::<sys::mln_render_session>::new();
1216        let status = attach(map_ptr, out.as_mut_ptr());
1217        maplibre_core::check(status)?;
1218        let ptr = out_handle(out, "mln_render_session")?;
1219        Ok(Self {
1220            inner: Rc::new(RenderSessionState::new(ptr, Rc::clone(&map.inner))),
1221        })
1222    }
1223
1224    /// Explicitly destroys the render session.
1225    ///
1226    /// Native destruction errors are returned. When destruction fails, the
1227    /// underlying native handle remains live so a later `close` can retry.
1228    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1229        if let Err(error) = self.inner.ensure_no_frame_acquired() {
1230            return Err(HandleOperationError::new(error, self));
1231        }
1232        if let Err(error) = self.inner.close() {
1233            return Err(HandleOperationError::new(error, self));
1234        }
1235        Ok(())
1236    }
1237
1238    pub fn is_closed(&self) -> bool {
1239        self.inner.handle.is_closed()
1240    }
1241
1242    /// Resizes this attached render session.
1243    pub fn resize(&self, width: u32, height: u32, scale_factor: f64) -> 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 {
1248            sys::mln_render_session_resize(session, width, height, scale_factor)
1249        })
1250    }
1251
1252    /// Processes the latest map render update for this render target.
1253    pub fn render_update(&self) -> Result<()> {
1254        self.inner.ensure_no_frame_acquired()?;
1255        let session = self.inner.as_ptr()?;
1256        // SAFETY: session is a live render session handle owned by this wrapper.
1257        maplibre_core::check(unsafe { sys::mln_render_session_render_update(session) })
1258    }
1259
1260    /// Detaches backend-bound render resources from the map.
1261    ///
1262    /// The native session remains live only for destruction, so successful
1263    /// detach consumes this handle and returns a detached close-only handle.
1264    pub fn detach(
1265        self,
1266    ) -> std::result::Result<DetachedRenderSessionHandle, HandleOperationError<Self>> {
1267        if let Err(error) = self.inner.ensure_no_frame_acquired() {
1268            return Err(HandleOperationError::new(error, self));
1269        }
1270        let session = match self.inner.as_ptr() {
1271            Ok(session) => session,
1272            Err(error) => return Err(HandleOperationError::new(error, self)),
1273        };
1274        // SAFETY: session is a live render session handle owned by this wrapper.
1275        if let Err(error) = maplibre_core::check(unsafe { sys::mln_render_session_detach(session) })
1276        {
1277            return Err(HandleOperationError::new(error, self));
1278        }
1279        self.inner.detached.set(true);
1280        Ok(DetachedRenderSessionHandle {
1281            inner: Rc::clone(&self.inner),
1282        })
1283    }
1284
1285    /// Asks the session renderer to release cached resources where possible.
1286    pub fn reduce_memory_use(&self) -> Result<()> {
1287        self.inner.ensure_no_frame_acquired()?;
1288        let session = self.inner.as_ptr()?;
1289        // SAFETY: session is a live render session handle owned by this wrapper.
1290        maplibre_core::check(unsafe { sys::mln_render_session_reduce_memory_use(session) })
1291    }
1292
1293    /// Clears renderer data for the session.
1294    pub fn clear_data(&self) -> Result<()> {
1295        self.inner.ensure_no_frame_acquired()?;
1296        let session = self.inner.as_ptr()?;
1297        // SAFETY: session is a live render session handle owned by this wrapper.
1298        maplibre_core::check(unsafe { sys::mln_render_session_clear_data(session) })
1299    }
1300
1301    /// Dumps renderer debug logs through MapLibre Native logging.
1302    pub fn dump_debug_logs(&self) -> Result<()> {
1303        self.inner.ensure_no_frame_acquired()?;
1304        let session = self.inner.as_ptr()?;
1305        // SAFETY: session is a live render session handle owned by this wrapper.
1306        maplibre_core::check(unsafe { sys::mln_render_session_dump_debug_logs(session) })
1307    }
1308
1309    /// Returns CPU readback metadata for the most recently rendered texture frame.
1310    pub fn texture_image_info(&self) -> Result<TextureImageInfo> {
1311        self.inner.ensure_no_frame_acquired()?;
1312        let session = self.inner.as_ptr()?;
1313        // SAFETY: Default constructor takes no arguments and initializes size.
1314        let mut info = unsafe { sys::mln_texture_image_info_default() };
1315        // SAFETY: session is live. Passing a null buffer with zero capacity is
1316        // the documented metadata probe path; out_info points to initialized storage.
1317        let status = unsafe {
1318            sys::mln_texture_read_premultiplied_rgba8(session, std::ptr::null_mut(), 0, &mut info)
1319        };
1320        if status == sys::MLN_STATUS_OK
1321            || (status == sys::MLN_STATUS_INVALID_ARGUMENT && info.byte_length > 0)
1322        {
1323            Ok(maplibre_core::values::texture_image_info_from_native(&info))
1324        } else {
1325            Err(crate::Error::from_status(status))
1326        }
1327    }
1328
1329    /// Reads the most recently rendered texture frame as premultiplied RGBA8.
1330    pub fn read_premultiplied_rgba8_into(&self, data: &mut [u8]) -> Result<TextureImageInfo> {
1331        self.inner.ensure_no_frame_acquired()?;
1332        let session = self.inner.as_ptr()?;
1333        // SAFETY: Default constructor takes no arguments and initializes size.
1334        let mut info = unsafe { sys::mln_texture_image_info_default() };
1335        let data_ptr = if data.is_empty() {
1336            std::ptr::null_mut()
1337        } else {
1338            data.as_mut_ptr()
1339        };
1340        // SAFETY: session is live, data_ptr either points to data's mutable
1341        // storage for data.len() bytes or is null for an empty buffer, and info
1342        // points to initialized writable storage.
1343        maplibre_core::check(unsafe {
1344            sys::mln_texture_read_premultiplied_rgba8(session, data_ptr, data.len(), &mut info)
1345        })?;
1346        Ok(maplibre_core::values::texture_image_info_from_native(&info))
1347    }
1348
1349    /// Reads the most recently rendered texture frame into owned bytes.
1350    pub fn read_premultiplied_rgba8(&self) -> Result<PremultipliedRgba8Image> {
1351        let info = self.texture_image_info()?;
1352        let mut data = vec![0; info.byte_length];
1353        let info = self.read_premultiplied_rgba8_into(&mut data)?;
1354        Ok(PremultipliedRgba8Image::new(info, data))
1355    }
1356
1357    /// Acquires a borrowed Metal frame from a session-owned texture target.
1358    pub fn acquire_metal_owned_texture_frame(&self) -> Result<MetalOwnedTextureFrameHandle> {
1359        self.inner.ensure_no_frame_acquired()?;
1360        let session = self.inner.as_ptr()?;
1361        let mut raw = empty_metal_owned_texture_frame();
1362        // SAFETY: session is live and raw points to initialized writable frame storage.
1363        maplibre_core::check(unsafe {
1364            sys::mln_metal_owned_texture_acquire_frame(session, &mut raw)
1365        })?;
1366        self.inner.frame_acquired.set(true);
1367        Ok(MetalOwnedTextureFrameHandle {
1368            session: Rc::clone(&self.inner),
1369            frame: MetalOwnedTextureFrame::from_native(&raw),
1370            raw,
1371            closed: Cell::new(false),
1372            _thread_affine: PhantomData,
1373        })
1374    }
1375
1376    /// Acquires a borrowed Vulkan frame from a session-owned texture target.
1377    pub fn acquire_vulkan_owned_texture_frame(&self) -> Result<VulkanOwnedTextureFrameHandle> {
1378        self.inner.ensure_no_frame_acquired()?;
1379        let session = self.inner.as_ptr()?;
1380        let mut raw = empty_vulkan_owned_texture_frame();
1381        // SAFETY: session is live and raw points to initialized writable frame storage.
1382        maplibre_core::check(unsafe {
1383            sys::mln_vulkan_owned_texture_acquire_frame(session, &mut raw)
1384        })?;
1385        self.inner.frame_acquired.set(true);
1386        Ok(VulkanOwnedTextureFrameHandle {
1387            session: Rc::clone(&self.inner),
1388            frame: VulkanOwnedTextureFrame::from_native(&raw),
1389            raw,
1390            closed: Cell::new(false),
1391            _thread_affine: PhantomData,
1392        })
1393    }
1394
1395    /// Acquires a borrowed OpenGL frame from a session-owned texture target.
1396    pub fn acquire_opengl_owned_texture_frame(&self) -> Result<OpenGLOwnedTextureFrameHandle> {
1397        self.inner.ensure_no_frame_acquired()?;
1398        let session = self.inner.as_ptr()?;
1399        let mut raw = empty_opengl_owned_texture_frame();
1400        // SAFETY: session is live and raw points to initialized writable frame storage.
1401        maplibre_core::check(unsafe {
1402            sys::mln_opengl_owned_texture_acquire_frame(session, &mut raw)
1403        })?;
1404        self.inner.frame_acquired.set(true);
1405        Ok(OpenGLOwnedTextureFrameHandle {
1406            session: Rc::clone(&self.inner),
1407            frame: OpenGLOwnedTextureFrame::from_native(&raw),
1408            raw,
1409            closed: Cell::new(false),
1410            _thread_affine: PhantomData,
1411        })
1412    }
1413}
1414
1415fn empty_metal_owned_texture_frame() -> sys::mln_metal_owned_texture_frame {
1416    sys::mln_metal_owned_texture_frame {
1417        size: mem::size_of::<sys::mln_metal_owned_texture_frame>() as u32,
1418        generation: 0,
1419        width: 0,
1420        height: 0,
1421        scale_factor: 0.0,
1422        frame_id: 0,
1423        texture: std::ptr::null_mut(),
1424        device: std::ptr::null_mut(),
1425        pixel_format: 0,
1426    }
1427}
1428
1429fn empty_vulkan_owned_texture_frame() -> sys::mln_vulkan_owned_texture_frame {
1430    sys::mln_vulkan_owned_texture_frame {
1431        size: mem::size_of::<sys::mln_vulkan_owned_texture_frame>() as u32,
1432        generation: 0,
1433        width: 0,
1434        height: 0,
1435        scale_factor: 0.0,
1436        frame_id: 0,
1437        image: std::ptr::null_mut(),
1438        image_view: std::ptr::null_mut(),
1439        device: std::ptr::null_mut(),
1440        format: 0,
1441        layout: 0,
1442    }
1443}
1444
1445fn empty_opengl_owned_texture_frame() -> sys::mln_opengl_owned_texture_frame {
1446    sys::mln_opengl_owned_texture_frame {
1447        size: mem::size_of::<sys::mln_opengl_owned_texture_frame>() as u32,
1448        generation: 0,
1449        width: 0,
1450        height: 0,
1451        scale_factor: 0.0,
1452        frame_id: 0,
1453        texture: 0,
1454        target: 0,
1455        internal_format: 0,
1456        format: 0,
1457        type_: 0,
1458    }
1459}
1460
1461#[cfg(test)]
1462mod tests;