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