Skip to main content

maplibre_native_ffi/
render.rs

1use std::cell::Cell;
2use std::fmt;
3use std::marker::PhantomData;
4use std::mem;
5use std::rc::Rc;
6
7pub use maplibre_core::{PremultipliedRgba8Image, TextureImageInfo};
8use maplibre_native_ffi_core as maplibre_core;
9use maplibre_native_ffi_core::{OpenGLClientApi, OpenGLContextOwnership, RenderResult};
10use maplibre_native_ffi_sys as sys;
11
12use crate::handle::{ThreadAffineNativeHandle, closed_handle_error, out_handle};
13use crate::map::MapAttachRef;
14use crate::{HandleOperationError, Result};
15
16/// Borrowed opaque native address used for backend interop handles. It does not
17/// own, retain, dereference, or validate the pointed-to object, and passing it
18/// to MapLibre Native transfers no ownership.
19#[derive(Clone, Copy, PartialEq, Eq, Hash)]
20pub struct NativePointer {
21    address: usize,
22    _thread_affine: PhantomData<Rc<()>>,
23}
24
25impl NativePointer {
26    /// Null backend handle value.
27    pub const NULL: Self = Self {
28        address: 0,
29        _thread_affine: PhantomData,
30    };
31
32    /// Creates an opaque borrowed pointer value from a native address.
33    ///
34    /// # Safety
35    ///
36    /// The address must have the correct backend-native type for every API it
37    /// is passed to, and the native object must stay valid for the whole borrow
38    /// that API requires. This wrapper validates nothing.
39    pub unsafe fn from_address(address: usize) -> Self {
40        if address == 0 {
41            Self::NULL
42        } else {
43            Self {
44                address,
45                _thread_affine: PhantomData,
46            }
47        }
48    }
49
50    /// Creates an opaque borrowed pointer value from a raw pointer.
51    ///
52    /// # Safety
53    ///
54    /// The pointer must satisfy the same requirements as
55    /// [`NativePointer::from_address`].
56    pub unsafe fn from_ptr<T>(ptr: *mut T) -> Self {
57        // SAFETY: The caller upholds the native pointer lifetime and type
58        // requirements documented above; this conversion only stores the address.
59        unsafe { Self::from_address(ptr as usize) }
60    }
61
62    /// Returns this opaque value as an integer address.
63    pub fn address(self) -> usize {
64        self.address
65    }
66
67    /// Returns whether this value is null.
68    pub fn is_null(self) -> bool {
69        self.address == 0
70    }
71
72    /// Reconstructs a raw pointer for a backend interop call.
73    ///
74    /// # Safety
75    ///
76    /// The caller must choose the correct pointer type and uphold the lifetime,
77    /// thread-affinity, synchronization, and aliasing requirements of the
78    /// backend API that will receive the pointer.
79    pub unsafe fn as_ptr<T>(self) -> *mut T {
80        self.address as *mut T
81    }
82
83    fn as_void_ptr(self) -> *mut std::ffi::c_void {
84        self.address as *mut std::ffi::c_void
85    }
86}
87
88impl fmt::Debug for NativePointer {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(f, "NativePointer(0x{:x})", self.address)
91    }
92}
93
94/// Borrowed opaque native address whose validity is tied to an active texture
95/// frame. It does not own, retain, dereference, or validate the pointed-to
96/// object.
97#[derive(Clone, Copy, PartialEq, Eq, Hash)]
98pub struct FrameNativePointer<'frame> {
99    address: usize,
100    _frame: PhantomData<&'frame ()>,
101    _thread_affine: PhantomData<Rc<()>>,
102}
103
104impl<'frame> FrameNativePointer<'frame> {
105    unsafe fn from_ptr<T>(ptr: *mut T) -> Self {
106        Self {
107            address: ptr as usize,
108            _frame: PhantomData,
109            _thread_affine: PhantomData,
110        }
111    }
112
113    /// Returns this opaque value as an integer address.
114    ///
115    /// # Safety
116    ///
117    /// The returned integer no longer carries this value's frame lifetime. The
118    /// caller must use it only while the borrowed frame remains open and must
119    /// satisfy the backend API's type, synchronization, and thread-affinity
120    /// requirements.
121    pub unsafe fn address(self) -> usize {
122        self.address
123    }
124
125    /// Returns whether this value is null.
126    pub fn is_null(self) -> bool {
127        self.address == 0
128    }
129
130    /// Reconstructs a raw pointer for a backend interop call.
131    ///
132    /// # Safety
133    ///
134    /// The caller must choose the correct pointer type and uphold the lifetime,
135    /// thread-affinity, synchronization, and aliasing requirements of the
136    /// backend API that will receive the pointer.
137    pub unsafe fn as_ptr<T>(self) -> *mut T {
138        self.address as *mut T
139    }
140}
141
142impl fmt::Debug for FrameNativePointer<'_> {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        write!(f, "FrameNativePointer(0x{:x})", self.address)
145    }
146}
147
148/// Borrowed OpenGL texture object name tied to an active texture frame.
149#[derive(Clone, Copy, PartialEq, Eq, Hash)]
150pub struct FrameOpenGLTextureName<'frame> {
151    name: u32,
152    _frame: PhantomData<&'frame ()>,
153    _thread_affine: PhantomData<Rc<()>>,
154}
155
156impl<'frame> FrameOpenGLTextureName<'frame> {
157    fn new(name: u32) -> Self {
158        Self {
159            name,
160            _frame: PhantomData,
161            _thread_affine: PhantomData,
162        }
163    }
164
165    /// Returns whether this OpenGL texture object name is zero.
166    pub fn is_zero(self) -> bool {
167        self.name == 0
168    }
169
170    /// Returns the OpenGL texture object name.
171    ///
172    /// # Safety
173    ///
174    /// The returned integer no longer carries this value's frame lifetime. Use
175    /// it only while the borrowed frame remains open and satisfy OpenGL
176    /// synchronization and context-share-group requirements.
177    pub unsafe fn value(self) -> u32 {
178        self.name
179    }
180}
181
182impl fmt::Debug for FrameOpenGLTextureName<'_> {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        write!(f, "FrameOpenGLTextureName({})", self.name)
185    }
186}
187
188mod query;
189pub use query::{
190    FeatureStateSelector, QueriedFeature, RenderedFeatureQueryOptions, RenderedQueryGeometry,
191    SourceFeatureQueryOptions,
192};
193#[derive(Debug, Clone, PartialEq)]
194#[non_exhaustive]
195pub struct RenderTargetExtent {
196    pub width: u32,
197    pub height: u32,
198    pub scale_factor: f64,
199}
200
201impl RenderTargetExtent {
202    pub fn new(width: u32, height: u32, scale_factor: f64) -> Self {
203        Self {
204            width,
205            height,
206            scale_factor,
207        }
208    }
209
210    pub(crate) fn to_core(&self) -> maplibre_core::render::RenderTargetExtentFields {
211        maplibre_core::render::RenderTargetExtentFields {
212            width: self.width,
213            height: self.height,
214            scale_factor: self.scale_factor,
215        }
216    }
217
218    /// Returns this extent's physical device-pixel size as
219    /// `ceil(logical * scale_factor)` per dimension. Surface and session-owned
220    /// texture targets are sized this way; borrowed texture targets state their
221    /// physical size instead.
222    pub fn physical_size(&self) -> Result<(u32, u32)> {
223        let native = maplibre_core::render::render_target_extent_to_native(self.to_core());
224        let mut width = 0u32;
225        let mut height = 0u32;
226        // SAFETY: native is a fully initialized extent and both out pointers
227        // reference live locals for the duration of the call.
228        maplibre_core::check(unsafe {
229            sys::mln_render_target_extent_physical_size(&native, &mut width, &mut height)
230        })?;
231        Ok((width, height))
232    }
233}
234
235impl Default for RenderTargetExtent {
236    fn default() -> Self {
237        Self::new(256, 256, 1.0)
238    }
239}
240
241#[derive(Debug, Clone, PartialEq)]
242#[non_exhaustive]
243pub struct MetalContextDescriptor {
244    pub device: NativePointer,
245}
246
247impl MetalContextDescriptor {
248    pub fn new(device: NativePointer) -> Self {
249        Self { device }
250    }
251
252    pub(crate) fn to_core(&self) -> maplibre_core::render::MetalContextDescriptorFields {
253        maplibre_core::render::MetalContextDescriptorFields {
254            device: self.device.as_void_ptr(),
255        }
256    }
257}
258
259impl Default for MetalContextDescriptor {
260    fn default() -> Self {
261        Self::new(NativePointer::NULL)
262    }
263}
264
265#[derive(Debug, Clone, PartialEq)]
266#[non_exhaustive]
267pub struct VulkanContextDescriptor {
268    pub instance: NativePointer,
269    pub physical_device: NativePointer,
270    pub device: NativePointer,
271    pub graphics_queue: NativePointer,
272    pub graphics_queue_family_index: u32,
273    pub get_instance_proc_addr: NativePointer,
274    pub get_device_proc_addr: NativePointer,
275}
276
277impl VulkanContextDescriptor {
278    #[allow(clippy::too_many_arguments)]
279    pub fn new(
280        instance: NativePointer,
281        physical_device: NativePointer,
282        device: NativePointer,
283        graphics_queue: NativePointer,
284        graphics_queue_family_index: u32,
285    ) -> Self {
286        Self {
287            instance,
288            physical_device,
289            device,
290            graphics_queue,
291            graphics_queue_family_index,
292            get_instance_proc_addr: NativePointer::NULL,
293            get_device_proc_addr: NativePointer::NULL,
294        }
295    }
296
297    pub(crate) fn to_core(&self) -> maplibre_core::render::VulkanContextDescriptorFields {
298        maplibre_core::render::VulkanContextDescriptorFields {
299            instance: self.instance.as_void_ptr(),
300            physical_device: self.physical_device.as_void_ptr(),
301            device: self.device.as_void_ptr(),
302            graphics_queue: self.graphics_queue.as_void_ptr(),
303            graphics_queue_family_index: self.graphics_queue_family_index,
304            get_instance_proc_addr: self.get_instance_proc_addr.as_void_ptr(),
305            get_device_proc_addr: self.get_device_proc_addr.as_void_ptr(),
306        }
307    }
308}
309
310impl Default for VulkanContextDescriptor {
311    fn default() -> Self {
312        Self::new(
313            NativePointer::NULL,
314            NativePointer::NULL,
315            NativePointer::NULL,
316            NativePointer::NULL,
317            0,
318        )
319    }
320}
321
322/// Browser WebGPU device a session renders with.
323///
324/// A browser host owns its WebGPU objects, so a session borrows these rather
325/// than creating any of them. They must stay valid until the session is
326/// detached or closed.
327#[derive(Debug, Clone, PartialEq)]
328#[non_exhaustive]
329pub struct WebGpuContextDescriptor {
330    /// Optional for texture sessions.
331    pub instance: NativePointer,
332    pub device: NativePointer,
333    /// Optional; null uses the device's default queue. A non-null queue must
334    /// belong to `device`.
335    pub queue: NativePointer,
336}
337
338impl WebGpuContextDescriptor {
339    pub fn new(device: NativePointer) -> Self {
340        Self {
341            instance: NativePointer::NULL,
342            device,
343            queue: NativePointer::NULL,
344        }
345    }
346
347    pub(crate) fn to_core(&self) -> maplibre_core::render::WebGpuContextDescriptorFields {
348        maplibre_core::render::WebGpuContextDescriptorFields {
349            instance: self.instance.as_void_ptr(),
350            device: self.device.as_void_ptr(),
351            queue: self.queue.as_void_ptr(),
352        }
353    }
354}
355
356impl Default for WebGpuContextDescriptor {
357    fn default() -> Self {
358        Self::new(NativePointer::NULL)
359    }
360}
361
362#[derive(Debug, Clone, PartialEq)]
363#[non_exhaustive]
364pub struct WglContextDescriptor {
365    pub device_context: NativePointer,
366    /// Borrowed `HGLRC` whose share group the session context joins. Required
367    /// under shared ownership. A dedicated session joins no share group, so it
368    /// is null there.
369    pub share_context: NativePointer,
370    pub get_proc_address: NativePointer,
371    /// Whether the session shares its thread with host graphics work.
372    pub ownership: OpenGLContextOwnership,
373}
374
375impl WglContextDescriptor {
376    pub fn new(device_context: NativePointer, share_context: NativePointer) -> Self {
377        Self {
378            device_context,
379            share_context,
380            get_proc_address: NativePointer::NULL,
381            ownership: OpenGLContextOwnership::Shared,
382        }
383    }
384
385    pub(crate) fn to_core(&self) -> maplibre_core::render::WglContextDescriptorFields {
386        maplibre_core::render::WglContextDescriptorFields {
387            device_context: self.device_context.as_void_ptr(),
388            share_context: self.share_context.as_void_ptr(),
389            get_proc_address: self.get_proc_address.as_void_ptr(),
390            ownership: self.ownership.as_raw(),
391        }
392    }
393}
394
395impl Default for WglContextDescriptor {
396    fn default() -> Self {
397        Self::new(NativePointer::NULL, NativePointer::NULL)
398    }
399}
400
401#[derive(Debug, Clone, PartialEq)]
402#[non_exhaustive]
403pub struct EglContextDescriptor {
404    pub display: NativePointer,
405    pub config: NativePointer,
406    /// Borrowed `EGLContext` whose share group the session context joins.
407    /// Required under shared ownership, where the session also takes its client
408    /// API from this context. A dedicated session joins no share group, so it
409    /// is null there and names [`client_api`](Self::client_api) instead.
410    pub share_context: NativePointer,
411    /// Client API the session creates its context for. Required under dedicated
412    /// ownership. A shared session queries
413    /// [`share_context`](Self::share_context) for it, so this is ignored there.
414    pub client_api: OpenGLClientApi,
415    pub get_proc_address: NativePointer,
416    /// Whether the session shares its thread with host graphics work.
417    pub ownership: OpenGLContextOwnership,
418}
419
420impl EglContextDescriptor {
421    pub fn new(
422        display: NativePointer,
423        config: NativePointer,
424        share_context: NativePointer,
425    ) -> Self {
426        Self {
427            display,
428            config,
429            share_context,
430            client_api: OpenGLClientApi::Unspecified,
431            get_proc_address: NativePointer::NULL,
432            ownership: OpenGLContextOwnership::Shared,
433        }
434    }
435
436    pub(crate) fn to_core(&self) -> maplibre_core::render::EglContextDescriptorFields {
437        maplibre_core::render::EglContextDescriptorFields {
438            display: self.display.as_void_ptr(),
439            config: self.config.as_void_ptr(),
440            share_context: self.share_context.as_void_ptr(),
441            client_api: self.client_api.as_raw(),
442            get_proc_address: self.get_proc_address.as_void_ptr(),
443            ownership: self.ownership.as_raw(),
444        }
445    }
446}
447
448impl Default for EglContextDescriptor {
449    fn default() -> Self {
450        Self::new(
451            NativePointer::NULL,
452            NativePointer::NULL,
453            NativePointer::NULL,
454        )
455    }
456}
457
458/// Browser WebGL context a session renders into.
459///
460/// The host creates the context and keeps owning it; a session shares it rather
461/// than holding it exclusively. `context` is an
462/// `EMSCRIPTEN_WEBGL_CONTEXT_HANDLE`, which the native library requires to be
463/// positive.
464#[derive(Debug, Clone, PartialEq)]
465#[non_exhaustive]
466pub struct WebGlContextDescriptor {
467    pub context: i32,
468}
469
470impl WebGlContextDescriptor {
471    pub fn new(context: i32) -> Self {
472        Self { context }
473    }
474
475    pub(crate) fn to_core(&self) -> maplibre_core::render::WebGlContextDescriptorFields {
476        maplibre_core::render::WebGlContextDescriptorFields {
477            context: self.context,
478        }
479    }
480}
481
482/// OpenGL platform context a render session draws through.
483///
484/// Each platform descriptor carries its own thread ownership. A browser session
485/// renders through the host's own WebGL context, so it is shared only.
486#[derive(Debug, Clone, PartialEq)]
487#[non_exhaustive]
488pub enum OpenGLContextDescriptor {
489    Wgl(WglContextDescriptor),
490    Egl(EglContextDescriptor),
491    WebGl(WebGlContextDescriptor),
492}
493
494impl OpenGLContextDescriptor {
495    pub(crate) fn to_core(&self) -> maplibre_core::render::OpenGLContextDescriptorFields {
496        match self {
497            Self::Wgl(descriptor) => {
498                maplibre_core::render::OpenGLContextDescriptorFields::Wgl(descriptor.to_core())
499            }
500            Self::Egl(descriptor) => {
501                maplibre_core::render::OpenGLContextDescriptorFields::Egl(descriptor.to_core())
502            }
503            Self::WebGl(descriptor) => {
504                maplibre_core::render::OpenGLContextDescriptorFields::WebGl(descriptor.to_core())
505            }
506        }
507    }
508}
509
510#[derive(Debug, Clone, PartialEq)]
511#[non_exhaustive]
512pub struct MetalSurfaceDescriptor {
513    pub extent: RenderTargetExtent,
514    pub context: MetalContextDescriptor,
515    pub layer: NativePointer,
516}
517
518impl MetalSurfaceDescriptor {
519    pub fn new(
520        extent: RenderTargetExtent,
521        context: MetalContextDescriptor,
522        layer: NativePointer,
523    ) -> Self {
524        Self {
525            extent,
526            context,
527            layer,
528        }
529    }
530
531    pub(crate) fn to_native(&self) -> sys::mln_metal_surface_descriptor {
532        maplibre_core::render::metal_surface_descriptor_to_native(
533            maplibre_core::render::MetalSurfaceDescriptorFields {
534                extent: self.extent.to_core(),
535                context: self.context.to_core(),
536                layer: self.layer.as_void_ptr(),
537            },
538        )
539    }
540}
541
542#[derive(Debug, Clone, PartialEq)]
543#[non_exhaustive]
544pub struct VulkanSurfaceDescriptor {
545    pub extent: RenderTargetExtent,
546    pub context: VulkanContextDescriptor,
547    pub surface: NativePointer,
548}
549
550impl VulkanSurfaceDescriptor {
551    pub fn new(
552        extent: RenderTargetExtent,
553        context: VulkanContextDescriptor,
554        surface: NativePointer,
555    ) -> Self {
556        Self {
557            extent,
558            context,
559            surface,
560        }
561    }
562
563    pub(crate) fn to_native(&self) -> sys::mln_vulkan_surface_descriptor {
564        maplibre_core::render::vulkan_surface_descriptor_to_native(
565            maplibre_core::render::VulkanSurfaceDescriptorFields {
566                extent: self.extent.to_core(),
567                context: self.context.to_core(),
568                surface: self.surface.as_void_ptr(),
569            },
570        )
571    }
572}
573
574/// WebGPU native surface session attachment options.
575///
576/// The surface is borrowed: the host creates it from whatever it presents to,
577/// which in a browser is a canvas, and keeps it alive for the session. The
578/// format is the host's too, because a surface reports what it supports through
579/// its adapter, which this descriptor does not carry.
580#[derive(Debug, Clone, PartialEq)]
581#[non_exhaustive]
582pub struct WebGpuSurfaceDescriptor {
583    pub extent: RenderTargetExtent,
584    pub context: WebGpuContextDescriptor,
585    pub surface: NativePointer,
586    pub format: u32,
587}
588
589impl WebGpuSurfaceDescriptor {
590    pub fn new(
591        extent: RenderTargetExtent,
592        context: WebGpuContextDescriptor,
593        surface: NativePointer,
594        format: u32,
595    ) -> Self {
596        Self {
597            extent,
598            context,
599            surface,
600            format,
601        }
602    }
603
604    pub(crate) fn to_native(&self) -> sys::mln_webgpu_surface_descriptor {
605        maplibre_core::render::webgpu_surface_descriptor_to_native(
606            maplibre_core::render::WebGpuSurfaceDescriptorFields {
607                extent: self.extent.to_core(),
608                context: self.context.to_core(),
609                surface: self.surface.as_void_ptr(),
610                format: self.format,
611            },
612        )
613    }
614}
615
616#[derive(Debug, Clone, PartialEq)]
617#[non_exhaustive]
618pub struct OpenGLSurfaceDescriptor {
619    pub extent: RenderTargetExtent,
620    pub context: OpenGLContextDescriptor,
621    pub surface: NativePointer,
622}
623
624impl OpenGLSurfaceDescriptor {
625    pub fn new(
626        extent: RenderTargetExtent,
627        context: OpenGLContextDescriptor,
628        surface: NativePointer,
629    ) -> Self {
630        Self {
631            extent,
632            context,
633            surface,
634        }
635    }
636
637    pub(crate) fn to_native(&self) -> sys::mln_opengl_surface_descriptor {
638        maplibre_core::render::opengl_surface_descriptor_to_native(
639            maplibre_core::render::OpenGLSurfaceDescriptorFields {
640                extent: self.extent.to_core(),
641                context: self.context.to_core(),
642                surface: self.surface.as_void_ptr(),
643            },
644        )
645    }
646}
647
648#[derive(Debug, Clone, PartialEq)]
649#[non_exhaustive]
650pub struct MetalOwnedTextureDescriptor {
651    pub extent: RenderTargetExtent,
652    pub context: MetalContextDescriptor,
653}
654
655impl MetalOwnedTextureDescriptor {
656    pub fn new(extent: RenderTargetExtent, context: MetalContextDescriptor) -> Self {
657        Self { extent, context }
658    }
659
660    pub(crate) fn to_native(&self) -> sys::mln_metal_owned_texture_descriptor {
661        maplibre_core::render::metal_owned_texture_descriptor_to_native(
662            maplibre_core::render::MetalOwnedTextureDescriptorFields {
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 MetalBorrowedTextureDescriptor {
673    pub extent: RenderTargetExtent,
674    /// Physical texture size in device pixels. The texture is sized by its
675    /// owner, so this is stated rather than derived from `extent`.
676    pub physical_width: u32,
677    pub physical_height: u32,
678    pub texture: NativePointer,
679}
680
681impl MetalBorrowedTextureDescriptor {
682    pub fn new(
683        extent: RenderTargetExtent,
684        physical_width: u32,
685        physical_height: u32,
686        texture: NativePointer,
687    ) -> Self {
688        Self {
689            extent,
690            physical_width,
691            physical_height,
692            texture,
693        }
694    }
695
696    pub(crate) fn to_native(&self) -> sys::mln_metal_borrowed_texture_descriptor {
697        maplibre_core::render::metal_borrowed_texture_descriptor_to_native(
698            maplibre_core::render::MetalBorrowedTextureDescriptorFields {
699                extent: self.extent.to_core(),
700                physical_width: self.physical_width,
701                physical_height: self.physical_height,
702                texture: self.texture.as_void_ptr(),
703            },
704        )
705    }
706}
707
708#[derive(Debug, Clone, PartialEq)]
709#[non_exhaustive]
710pub struct VulkanOwnedTextureDescriptor {
711    pub extent: RenderTargetExtent,
712    pub context: VulkanContextDescriptor,
713}
714
715impl VulkanOwnedTextureDescriptor {
716    pub fn new(extent: RenderTargetExtent, context: VulkanContextDescriptor) -> Self {
717        Self { extent, context }
718    }
719
720    pub(crate) fn to_native(&self) -> sys::mln_vulkan_owned_texture_descriptor {
721        maplibre_core::render::vulkan_owned_texture_descriptor_to_native(
722            maplibre_core::render::VulkanOwnedTextureDescriptorFields {
723                extent: self.extent.to_core(),
724                context: self.context.to_core(),
725            },
726        )
727    }
728}
729
730#[derive(Debug, Clone, PartialEq)]
731#[non_exhaustive]
732pub struct VulkanBorrowedTextureDescriptor {
733    pub extent: RenderTargetExtent,
734    /// Physical image size in device pixels. The image is sized by its owner,
735    /// so this is stated rather than derived from `extent`.
736    pub physical_width: u32,
737    pub physical_height: u32,
738    pub context: VulkanContextDescriptor,
739    pub image: NativePointer,
740    pub image_view: NativePointer,
741    pub format: u32,
742    pub initial_layout: u32,
743    pub final_layout: u32,
744}
745
746impl VulkanBorrowedTextureDescriptor {
747    #[allow(clippy::too_many_arguments)]
748    pub fn new(
749        extent: RenderTargetExtent,
750        physical_width: u32,
751        physical_height: u32,
752        context: VulkanContextDescriptor,
753        image: NativePointer,
754        image_view: NativePointer,
755        format: u32,
756        initial_layout: u32,
757        final_layout: u32,
758    ) -> Self {
759        Self {
760            extent,
761            physical_width,
762            physical_height,
763            context,
764            image,
765            image_view,
766            format,
767            initial_layout,
768            final_layout,
769        }
770    }
771
772    pub(crate) fn to_native(&self) -> sys::mln_vulkan_borrowed_texture_descriptor {
773        maplibre_core::render::vulkan_borrowed_texture_descriptor_to_native(
774            maplibre_core::render::VulkanBorrowedTextureDescriptorFields {
775                extent: self.extent.to_core(),
776                physical_width: self.physical_width,
777                physical_height: self.physical_height,
778                context: self.context.to_core(),
779                image: self.image.as_void_ptr(),
780                image_view: self.image_view.as_void_ptr(),
781                format: self.format,
782                initial_layout: self.initial_layout,
783                final_layout: self.final_layout,
784            },
785        )
786    }
787}
788
789#[derive(Debug, Clone, PartialEq)]
790#[non_exhaustive]
791pub struct WebGpuOwnedTextureDescriptor {
792    pub extent: RenderTargetExtent,
793    pub context: WebGpuContextDescriptor,
794}
795
796impl WebGpuOwnedTextureDescriptor {
797    pub fn new(extent: RenderTargetExtent, context: WebGpuContextDescriptor) -> Self {
798        Self { extent, context }
799    }
800
801    pub(crate) fn to_native(&self) -> sys::mln_webgpu_owned_texture_descriptor {
802        maplibre_core::render::webgpu_owned_texture_descriptor_to_native(
803            maplibre_core::render::WebGpuOwnedTextureDescriptorFields {
804                extent: self.extent.to_core(),
805                context: self.context.to_core(),
806            },
807        )
808    }
809}
810
811#[derive(Debug, Clone, PartialEq)]
812#[non_exhaustive]
813pub struct WebGpuBorrowedTextureDescriptor {
814    pub extent: RenderTargetExtent,
815    /// Physical texture size in device pixels. The texture is sized by its
816    /// owner, so this is stated rather than derived from `extent`.
817    pub physical_width: u32,
818    pub physical_height: u32,
819    pub context: WebGpuContextDescriptor,
820    pub texture: NativePointer,
821    pub texture_view: NativePointer,
822    /// Backend-native `WGPUTextureFormat` value.
823    pub format: u32,
824}
825
826impl WebGpuBorrowedTextureDescriptor {
827    #[allow(clippy::too_many_arguments)]
828    pub fn new(
829        extent: RenderTargetExtent,
830        physical_width: u32,
831        physical_height: u32,
832        context: WebGpuContextDescriptor,
833        texture: NativePointer,
834        texture_view: NativePointer,
835        format: u32,
836    ) -> Self {
837        Self {
838            extent,
839            physical_width,
840            physical_height,
841            context,
842            texture,
843            texture_view,
844            format,
845        }
846    }
847
848    pub(crate) fn to_native(&self) -> sys::mln_webgpu_borrowed_texture_descriptor {
849        maplibre_core::render::webgpu_borrowed_texture_descriptor_to_native(
850            maplibre_core::render::WebGpuBorrowedTextureDescriptorFields {
851                extent: self.extent.to_core(),
852                physical_width: self.physical_width,
853                physical_height: self.physical_height,
854                context: self.context.to_core(),
855                texture: self.texture.as_void_ptr(),
856                texture_view: self.texture_view.as_void_ptr(),
857                format: self.format,
858            },
859        )
860    }
861}
862
863#[derive(Debug, Clone, PartialEq)]
864#[non_exhaustive]
865pub struct OpenGLOwnedTextureDescriptor {
866    pub extent: RenderTargetExtent,
867    pub context: OpenGLContextDescriptor,
868}
869
870impl OpenGLOwnedTextureDescriptor {
871    pub fn new(extent: RenderTargetExtent, context: OpenGLContextDescriptor) -> Self {
872        Self { extent, context }
873    }
874
875    pub(crate) fn to_native(&self) -> sys::mln_opengl_owned_texture_descriptor {
876        maplibre_core::render::opengl_owned_texture_descriptor_to_native(
877            maplibre_core::render::OpenGLOwnedTextureDescriptorFields {
878                extent: self.extent.to_core(),
879                context: self.context.to_core(),
880            },
881        )
882    }
883}
884
885#[derive(Debug, Clone, PartialEq)]
886#[non_exhaustive]
887pub struct OpenGLBorrowedTextureDescriptor {
888    pub extent: RenderTargetExtent,
889    /// Physical texture size in device pixels. The texture is sized by its
890    /// owner, so this is stated rather than derived from `extent`.
891    pub physical_width: u32,
892    pub physical_height: u32,
893    pub context: OpenGLContextDescriptor,
894    pub texture: u32,
895    pub target: u32,
896}
897
898impl OpenGLBorrowedTextureDescriptor {
899    pub fn new(
900        extent: RenderTargetExtent,
901        physical_width: u32,
902        physical_height: u32,
903        context: OpenGLContextDescriptor,
904        texture: u32,
905        target: u32,
906    ) -> Self {
907        Self {
908            extent,
909            physical_width,
910            physical_height,
911            context,
912            texture,
913            target,
914        }
915    }
916
917    pub(crate) fn to_native(&self) -> sys::mln_opengl_borrowed_texture_descriptor {
918        maplibre_core::render::opengl_borrowed_texture_descriptor_to_native(
919            maplibre_core::render::OpenGLBorrowedTextureDescriptorFields {
920                extent: self.extent.to_core(),
921                physical_width: self.physical_width,
922                physical_height: self.physical_height,
923                context: self.context.to_core(),
924                texture: self.texture,
925                target: self.target,
926            },
927        )
928    }
929}
930
931#[derive(Debug)]
932struct RenderSessionState {
933    handle: ThreadAffineNativeHandle<sys::mln_render_session>,
934    detached: Cell<bool>,
935    frame_acquired: Cell<bool>,
936}
937
938impl RenderSessionState {
939    fn new(native: sys::mln_render_session) -> Result<Self> {
940        // SAFETY: native came from a successful render-session attach call and is
941        // paired with the matching render-session destroy function.
942        let handle = unsafe {
943            ThreadAffineNativeHandle::from_handle(
944                native,
945                sys::mln_render_session_destroy,
946                "mln_render_session",
947            )
948        }?;
949        Ok(Self {
950            handle,
951            detached: Cell::new(false),
952            frame_acquired: Cell::new(false),
953        })
954    }
955
956    fn ensure_no_frame_acquired(&self) -> Result<()> {
957        if self.frame_acquired.get() {
958            Err(frame_acquired_error())
959        } else {
960            Ok(())
961        }
962    }
963
964    fn native(&self) -> Result<sys::mln_render_session> {
965        self.handle
966            .live_handle()
967            .ok_or_else(|| closed_handle_error("RenderSessionHandle"))
968    }
969
970    fn close(&self) -> Result<()> {
971        self.handle.close()
972    }
973}
974
975/// Outcome of a successful [`RenderSessionHandle::render_update`] call.
976#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
977#[non_exhaustive]
978pub struct RenderUpdate {
979    /// Which outcome the call reached; see [`RenderResult`] for the wake each
980    /// variant names.
981    pub result: RenderResult,
982    /// Whether the map asked for another frame while it rendered this one, as
983    /// during an ongoing camera transition. Set only when `result` is
984    /// [`RenderResult::Rendered`]; false for every other outcome. This is the
985    /// same signal the render-frame-finished event carries, delivered here
986    /// without the event round trip, so a host can re-arm its frame loop
987    /// before it drains events.
988    pub needs_repaint: bool,
989}
990
991/// Render session handle bound to the thread that attached it.
992///
993/// The session holds no Rust-level retention of its map. Native keeps the map
994/// alive instead: destroying a map fails while a session is attached to it.
995pub struct RenderSessionHandle {
996    inner: Rc<RenderSessionState>,
997}
998
999impl fmt::Debug for RenderSessionHandle {
1000    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1001        f.debug_struct("RenderSessionHandle")
1002            .field("closed", &self.inner.handle.is_closed())
1003            .field("detached", &self.inner.detached.get())
1004            .finish()
1005    }
1006}
1007
1008/// Render session after backend resources have been detached.
1009///
1010/// A detached session holds no reference to its former map, so it stays
1011/// destroyable after that map closes.
1012pub struct DetachedRenderSessionHandle {
1013    inner: Rc<RenderSessionState>,
1014}
1015
1016impl fmt::Debug for DetachedRenderSessionHandle {
1017    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1018        f.debug_struct("DetachedRenderSessionHandle")
1019            .field("closed", &self.inner.handle.is_closed())
1020            .finish()
1021    }
1022}
1023
1024impl DetachedRenderSessionHandle {
1025    /// Explicitly destroys the detached render session.
1026    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1027        if let Err(error) = self.inner.close() {
1028            return Err(HandleOperationError::new(error, self));
1029        }
1030        Ok(())
1031    }
1032}
1033
1034fn frame_acquired_error() -> crate::Error {
1035    crate::Error::new(
1036        crate::ErrorKind::InvalidState,
1037        None,
1038        "render session has an acquired texture frame",
1039    )
1040}
1041
1042/// Copied metadata for an acquired Metal session-owned texture frame.
1043///
1044/// Backend pointers are exposed by [`MetalOwnedTextureFrameHandle`] so their
1045/// lifetime stays tied to the open frame handle.
1046#[derive(Debug, Clone, Copy, PartialEq)]
1047#[non_exhaustive]
1048pub struct MetalOwnedTextureFrame {
1049    pub generation: u64,
1050    pub width: u32,
1051    pub height: u32,
1052    pub scale_factor: f64,
1053    pub frame_id: u64,
1054    pub pixel_format: u64,
1055}
1056
1057impl MetalOwnedTextureFrame {
1058    fn from_native(raw: &sys::mln_metal_owned_texture_frame) -> Self {
1059        Self {
1060            generation: raw.generation,
1061            width: raw.width,
1062            height: raw.height,
1063            scale_factor: raw.scale_factor,
1064            frame_id: raw.frame_id,
1065            pixel_format: raw.pixel_format,
1066        }
1067    }
1068}
1069
1070/// Copied metadata for an acquired Vulkan session-owned texture frame.
1071///
1072/// Backend pointers are exposed by [`VulkanOwnedTextureFrameHandle`] so their
1073/// lifetime stays tied to the open frame handle.
1074#[derive(Debug, Clone, Copy, PartialEq)]
1075#[non_exhaustive]
1076pub struct VulkanOwnedTextureFrame {
1077    pub generation: u64,
1078    pub width: u32,
1079    pub height: u32,
1080    pub scale_factor: f64,
1081    pub frame_id: u64,
1082    pub format: u32,
1083    pub layout: u32,
1084}
1085
1086impl VulkanOwnedTextureFrame {
1087    fn from_native(raw: &sys::mln_vulkan_owned_texture_frame) -> Self {
1088        Self {
1089            generation: raw.generation,
1090            width: raw.width,
1091            height: raw.height,
1092            scale_factor: raw.scale_factor,
1093            frame_id: raw.frame_id,
1094            format: raw.format,
1095            layout: raw.layout,
1096        }
1097    }
1098}
1099
1100/// Copied metadata for an acquired WebGPU session-owned texture frame.
1101///
1102/// Backend pointers are exposed by [`WebGpuOwnedTextureFrameHandle`] so their
1103/// lifetime stays tied to the open frame handle.
1104#[derive(Debug, Clone, Copy, PartialEq)]
1105#[non_exhaustive]
1106pub struct WebGpuOwnedTextureFrame {
1107    pub generation: u64,
1108    pub width: u32,
1109    pub height: u32,
1110    pub scale_factor: f64,
1111    pub frame_id: u64,
1112    pub format: u32,
1113}
1114
1115impl WebGpuOwnedTextureFrame {
1116    fn from_native(raw: &sys::mln_webgpu_owned_texture_frame) -> Self {
1117        Self {
1118            generation: raw.generation,
1119            width: raw.width,
1120            height: raw.height,
1121            scale_factor: raw.scale_factor,
1122            frame_id: raw.frame_id,
1123            format: raw.format,
1124        }
1125    }
1126}
1127
1128/// Copied metadata for an acquired OpenGL session-owned texture frame.
1129///
1130/// The texture object name is exposed by [`OpenGLOwnedTextureFrameHandle`] so
1131/// its lifetime stays tied to the open frame handle.
1132#[derive(Debug, Clone, Copy, PartialEq)]
1133#[non_exhaustive]
1134pub struct OpenGLOwnedTextureFrame {
1135    pub generation: u64,
1136    pub width: u32,
1137    pub height: u32,
1138    pub scale_factor: f64,
1139    pub frame_id: u64,
1140    pub target: u32,
1141    pub internal_format: u32,
1142    pub format: u32,
1143    pub type_: u32,
1144}
1145
1146impl OpenGLOwnedTextureFrame {
1147    fn from_native(raw: &sys::mln_opengl_owned_texture_frame) -> Self {
1148        Self {
1149            generation: raw.generation,
1150            width: raw.width,
1151            height: raw.height,
1152            scale_factor: raw.scale_factor,
1153            frame_id: raw.frame_id,
1154            target: raw.target,
1155            internal_format: raw.internal_format,
1156            format: raw.format,
1157            type_: raw.type_,
1158        }
1159    }
1160}
1161
1162/// RAII guard for an acquired Metal session-owned texture frame.
1163///
1164/// Releasing the guard ends the borrow of the backend Metal texture and device.
1165pub struct MetalOwnedTextureFrameHandle {
1166    session: Rc<RenderSessionState>,
1167    raw: sys::mln_metal_owned_texture_frame,
1168    frame: MetalOwnedTextureFrame,
1169    closed: Cell<bool>,
1170    _thread_affine: PhantomData<Rc<()>>,
1171}
1172
1173impl fmt::Debug for MetalOwnedTextureFrameHandle {
1174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1175        f.debug_struct("MetalOwnedTextureFrameHandle")
1176            .field("closed", &self.closed.get())
1177            .field("frame", &self.frame)
1178            .finish()
1179    }
1180}
1181
1182impl MetalOwnedTextureFrameHandle {
1183    /// Returns copied metadata for this acquired frame.
1184    pub fn frame(&self) -> Result<&MetalOwnedTextureFrame> {
1185        if self.closed.get() {
1186            Err(closed_handle_error("MetalOwnedTextureFrameHandle"))
1187        } else {
1188            Ok(&self.frame)
1189        }
1190    }
1191    /// Returns the borrowed Metal texture pointer for backend interop.
1192    ///
1193    /// # Safety
1194    ///
1195    /// The returned pointer is valid only while this frame handle remains open.
1196    /// The caller must not store or use it after frame release and must satisfy
1197    /// Metal synchronization and thread-affinity requirements.
1198    pub unsafe fn texture(&self) -> Result<FrameNativePointer<'_>> {
1199        if self.closed.get() {
1200            Err(closed_handle_error("MetalOwnedTextureFrameHandle"))
1201        } else {
1202            // SAFETY: The active native frame owns the validity contract for
1203            // this borrowed backend handle until release.
1204            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.texture) })
1205        }
1206    }
1207
1208    /// Returns the borrowed Metal device pointer for backend interop.
1209    ///
1210    /// # Safety
1211    ///
1212    /// The returned pointer has the same lifetime and synchronization
1213    /// requirements as [`MetalOwnedTextureFrameHandle::texture`].
1214    pub unsafe fn device(&self) -> Result<FrameNativePointer<'_>> {
1215        if self.closed.get() {
1216            Err(closed_handle_error("MetalOwnedTextureFrameHandle"))
1217        } else {
1218            // SAFETY: See texture above.
1219            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.device) })
1220        }
1221    }
1222
1223    /// Explicitly releases this frame.
1224    #[allow(clippy::result_large_err)]
1225    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1226        self.close_with_release(sys::mln_metal_owned_texture_release_frame)
1227    }
1228
1229    #[allow(clippy::result_large_err)]
1230    fn close_with_release(
1231        self,
1232        release: unsafe extern "C" fn(
1233            sys::mln_render_session,
1234            *const sys::mln_metal_owned_texture_frame,
1235        ) -> sys::mln_status,
1236    ) -> std::result::Result<(), HandleOperationError<Self>> {
1237        if self.closed.get() {
1238            return Ok(());
1239        }
1240        let session = match self.session.native() {
1241            Ok(session) => session,
1242            Err(error) => return Err(HandleOperationError::new(error, self)),
1243        };
1244        // SAFETY: session is live, and raw is the active frame returned by a
1245        // successful acquire for this session until release succeeds.
1246        if let Err(error) = maplibre_core::check(unsafe { release(session, &self.raw) }) {
1247            return Err(HandleOperationError::new(error, self));
1248        }
1249        self.closed.set(true);
1250        self.session.frame_acquired.set(false);
1251        Ok(())
1252    }
1253}
1254
1255impl Drop for MetalOwnedTextureFrameHandle {
1256    fn drop(&mut self) {
1257        if self.closed.get() {
1258            return;
1259        }
1260        if let Ok(session) = self.session.native() {
1261            // SAFETY: Best-effort release of the active frame. Drop cannot
1262            // report errors and never panics.
1263            let status = unsafe { sys::mln_metal_owned_texture_release_frame(session, &self.raw) };
1264            if status == sys::MLN_STATUS_OK {
1265                self.closed.set(true);
1266                self.session.frame_acquired.set(false);
1267            }
1268        }
1269    }
1270}
1271
1272/// RAII guard for an acquired Vulkan session-owned texture frame.
1273///
1274/// Releasing the guard ends the borrow of the backend Vulkan image, image view,
1275/// and device.
1276pub struct VulkanOwnedTextureFrameHandle {
1277    session: Rc<RenderSessionState>,
1278    raw: sys::mln_vulkan_owned_texture_frame,
1279    frame: VulkanOwnedTextureFrame,
1280    closed: Cell<bool>,
1281    _thread_affine: PhantomData<Rc<()>>,
1282}
1283
1284impl fmt::Debug for VulkanOwnedTextureFrameHandle {
1285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1286        f.debug_struct("VulkanOwnedTextureFrameHandle")
1287            .field("closed", &self.closed.get())
1288            .field("frame", &self.frame)
1289            .finish()
1290    }
1291}
1292
1293impl VulkanOwnedTextureFrameHandle {
1294    /// Returns copied metadata for this acquired frame.
1295    pub fn frame(&self) -> Result<&VulkanOwnedTextureFrame> {
1296        if self.closed.get() {
1297            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1298        } else {
1299            Ok(&self.frame)
1300        }
1301    }
1302    /// Returns the borrowed Vulkan image pointer for backend interop.
1303    ///
1304    /// # Safety
1305    ///
1306    /// The returned pointer is valid only while this frame handle remains open.
1307    /// The caller must not store or use it after frame release and must satisfy
1308    /// Vulkan synchronization and thread-affinity requirements.
1309    pub unsafe fn image(&self) -> Result<FrameNativePointer<'_>> {
1310        if self.closed.get() {
1311            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1312        } else {
1313            // SAFETY: The active native frame owns the validity contract for
1314            // this borrowed backend handle until release.
1315            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.image) })
1316        }
1317    }
1318
1319    /// Returns the borrowed Vulkan image view pointer for backend interop.
1320    ///
1321    /// # Safety
1322    ///
1323    /// The returned pointer has the same lifetime and synchronization
1324    /// requirements as [`VulkanOwnedTextureFrameHandle::image`].
1325    pub unsafe fn image_view(&self) -> Result<FrameNativePointer<'_>> {
1326        if self.closed.get() {
1327            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1328        } else {
1329            // SAFETY: See image above.
1330            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.image_view) })
1331        }
1332    }
1333
1334    /// Returns the borrowed Vulkan device pointer for backend interop.
1335    ///
1336    /// # Safety
1337    ///
1338    /// The returned pointer has the same lifetime and synchronization
1339    /// requirements as [`VulkanOwnedTextureFrameHandle::image`].
1340    pub unsafe fn device(&self) -> Result<FrameNativePointer<'_>> {
1341        if self.closed.get() {
1342            Err(closed_handle_error("VulkanOwnedTextureFrameHandle"))
1343        } else {
1344            // SAFETY: See image above.
1345            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.device) })
1346        }
1347    }
1348
1349    /// Explicitly releases this frame.
1350    #[allow(clippy::result_large_err)]
1351    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1352        if self.closed.get() {
1353            return Ok(());
1354        }
1355        let session = match self.session.native() {
1356            Ok(session) => session,
1357            Err(error) => return Err(HandleOperationError::new(error, self)),
1358        };
1359        // SAFETY: session is live, and raw is the active frame returned by a
1360        // successful acquire for this session until release succeeds.
1361        if let Err(error) = maplibre_core::check(unsafe {
1362            sys::mln_vulkan_owned_texture_release_frame(session, &self.raw)
1363        }) {
1364            return Err(HandleOperationError::new(error, self));
1365        }
1366        self.closed.set(true);
1367        self.session.frame_acquired.set(false);
1368        Ok(())
1369    }
1370}
1371
1372impl Drop for VulkanOwnedTextureFrameHandle {
1373    fn drop(&mut self) {
1374        if self.closed.get() {
1375            return;
1376        }
1377        if let Ok(session) = self.session.native() {
1378            // SAFETY: Best-effort release of the active frame. Drop cannot
1379            // report errors and never panics.
1380            let status = unsafe { sys::mln_vulkan_owned_texture_release_frame(session, &self.raw) };
1381            if status == sys::MLN_STATUS_OK {
1382                self.closed.set(true);
1383                self.session.frame_acquired.set(false);
1384            }
1385        }
1386    }
1387}
1388
1389/// RAII guard for an acquired WebGPU session-owned texture frame.
1390///
1391/// Releasing the guard ends the borrow of the backend WebGPU texture, texture
1392/// view, and device.
1393pub struct WebGpuOwnedTextureFrameHandle {
1394    session: Rc<RenderSessionState>,
1395    raw: sys::mln_webgpu_owned_texture_frame,
1396    frame: WebGpuOwnedTextureFrame,
1397    closed: Cell<bool>,
1398    _thread_affine: PhantomData<Rc<()>>,
1399}
1400
1401impl fmt::Debug for WebGpuOwnedTextureFrameHandle {
1402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1403        f.debug_struct("WebGpuOwnedTextureFrameHandle")
1404            .field("closed", &self.closed.get())
1405            .field("frame", &self.frame)
1406            .finish()
1407    }
1408}
1409
1410impl WebGpuOwnedTextureFrameHandle {
1411    /// Returns copied metadata for this acquired frame.
1412    pub fn frame(&self) -> Result<&WebGpuOwnedTextureFrame> {
1413        if self.closed.get() {
1414            Err(closed_handle_error("WebGpuOwnedTextureFrameHandle"))
1415        } else {
1416            Ok(&self.frame)
1417        }
1418    }
1419
1420    /// Returns the borrowed WebGPU texture pointer for backend interop.
1421    ///
1422    /// # Safety
1423    ///
1424    /// The returned pointer is valid only while this frame handle remains open.
1425    /// The caller must not store or use it after frame release and must satisfy
1426    /// WebGPU synchronization and thread-affinity requirements.
1427    pub unsafe fn texture(&self) -> Result<FrameNativePointer<'_>> {
1428        if self.closed.get() {
1429            Err(closed_handle_error("WebGpuOwnedTextureFrameHandle"))
1430        } else {
1431            // SAFETY: The active native frame owns the validity contract for
1432            // this borrowed backend handle until release.
1433            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.texture) })
1434        }
1435    }
1436
1437    /// Returns the borrowed WebGPU texture view pointer for backend interop.
1438    ///
1439    /// # Safety
1440    ///
1441    /// The returned pointer has the same lifetime and synchronization
1442    /// requirements as [`WebGpuOwnedTextureFrameHandle::texture`].
1443    pub unsafe fn texture_view(&self) -> Result<FrameNativePointer<'_>> {
1444        if self.closed.get() {
1445            Err(closed_handle_error("WebGpuOwnedTextureFrameHandle"))
1446        } else {
1447            // SAFETY: See texture above.
1448            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.texture_view) })
1449        }
1450    }
1451
1452    /// Returns the borrowed WebGPU device pointer for backend interop.
1453    ///
1454    /// # Safety
1455    ///
1456    /// The returned pointer has the same lifetime and synchronization
1457    /// requirements as [`WebGpuOwnedTextureFrameHandle::texture`].
1458    pub unsafe fn device(&self) -> Result<FrameNativePointer<'_>> {
1459        if self.closed.get() {
1460            Err(closed_handle_error("WebGpuOwnedTextureFrameHandle"))
1461        } else {
1462            // SAFETY: See texture above.
1463            Ok(unsafe { FrameNativePointer::from_ptr(self.raw.device) })
1464        }
1465    }
1466
1467    /// Explicitly releases this frame.
1468    #[allow(clippy::result_large_err)]
1469    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1470        if self.closed.get() {
1471            return Ok(());
1472        }
1473        let session = match self.session.native() {
1474            Ok(session) => session,
1475            Err(error) => return Err(HandleOperationError::new(error, self)),
1476        };
1477        // SAFETY: session is live, and raw is the active frame returned by a
1478        // successful acquire for this session until release succeeds.
1479        if let Err(error) = maplibre_core::check(unsafe {
1480            sys::mln_webgpu_owned_texture_release_frame(session, &self.raw)
1481        }) {
1482            return Err(HandleOperationError::new(error, self));
1483        }
1484        self.closed.set(true);
1485        self.session.frame_acquired.set(false);
1486        Ok(())
1487    }
1488}
1489
1490impl Drop for WebGpuOwnedTextureFrameHandle {
1491    fn drop(&mut self) {
1492        if self.closed.get() {
1493            return;
1494        }
1495        if let Ok(session) = self.session.native() {
1496            // SAFETY: Best-effort release of the active frame. Drop cannot
1497            // report errors and never panics.
1498            let status = unsafe { sys::mln_webgpu_owned_texture_release_frame(session, &self.raw) };
1499            if status == sys::MLN_STATUS_OK {
1500                self.closed.set(true);
1501                self.session.frame_acquired.set(false);
1502            }
1503        }
1504    }
1505}
1506
1507/// RAII guard for an acquired OpenGL session-owned texture frame.
1508///
1509/// Releasing the guard ends the borrow of the backend OpenGL texture object.
1510pub struct OpenGLOwnedTextureFrameHandle {
1511    session: Rc<RenderSessionState>,
1512    raw: sys::mln_opengl_owned_texture_frame,
1513    frame: OpenGLOwnedTextureFrame,
1514    closed: Cell<bool>,
1515    _thread_affine: PhantomData<Rc<()>>,
1516}
1517
1518impl fmt::Debug for OpenGLOwnedTextureFrameHandle {
1519    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1520        f.debug_struct("OpenGLOwnedTextureFrameHandle")
1521            .field("closed", &self.closed.get())
1522            .field("frame", &self.frame)
1523            .finish()
1524    }
1525}
1526
1527impl OpenGLOwnedTextureFrameHandle {
1528    /// Returns copied metadata for this acquired frame.
1529    pub fn frame(&self) -> Result<&OpenGLOwnedTextureFrame> {
1530        if self.closed.get() {
1531            Err(closed_handle_error("OpenGLOwnedTextureFrameHandle"))
1532        } else {
1533            Ok(&self.frame)
1534        }
1535    }
1536    /// Returns the borrowed OpenGL texture object name for backend interop.
1537    pub fn texture(&self) -> Result<FrameOpenGLTextureName<'_>> {
1538        if self.closed.get() {
1539            Err(closed_handle_error("OpenGLOwnedTextureFrameHandle"))
1540        } else {
1541            Ok(FrameOpenGLTextureName::new(self.raw.texture))
1542        }
1543    }
1544
1545    /// Explicitly releases this frame.
1546    #[allow(clippy::result_large_err)]
1547    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1548        if self.closed.get() {
1549            return Ok(());
1550        }
1551        let session = match self.session.native() {
1552            Ok(session) => session,
1553            Err(error) => return Err(HandleOperationError::new(error, self)),
1554        };
1555        // SAFETY: session is live, and raw is the active frame returned by a
1556        // successful acquire for this session until release succeeds.
1557        if let Err(error) = maplibre_core::check(unsafe {
1558            sys::mln_opengl_owned_texture_release_frame(session, &self.raw)
1559        }) {
1560            return Err(HandleOperationError::new(error, self));
1561        }
1562        self.closed.set(true);
1563        self.session.frame_acquired.set(false);
1564        Ok(())
1565    }
1566}
1567
1568impl Drop for OpenGLOwnedTextureFrameHandle {
1569    fn drop(&mut self) {
1570        if self.closed.get() {
1571            return;
1572        }
1573        if let Ok(session) = self.session.native() {
1574            // SAFETY: Best-effort release of the active frame. Drop cannot
1575            // report errors and never panics.
1576            let status = unsafe { sys::mln_opengl_owned_texture_release_frame(session, &self.raw) };
1577            if status == sys::MLN_STATUS_OK {
1578                self.closed.set(true);
1579                self.session.frame_acquired.set(false);
1580            }
1581        }
1582    }
1583}
1584
1585impl RenderSessionHandle {
1586    pub(crate) fn attach<F>(map: &MapAttachRef, attach: F) -> Result<Self>
1587    where
1588        F: FnOnce(sys::mln_map, *mut sys::mln_render_session) -> sys::mln_status,
1589    {
1590        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_render_session>::new();
1591        // A close racing this attach makes the handle stale, which the C API
1592        // rejects.
1593        let status = attach(map.map(), out.as_mut_ptr());
1594        maplibre_core::check(status)?;
1595        let ptr = out_handle(out, "mln_render_session")?;
1596        Ok(Self {
1597            inner: Rc::new(RenderSessionState::new(ptr)?),
1598        })
1599    }
1600
1601    /// Explicitly destroys the render session.
1602    ///
1603    /// Native destruction errors are returned. When destruction fails, the
1604    /// underlying native handle remains live so a later `close` can retry.
1605    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
1606        if let Err(error) = self.inner.ensure_no_frame_acquired() {
1607            return Err(HandleOperationError::new(error, self));
1608        }
1609        if let Err(error) = self.inner.close() {
1610            return Err(HandleOperationError::new(error, self));
1611        }
1612        Ok(())
1613    }
1614    /// Resizes this attached render session.
1615    ///
1616    /// Surface and owned-texture sessions resize in place, keeping the
1617    /// renderer and its state. A scale factor change starts a new renderer with
1618    /// renderer-held state empty. Borrowed texture targets report an
1619    /// unsupported-feature error; hand over a new texture with the backend's
1620    /// `set_*_borrowed_texture_target` method instead.
1621    pub fn resize(&self, width: u32, height: u32, scale_factor: f64) -> Result<()> {
1622        self.inner.ensure_no_frame_acquired()?;
1623        let session = self.inner.native()?;
1624        // SAFETY: session is a live render session handle owned by this wrapper.
1625        maplibre_core::check(unsafe {
1626            sys::mln_render_session_resize(session, width, height, scale_factor)
1627        })
1628    }
1629
1630    /// Presents this attached surface session through a new surface, keeping
1631    /// this session's renderer and its state.
1632    ///
1633    /// The descriptor's extent applies as a resize does. A `context.device`
1634    /// that is neither null nor this session's device reports an
1635    /// invalid-argument error and leaves the current surface in place.
1636    pub fn set_metal_surface_target(&self, descriptor: &MetalSurfaceDescriptor) -> Result<()> {
1637        self.set_target(descriptor.to_native(), |session, raw| {
1638            // SAFETY: session is a live render session handle owned by this
1639            // wrapper, and raw is a materialized descriptor valid for this call.
1640            unsafe { sys::mln_metal_surface_set_target(session, raw) }
1641        })
1642    }
1643
1644    /// Presents this attached surface session through a new surface.
1645    ///
1646    /// See [`RenderSessionHandle::set_metal_surface_target`] for what replacing
1647    /// a surface preserves. The outgoing `VkSurfaceKHR` must still be valid:
1648    /// this session holds a swapchain built from it, and Vulkan destroys every
1649    /// swapchain before its surface.
1650    pub fn set_vulkan_surface_target(&self, descriptor: &VulkanSurfaceDescriptor) -> Result<()> {
1651        self.set_target(descriptor.to_native(), |session, raw| {
1652            // SAFETY: session is a live render session handle owned by this
1653            // wrapper, and raw is a materialized descriptor valid for this call.
1654            unsafe { sys::mln_vulkan_surface_set_target(session, raw) }
1655        })
1656    }
1657
1658    /// Presents this attached surface session through a new WebGPU surface.
1659    ///
1660    /// See [`RenderSessionHandle::set_metal_surface_target`] for what replacing
1661    /// a surface preserves. The replacement names the same device and format as
1662    /// the session attached with.
1663    pub fn set_webgpu_surface_target(&self, descriptor: &WebGpuSurfaceDescriptor) -> Result<()> {
1664        self.set_target(descriptor.to_native(), |session, raw| {
1665            // SAFETY: session is a live render session handle owned by this
1666            // wrapper, and raw is a materialized descriptor valid for this call.
1667            unsafe { sys::mln_webgpu_surface_set_target(session, raw) }
1668        })
1669    }
1670
1671    /// Presents this attached surface session through a new surface.
1672    ///
1673    /// See [`RenderSessionHandle::set_metal_surface_target`] for what replacing
1674    /// a surface preserves. The new surface is made current on the next render,
1675    /// so a host may hand over a replacement for one it has already destroyed.
1676    /// A surface accepted here can still prove unusable, which the next
1677    /// `render_update` reports rather than this call.
1678    pub fn set_opengl_surface_target(&self, descriptor: &OpenGLSurfaceDescriptor) -> Result<()> {
1679        self.set_target(descriptor.to_native(), |session, raw| {
1680            // SAFETY: session is a live render session handle owned by this
1681            // wrapper, and raw is a materialized descriptor valid for this call.
1682            unsafe { sys::mln_opengl_surface_set_target(session, raw) }
1683        })
1684    }
1685
1686    /// Renders this attached texture session into a new caller-owned texture,
1687    /// keeping this session's renderer. A scale factor change starts a new
1688    /// renderer, as [`RenderSessionHandle::resize`] does.
1689    ///
1690    /// The replacement must belong to the device this session attached with and
1691    /// carry the pixel format it attached with; otherwise this reports an error
1692    /// and leaves the current texture in place. The caller owns the replacement
1693    /// and keeps it valid until the next replacement, detach, or close. The
1694    /// outgoing texture is neither retained nor read here.
1695    pub fn set_metal_borrowed_texture_target(
1696        &self,
1697        descriptor: &MetalBorrowedTextureDescriptor,
1698    ) -> Result<()> {
1699        self.set_target(descriptor.to_native(), |session, raw| {
1700            // SAFETY: session is a live render session handle owned by this
1701            // wrapper, and raw is a materialized descriptor valid for this call.
1702            unsafe { sys::mln_metal_borrowed_texture_set_target(session, raw) }
1703        })
1704    }
1705
1706    /// Renders this attached texture session into a new caller-owned image.
1707    ///
1708    /// See [`RenderSessionHandle::set_metal_borrowed_texture_target`] for what
1709    /// replacing a target preserves. The replacement carries the format and
1710    /// both layouts this session attached with, since its render pass was built
1711    /// around them.
1712    pub fn set_vulkan_borrowed_texture_target(
1713        &self,
1714        descriptor: &VulkanBorrowedTextureDescriptor,
1715    ) -> Result<()> {
1716        self.set_target(descriptor.to_native(), |session, raw| {
1717            // SAFETY: session is a live render session handle owned by this
1718            // wrapper, and raw is a materialized descriptor valid for this call.
1719            unsafe { sys::mln_vulkan_borrowed_texture_set_target(session, raw) }
1720        })
1721    }
1722
1723    /// Renders this attached texture session into a new caller-owned texture.
1724    ///
1725    /// See [`RenderSessionHandle::set_metal_borrowed_texture_target`] for what
1726    /// replacing a target preserves. The replacement carries the format this
1727    /// session attached with, and belongs to the device it attached with.
1728    pub fn set_webgpu_borrowed_texture_target(
1729        &self,
1730        descriptor: &WebGpuBorrowedTextureDescriptor,
1731    ) -> Result<()> {
1732        self.set_target(descriptor.to_native(), |session, raw| {
1733            // SAFETY: session is a live render session handle owned by this
1734            // wrapper, and raw is a materialized descriptor valid for this call.
1735            unsafe { sys::mln_webgpu_borrowed_texture_set_target(session, raw) }
1736        })
1737    }
1738
1739    /// Renders this attached texture session into a new caller-owned texture.
1740    ///
1741    /// See [`RenderSessionHandle::set_metal_borrowed_texture_target`] for what
1742    /// replacing a target preserves. The replacement belongs to the context
1743    /// this session attached with, or one in its share group, and the host
1744    /// context must be current on this thread.
1745    pub fn set_opengl_borrowed_texture_target(
1746        &self,
1747        descriptor: &OpenGLBorrowedTextureDescriptor,
1748    ) -> Result<()> {
1749        self.set_target(descriptor.to_native(), |session, raw| {
1750            // SAFETY: session is a live render session handle owned by this
1751            // wrapper, and raw is a materialized descriptor valid for this call.
1752            unsafe { sys::mln_opengl_borrowed_texture_set_target(session, raw) }
1753        })
1754    }
1755
1756    /// Shared body for the `set_*_target` methods. The caller-materialized
1757    /// descriptor lives for the whole call, which is all the C API borrows it
1758    /// for.
1759    fn set_target<D>(
1760        &self,
1761        raw: D,
1762        set_target: impl FnOnce(sys::mln_render_session, &D) -> sys::mln_status,
1763    ) -> Result<()> {
1764        self.inner.ensure_no_frame_acquired()?;
1765        let session = self.inner.native()?;
1766        maplibre_core::check(set_target(session, &raw))
1767    }
1768
1769    /// Processes the latest map render update for this render target.
1770    ///
1771    /// The map retains its latest update, so repeated calls re-render it and
1772    /// report [`RenderResult::Rendered`] again. Every other result names the
1773    /// wake to wait for: [`RenderResult::NoUpdate`] and
1774    /// [`RenderResult::SizePending`] resolve on a render-update-available
1775    /// event, and [`RenderResult::TargetNotReady`] resolves when the host
1776    /// changes the render target.
1777    ///
1778    /// The returned [`RenderUpdate::needs_repaint`] reports whether the map
1779    /// asked for another frame while rendering this one, so a frame loop can
1780    /// re-arm without draining events first.
1781    pub fn render_update(&self) -> Result<RenderUpdate> {
1782        self.inner.ensure_no_frame_acquired()?;
1783        let session = self.inner.native()?;
1784        let mut result = sys::MLN_RENDER_RESULT_NO_UPDATE;
1785        let mut needs_repaint = false;
1786        // SAFETY: session is a live render session handle owned by this wrapper,
1787        // and result and needs_repaint point to caller-owned output storage.
1788        maplibre_core::check(unsafe {
1789            sys::mln_render_session_render_update(session, &raw mut result, &raw mut needs_repaint)
1790        })?;
1791        Ok(RenderUpdate {
1792            result: RenderResult::from_raw(result),
1793            needs_repaint,
1794        })
1795    }
1796
1797    /// Detaches backend-bound render resources from the map, consuming this
1798    /// handle and returning a close-only handle.
1799    ///
1800    /// A detached session no longer reaches its map, so the map is free to
1801    /// close and the detached session stays destroyable afterwards.
1802    pub fn detach(
1803        self,
1804    ) -> std::result::Result<DetachedRenderSessionHandle, HandleOperationError<Self>> {
1805        if let Err(error) = self.inner.ensure_no_frame_acquired() {
1806            return Err(HandleOperationError::new(error, self));
1807        }
1808        let session = match self.inner.native() {
1809            Ok(session) => session,
1810            Err(error) => return Err(HandleOperationError::new(error, self)),
1811        };
1812        // SAFETY: session is a live render session handle owned by this wrapper.
1813        if let Err(error) = maplibre_core::check(unsafe { sys::mln_render_session_detach(session) })
1814        {
1815            return Err(HandleOperationError::new(error, self));
1816        }
1817        self.inner.detached.set(true);
1818        Ok(DetachedRenderSessionHandle {
1819            inner: Rc::clone(&self.inner),
1820        })
1821    }
1822
1823    /// Asks the session renderer to release cached resources where possible.
1824    pub fn reduce_memory_use(&self) -> Result<()> {
1825        self.inner.ensure_no_frame_acquired()?;
1826        let session = self.inner.native()?;
1827        // SAFETY: session is a live render session handle owned by this wrapper.
1828        maplibre_core::check(unsafe { sys::mln_render_session_reduce_memory_use(session) })
1829    }
1830
1831    /// Clears renderer data for the session.
1832    pub fn clear_data(&self) -> Result<()> {
1833        self.inner.ensure_no_frame_acquired()?;
1834        let session = self.inner.native()?;
1835        // SAFETY: session is a live render session handle owned by this wrapper.
1836        maplibre_core::check(unsafe { sys::mln_render_session_clear_data(session) })
1837    }
1838
1839    /// Dumps renderer debug logs through MapLibre Native logging.
1840    pub fn dump_debug_logs(&self) -> Result<()> {
1841        self.inner.ensure_no_frame_acquired()?;
1842        let session = self.inner.native()?;
1843        // SAFETY: session is a live render session handle owned by this wrapper.
1844        maplibre_core::check(unsafe { sys::mln_render_session_dump_debug_logs(session) })
1845    }
1846
1847    /// Returns CPU readback metadata for the most recently rendered texture frame.
1848    pub fn texture_image_info(&self) -> Result<TextureImageInfo> {
1849        self.inner.ensure_no_frame_acquired()?;
1850        let session = self.inner.native()?;
1851        // SAFETY: Default constructor takes no arguments and initializes size.
1852        let mut info = unsafe { sys::mln_texture_image_info_default() };
1853        // SAFETY: session is live. Passing a null buffer with zero capacity is
1854        // the documented metadata probe path; out_info points to initialized storage.
1855        maplibre_core::check(unsafe {
1856            sys::mln_texture_read_premultiplied_rgba8(session, std::ptr::null_mut(), 0, &mut info)
1857        })?;
1858        Ok(maplibre_core::values::texture_image_info_from_native(&info))
1859    }
1860
1861    /// Reads the most recently rendered texture frame as premultiplied RGBA8.
1862    pub fn read_premultiplied_rgba8_into(&self, data: &mut [u8]) -> Result<TextureImageInfo> {
1863        self.inner.ensure_no_frame_acquired()?;
1864        let session = self.inner.native()?;
1865        // SAFETY: Default constructor takes no arguments and initializes size.
1866        let mut info = unsafe { sys::mln_texture_image_info_default() };
1867        let data_ptr = if data.is_empty() {
1868            std::ptr::null_mut()
1869        } else {
1870            data.as_mut_ptr()
1871        };
1872        // SAFETY: session is live, data_ptr either points to data's mutable
1873        // storage for data.len() bytes or is null for an empty buffer, and info
1874        // points to initialized writable storage.
1875        maplibre_core::check(unsafe {
1876            sys::mln_texture_read_premultiplied_rgba8(session, data_ptr, data.len(), &mut info)
1877        })?;
1878        // An empty destination reaches native code as the size probe, which
1879        // succeeds without copying, so report it as too small here.
1880        if data.is_empty() && info.byte_length > 0 {
1881            return Err(crate::Error::invalid_argument(format!(
1882                "buffer length 0 is smaller than the required {} bytes",
1883                info.byte_length
1884            )));
1885        }
1886        Ok(maplibre_core::values::texture_image_info_from_native(&info))
1887    }
1888
1889    /// Acquires a borrowed Metal frame from a session-owned texture target.
1890    pub fn acquire_metal_owned_texture_frame(&self) -> Result<MetalOwnedTextureFrameHandle> {
1891        self.inner.ensure_no_frame_acquired()?;
1892        let session = self.inner.native()?;
1893        let mut raw = empty_metal_owned_texture_frame();
1894        // SAFETY: session is live and raw points to initialized writable frame storage.
1895        maplibre_core::check(unsafe {
1896            sys::mln_metal_owned_texture_acquire_frame(session, &mut raw)
1897        })?;
1898        self.inner.frame_acquired.set(true);
1899        Ok(MetalOwnedTextureFrameHandle {
1900            session: Rc::clone(&self.inner),
1901            frame: MetalOwnedTextureFrame::from_native(&raw),
1902            raw,
1903            closed: Cell::new(false),
1904            _thread_affine: PhantomData,
1905        })
1906    }
1907
1908    /// Acquires a borrowed Vulkan frame from a session-owned texture target.
1909    pub fn acquire_vulkan_owned_texture_frame(&self) -> Result<VulkanOwnedTextureFrameHandle> {
1910        self.inner.ensure_no_frame_acquired()?;
1911        let session = self.inner.native()?;
1912        let mut raw = empty_vulkan_owned_texture_frame();
1913        // SAFETY: session is live and raw points to initialized writable frame storage.
1914        maplibre_core::check(unsafe {
1915            sys::mln_vulkan_owned_texture_acquire_frame(session, &mut raw)
1916        })?;
1917        self.inner.frame_acquired.set(true);
1918        Ok(VulkanOwnedTextureFrameHandle {
1919            session: Rc::clone(&self.inner),
1920            frame: VulkanOwnedTextureFrame::from_native(&raw),
1921            raw,
1922            closed: Cell::new(false),
1923            _thread_affine: PhantomData,
1924        })
1925    }
1926
1927    /// Acquires a borrowed WebGPU frame from a session-owned texture target.
1928    pub fn acquire_webgpu_owned_texture_frame(&self) -> Result<WebGpuOwnedTextureFrameHandle> {
1929        self.inner.ensure_no_frame_acquired()?;
1930        let session = self.inner.native()?;
1931        let mut raw = empty_webgpu_owned_texture_frame();
1932        // SAFETY: session is live and raw points to initialized writable frame storage.
1933        maplibre_core::check(unsafe {
1934            sys::mln_webgpu_owned_texture_acquire_frame(session, &mut raw)
1935        })?;
1936        self.inner.frame_acquired.set(true);
1937        Ok(WebGpuOwnedTextureFrameHandle {
1938            session: Rc::clone(&self.inner),
1939            frame: WebGpuOwnedTextureFrame::from_native(&raw),
1940            raw,
1941            closed: Cell::new(false),
1942            _thread_affine: PhantomData,
1943        })
1944    }
1945
1946    /// Acquires a borrowed OpenGL frame from a session-owned texture target.
1947    pub fn acquire_opengl_owned_texture_frame(&self) -> Result<OpenGLOwnedTextureFrameHandle> {
1948        self.inner.ensure_no_frame_acquired()?;
1949        let session = self.inner.native()?;
1950        let mut raw = empty_opengl_owned_texture_frame();
1951        // SAFETY: session is live and raw points to initialized writable frame storage.
1952        maplibre_core::check(unsafe {
1953            sys::mln_opengl_owned_texture_acquire_frame(session, &mut raw)
1954        })?;
1955        self.inner.frame_acquired.set(true);
1956        Ok(OpenGLOwnedTextureFrameHandle {
1957            session: Rc::clone(&self.inner),
1958            frame: OpenGLOwnedTextureFrame::from_native(&raw),
1959            raw,
1960            closed: Cell::new(false),
1961            _thread_affine: PhantomData,
1962        })
1963    }
1964}
1965
1966fn empty_metal_owned_texture_frame() -> sys::mln_metal_owned_texture_frame {
1967    sys::mln_metal_owned_texture_frame {
1968        size: mem::size_of::<sys::mln_metal_owned_texture_frame>() as u32,
1969        generation: 0,
1970        width: 0,
1971        height: 0,
1972        scale_factor: 0.0,
1973        frame_id: 0,
1974        texture: std::ptr::null_mut(),
1975        device: std::ptr::null_mut(),
1976        pixel_format: 0,
1977    }
1978}
1979
1980fn empty_webgpu_owned_texture_frame() -> sys::mln_webgpu_owned_texture_frame {
1981    sys::mln_webgpu_owned_texture_frame {
1982        size: mem::size_of::<sys::mln_webgpu_owned_texture_frame>() as u32,
1983        generation: 0,
1984        width: 0,
1985        height: 0,
1986        scale_factor: 0.0,
1987        frame_id: 0,
1988        texture: std::ptr::null_mut(),
1989        texture_view: std::ptr::null_mut(),
1990        device: std::ptr::null_mut(),
1991        format: 0,
1992    }
1993}
1994
1995fn empty_vulkan_owned_texture_frame() -> sys::mln_vulkan_owned_texture_frame {
1996    sys::mln_vulkan_owned_texture_frame {
1997        size: mem::size_of::<sys::mln_vulkan_owned_texture_frame>() as u32,
1998        generation: 0,
1999        width: 0,
2000        height: 0,
2001        scale_factor: 0.0,
2002        frame_id: 0,
2003        image: std::ptr::null_mut(),
2004        image_view: std::ptr::null_mut(),
2005        device: std::ptr::null_mut(),
2006        format: 0,
2007        layout: 0,
2008    }
2009}
2010
2011fn empty_opengl_owned_texture_frame() -> sys::mln_opengl_owned_texture_frame {
2012    sys::mln_opengl_owned_texture_frame {
2013        size: mem::size_of::<sys::mln_opengl_owned_texture_frame>() as u32,
2014        generation: 0,
2015        width: 0,
2016        height: 0,
2017        scale_factor: 0.0,
2018        frame_id: 0,
2019        texture: 0,
2020        target: 0,
2021        internal_format: 0,
2022        format: 0,
2023        type_: 0,
2024    }
2025}
2026
2027#[cfg(test)]
2028mod tests;