Render to a texture
This guide shows how to render a continuous map into textures for another renderer to draw, such as a UI framework, a game engine, or a compositor. It builds on the frame loop from Render to a window.
To read a map’s pixels on the CPU instead, render offscreen, as in Render images.
Attach a texture ring
Section titled “Attach a texture ring”The session renders into a ring of textures that you create on your GPU device, so your renderer can draw one frame while the session renders the next. Create two textures at the map’s size in device pixels, which is its logical size times its scale factor.
On Vulkan, you can have the library create the device with
mln_vulkan_device_createVulkanDeviceHandle.createVulkanDeviceHandle.CreateNewVulkanDevicemaplibre_native_ffi_vulkan_device_createVulkanDeviceHandle.createVulkanDeviceHandle.createVulkanDeviceHandle.createVulkanDeviceHandle::createVulkanDeviceHandle.createVulkanDeviceHandle.createVulkanDevice.create, and create your textures on the context from
mln_vulkan_device_get_contextVulkanDeviceHandle.getContextVulkanDeviceHandle.GetContextVulkanDeviceHandle.Contextmaplibre_native_ffi_vulkan_device_get_contextVulkanDeviceHandle.getContextVulkanDeviceHandle.getContextVulkanDeviceHandle.get_contextVulkanDeviceHandle::get_contextVulkanDeviceHandle.getContextVulkanDeviceHandle.getContextVulkanDevice.getContext. To share the textures with another
graphics API on the same GPU, name that GPU by its UUID or LUID in the options.
If your renderer submits to the same queue as the session, pass a lock in the
attach options, and hold it around your own submissions.
As with a window, use your graphics thread as the driver for OpenGL and WebGPU textures, and the library thread otherwise. See Service driver work.
const mln_render_session_attach_options options = { .size = sizeof(mln_render_session_attach_options), .driver = MLN_RENDER_DRIVER_CORE_WORKER, .frame_wake = {.callback = on_frame_wake}, // Only if your renderer also submits to the graphics queue. .queue_lock = {.lock = lock_queue, .unlock = unlock_queue},};final options = RenderSessionAttachOptions( driver: RenderDriverKind.coreWorker, frameWake: const Wake(callback: onFrameWake), // Only if your renderer also submits to the graphics queue. The // lock and unlock are native function pointers. queueLock: QueueLock.native(lock: lockQueue, unlock: unlockQueue),);var options = new RenderSessionAttachOptions{ Driver = RenderDriverKind.CoreWorker, FrameWake = new Wake(OnFrameWake), // Only if your renderer also // submits to the graphics queue. QueueLock = new QueueLock(LockQueue, UnlockQueue),};options := maplibre.RenderSessionAttachOptions{ Driver: maplibre.RenderDriverKindCoreWorker, FrameWake: maplibre.Wake{Callback: onFrameWake}, // Only if your renderer also submits to the graphics queue, under // the same mutex. QueueLock: maplibre.QueueLock{ Lock: queue.Lock, Unlock: queue.Unlock, },}var frame_wake = new Wake ();frame_wake.set_callback (on_frame_wake);var options = new RenderSessionAttachOptions () { driver = RenderDriverKind.CORE_WORKER, frame_wake = frame_wake,};// A queue lock, which your renderer needs only if it also// submits to the graphics queue, takes native callbacks that// only C code can set.RenderSessionAttachOptions options = RenderSessionAttachOptions.of(RenderDriverKind.CORE_WORKER) .withFrameWake(new Wake(this::onFrameWake)) // Only if your renderer also submits to the graphics // queue. .withQueueLock( new QueueLock(queueMutex::lock, queueMutex::unlock));val options = RenderSessionAttachOptions( driver = RenderDriverKind.CORE_WORKER, frameWake = Wake(::onFrameWake), // Only if your renderer also submits to the graphics queue. queueLock = QueueLock( lock = { queueMutex.lock() }, unlock = { queueMutex.unlock() }, ), )options = mln.RenderSessionAttachOptions( driver=mln.RenderDriverKind.CORE_WORKER, frame_wake=mln.Wake(on_frame_wake), # Only if your renderer also submits to the graphics queue. # The lock and unlock are native function pointers. queue_lock=mln.QueueLock.native( lock=lock_queue, unlock=unlock_queue ),)let options = RenderSessionAttachOptions { frame_wake: Wake::new(on_frame_wake), // Only if your renderer also submits to the graphics queue. queue_lock: QueueLock::new(lock_queue) .with_unlock(unlock_queue), ..RenderSessionAttachOptions::new( RenderDriverKind::CoreWorker, )};let options = RenderSessionAttachOptions( driver: .coreWorker, frameWake: Wake(callback: onFrameWake), // Only if your renderer also submits to the graphics queue. queueLock: QueueLock(lock: lockQueue, unlock: unlockQueue))const options: RenderSessionAttachOptions = { driver: RenderDriverKind.coreWorker, frameWake: { callback: onFrameWake }, // Only if your renderer also submits to the graphics queue. The // lock and unlock are native function pointers. queueLock: QueueLock.native({ lock: lockQueue, unlock: unlockQueue, }),};const options = maplibre.RenderSessionAttachOptions{ .driver = .core_worker, .frame_wake = .{ .callback = onFrameWake }, // Only if your renderer also submits to the graphics queue. .queue_lock = .{ .lock = lockQueue, .unlock = unlockQueue },};Then attach your textures, with your graphics context, such as your device and queue, and the textures’ size in device pixels. Keep the textures alive until you replace them, or until the session detaches.
// Your VkImage and VkImageView for each texture of the ring.const mln_vulkan_borrowed_texture textures[2] = { {.image = images[0], .image_view = views[0]}, {.image = images[1], .image_view = views[1]},};const mln_vulkan_borrowed_texture_descriptor ring = { .size = sizeof(mln_vulkan_borrowed_texture_descriptor), // The map's size in logical pixels. .logical_size = {.width = 800, .height = 600}, // The size of your textures in device pixels. .physical_width = 1600, .physical_height = 1200, .context = vulkan, .textures = textures, .texture_count = 2, .format = 37, // VK_FORMAT_R8G8B8A8_UNORM .final_layout = 5, // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
mln_map_attach_vulkan_borrowed_texture( map, &ring, &options, &session, &done, NULL);final ring = VulkanBorrowedTextureDescriptor( // The map's size in logical pixels. logicalSize: const LogicalSize(width: 800, height: 600), // The size of your textures in device pixels. physicalWidth: 1600, physicalHeight: 1200, context: vulkan, // Your VkImage and VkImageView for each texture of the ring. textures: [ VulkanBorrowedTexture(image: images[0], imageView: views[0]), VulkanBorrowedTexture(image: images[1], imageView: views[1]), ], format: 37, // VK_FORMAT_R8G8B8A8_UNORM finalLayout: 5, // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
session = map.attachVulkanBorrowedTexture(ring, options);await session.ready;var ring = new VulkanBorrowedTextureDescriptor{ // The map's size in logical pixels. LogicalSize = new LogicalSize(800, 600), // The size of your textures in device pixels. PhysicalWidth = 1600, PhysicalHeight = 1200, Context = vulkan, // Your VkImage and VkImageView // for each texture of the ring. Textures = [ new VulkanBorrowedTexture(images[0], views[0]), new VulkanBorrowedTexture(images[1], views[1]), ], // VK_FORMAT_R8G8B8A8_UNORM Format = 37, // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL FinalLayout = 5,};
session = map.AttachVulkanBorrowedTexture(ring, options);await session.Ready;ring := maplibre.VulkanBorrowedTextureDescriptor{ // The map's size in logical pixels. LogicalSize: maplibre.LogicalSize{Width: 800, Height: 600}, // The size of your textures in device pixels. PhysicalWidth: 1600, PhysicalHeight: 1200, Context: vulkan, // Your VkImage and VkImageView for each texture of the ring. Textures: []maplibre.VulkanBorrowedTexture{ {Image: images[0], ImageView: views[0]}, {Image: images[1], ImageView: views[1]}, }, Format: 37, // VK_FORMAT_R8G8B8A8_UNORM FinalLayout: 5, // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}
borrowed, ready, err := m.AttachVulkanBorrowedTexture(ring, options)if err != nil { return err}session = borrowedif _, err := ready.Await(ctx); err != nil { return err}var ring = new VulkanBorrowedTextureDescriptor () { // The map's size in logical pixels. logical_size = { width: 800, height: 600 }, // The size of your textures in device pixels. physical_width = 1600, physical_height = 1200, context = vulkan, format = 37, // VK_FORMAT_R8G8B8A8_UNORM // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL final_layout = 5,};// Your VkImage and VkImageView for each texture of the ring.ring.set_textures ({ { image: images[0], image_view: views[0] }, { image: images[1], image_view: views[1] },});
session = map.attach_vulkan_borrowed_texture (ring, options);yield session.ready_async ();VulkanBorrowedTextureDescriptor ring = VulkanBorrowedTextureDescriptor.of( // The map's size in logical pixels. LogicalSize.of(800, 600), // The size of your textures in device pixels. 1600, 1200, vulkan, // Your VkImage and VkImageView for each texture of the // ring. List.of( VulkanBorrowedTexture.of(images.get(0), views.get(0)), VulkanBorrowedTexture.of( images.get(1), views.get(1))), 37, // VK_FORMAT_R8G8B8A8_UNORM 5); // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
session = MaplibreNative.attachVulkanBorrowedTexture( map, ring, options);session.getReady().join();val ring = VulkanBorrowedTextureDescriptor( // The map's size in logical pixels. logicalSize = LogicalSize(width = 800u, height = 600u), // The size of your textures in device pixels. physicalWidth = 1600u, physicalHeight = 1200u, context = vulkan, // Your VkImage and VkImageView for each texture of the ring. textures = listOf( VulkanBorrowedTexture( image = images[0], imageView = views[0], ), VulkanBorrowedTexture( image = images[1], imageView = views[1], ), ), format = 37u, // VK_FORMAT_R8G8B8A8_UNORM finalLayout = 5u, // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL )
session = map.attachVulkanBorrowedTexture(ring, options)session.ready.await()ring = mln.VulkanBorrowedTextureDescriptor( # The map's size in logical pixels. logical_size=mln.LogicalSize(width=800, height=600), # The size of your textures in device pixels. physical_width=1600, physical_height=1200, context=vulkan, # Your VkImage and VkImageView for each texture of the ring. textures=[ mln.VulkanBorrowedTexture( image=images[0], image_view=views[0] ), mln.VulkanBorrowedTexture( image=images[1], image_view=views[1] ), ], format=37, # VK_FORMAT_R8G8B8A8_UNORM final_layout=5, # VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
session = map_handle.attach_vulkan_borrowed_texture(ring, options)session.ready.result()let ring = VulkanBorrowedTextureDescriptor { // The map's size in logical pixels. logical_size: LogicalSize::new(800, 600), // The size of your textures in device pixels. physical_width: 1600, physical_height: 1200, context: vulkan, // Your VkImage and VkImageView for each texture of the ring. textures: vec![ VulkanBorrowedTexture::new(images[0], views[0]), VulkanBorrowedTexture::new(images[1], views[1]), ], format: 37, // VK_FORMAT_R8G8B8A8_UNORM final_layout: 5, // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL owner: VulkanImageOwner::GraphicsQueue,};
// SAFETY: the textures and the Vulkan objects outlive the// session.let (session, attached) = unsafe { map.attach_vulkan_borrowed_texture(&ring, &options)?};attached.await?;let ring = VulkanBorrowedTextureDescriptor( // The map's size in logical pixels. logicalSize: LogicalSize(width: 800, height: 600), // The size of your textures in device pixels. physicalWidth: 1600, physicalHeight: 1200, context: vulkan, // Your VkImage and VkImageView for each texture of the ring. textures: [ VulkanBorrowedTexture(image: images[0], imageView: views[0]), VulkanBorrowedTexture(image: images[1], imageView: views[1]), ], format: 37, // VK_FORMAT_R8G8B8A8_UNORM finalLayout: 5 // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
session = try map.attachVulkanBorrowedTexture( descriptor: ring, options: options)try await session.readyconst ring: VulkanBorrowedTextureDescriptor = { // The map's size in logical pixels. logicalSize: { width: 800, height: 600 }, // The size of your textures in device pixels. physicalWidth: 1600, physicalHeight: 1200, context: vulkan, // Your VkImage and VkImageView for each texture of the ring. textures: [ { image: images[0], imageView: views[0] }, { image: images[1], imageView: views[1] }, ], format: 37, // VK_FORMAT_R8G8B8A8_UNORM finalLayout: 5, // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
session = map.attachVulkanBorrowedTexture(ring, options);await session.ready;// Your VkImage and VkImageView for each texture of the ring.const textures = [_]maplibre.VulkanBorrowedTexture{ .{ .image = images[0], .image_view = views[0] }, .{ .image = images[1], .image_view = views[1] },};const ring = maplibre.VulkanBorrowedTextureDescriptor{ // The map's size in logical pixels. .logical_size = .{ .width = 800, .height = 600 }, // The size of your textures in device pixels. .physical_width = 1600, .physical_height = 1200, .context = vulkan, .textures = &textures, .format = 37, // VK_FORMAT_R8G8B8A8_UNORM .final_layout = 5, // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
const borrowed = try map.attachVulkanBorrowedTexture( allocator, ring, options, null,);session = borrowed.session;try borrowed.ready.await(null);Android hardware buffers
Section titled “Android hardware buffers”On Android 8.0 and later, the library can allocate the ring itself, as hardware
buffers that your UI draws, such as through Bitmap.wrapHardwareBuffer. Attach
with mln_map_attach_android_hardware_buffer_ringMapHandle.attachAndroidHardwareBufferRingMapHandle.AttachAndroidHardwareBufferRingmln_map_attach_android_hardware_buffer_ringmln_map_attach_android_hardware_buffer_ringMapHandle.attachAndroidHardwareBufferRingMapHandle.attachAndroidHardwareBufferRingMapHandle.attach_android_hardware_buffer_ringmln_map_attach_android_hardware_buffer_ringmln_map_attach_android_hardware_buffer_ringMapHandle.attachAndroidHardwareBufferRingMap.attachAndroidHardwareBufferRing and the library’s
thread as the driver, and get each frame’s buffer with
mln_acquired_frame_get_android_hardware_bufferAcquiredFrameHandle.withAndroidHardwareBufferAcquiredFrameHandle.WithAndroidHardwareBuffermln_acquired_frame_get_android_hardware_buffermln_acquired_frame_get_android_hardware_bufferAcquiredFrameHandle.withAndroidHardwareBufferAcquiredFrameHandle.withAndroidHardwareBufferAcquiredFrameHandle.android_hardware_buffermln_acquired_frame_get_android_hardware_buffermln_acquired_frame_get_android_hardware_bufferAcquiredFrameHandle.withAndroidHardwareBufferAcquiredFrame.getAndroidHardwareBuffer. To resize the ring, use
mln_render_session_resizeRenderSessionHandle.resizeRenderSessionHandle.ResizeAsyncRenderSessionHandle.Resizemaplibre_native_ffi_render_session_resize_asyncRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSessionHandle::resizeRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSession.resize instead of replacing it. Attach with three
buffers, and keep each frame acquired until the next one replaces it on screen.
Linux dmabufs
Section titled “Linux dmabufs”On Linux and FreeBSD, the library can allocate the ring itself, as dmabufs that
your compositor or renderer imports, such as through
EGL_EXT_image_dma_buf_import or zwp_linux_dmabuf_v1. Attach with
mln_map_attach_dma_buf_ringMapHandle.attachDmaBufRingMapHandle.AttachDmaBufRingMapHandle.AttachDmaBufRingmaplibre_native_ffi_map_attach_dma_buf_ringMapHandle.attachDmaBufRingMapHandle.attachDmaBufRingMapHandle.attach_dma_buf_ringMapHandle::attach_dma_buf_ringMapHandle.attachDmaBufRingMapHandle.attachDmaBufRingMap.attachDmaBufRing and the library’s thread as the driver, and
list the formats and modifiers that your importer takes. Get each frame’s buffer
with mln_acquired_frame_get_dma_bufAcquiredFrameHandle.withDmaBufAcquiredFrameHandle.WithDmaBufAcquiredFrameHandle.WithDmaBufmaplibre_native_ffi_acquired_frame_get_dma_bufAcquiredFrameHandle.withDmaBufAcquiredFrameHandle.withDmaBufAcquiredFrameHandle.dma_bufAcquiredFrameView::get_dma_bufAcquiredFrameHandle.withDmaBufAcquiredFrameHandle.withDmaBufAcquiredFrame.getDmaBuf, and import all of its planes. A
modifier with compression, such as AMD’s DCC, adds a plane to the buffer, and
the planes often share one file descriptor. To resize the ring, use
mln_render_session_resizeRenderSessionHandle.resizeRenderSessionHandle.ResizeAsyncRenderSessionHandle.Resizemaplibre_native_ffi_render_session_resize_asyncRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSessionHandle::resizeRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSession.resize instead of replacing it. Attach with three
buffers, and keep each frame acquired until your importer is done with it.
Windows DXGI shared textures
Section titled “Windows DXGI shared textures”On Windows, a Vulkan build can allocate the ring itself, as D3D11 textures that
your D3D11 or D3D12 renderer opens through their shared handles. Attach with
mln_map_attach_dxgi_shared_texture_ringMapHandle.attachDxgiSharedTextureRingMapHandle.AttachDxgiSharedTextureRingmln_map_attach_dxgi_shared_texture_ringmln_map_attach_dxgi_shared_texture_ringMapHandle.attachDxgiSharedTextureRingMapHandle.attachDxgiSharedTextureRingMapHandle.attach_dxgi_shared_texture_ringmln_map_attach_dxgi_shared_texture_ringmln_map_attach_dxgi_shared_texture_ringMapHandle.attachDxgiSharedTextureRingMap.attachDxgiSharedTextureRing and the library’s thread as the
driver, and render on your renderer’s GPU by passing a Vulkan device that you
created with its adapter’s LUID. Get each frame’s texture with
mln_acquired_frame_get_dxgi_shared_textureAcquiredFrameHandle.withDxgiSharedTextureAcquiredFrameHandle.WithDxgiSharedTexturemln_acquired_frame_get_dxgi_shared_texturemln_acquired_frame_get_dxgi_shared_textureAcquiredFrameHandle.withDxgiSharedTextureAcquiredFrameHandle.withDxgiSharedTextureAcquiredFrameHandle.dxgi_shared_texturemln_acquired_frame_get_dxgi_shared_texturemln_acquired_frame_get_dxgi_shared_textureAcquiredFrameHandle.withDxgiSharedTextureAcquiredFrame.getDxgiSharedTexture. To resize the ring, use
mln_render_session_resizeRenderSessionHandle.resizeRenderSessionHandle.ResizeAsyncRenderSessionHandle.Resizemaplibre_native_ffi_render_session_resize_asyncRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSessionHandle::resizeRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSession.resize instead of replacing it. Attach with three
textures, and keep each frame acquired until your renderer is done with it.
Apple IOSurfaces
Section titled “Apple IOSurfaces”On Apple platforms with Metal, the library can allocate the ring itself, as
IOSurfaces that your UI draws, such as through a CVPixelBuffer from
CVPixelBufferCreateWithIOSurface. Attach with
mln_map_attach_io_surface_ringMapHandle.attachIoSurfaceRingMapHandle.AttachIoSurfaceRingmln_map_attach_io_surface_ringmln_map_attach_io_surface_ringMapHandle.attachIoSurfaceRingMapHandle.attachIoSurfaceRingMapHandle.attach_io_surface_ringmln_map_attach_io_surface_ringmln_map_attach_io_surface_ringMapHandle.attachIoSurfaceRingMap.attachIoSurfaceRing and the library’s thread as the driver,
and get each frame’s surface with mln_acquired_frame_get_io_surfaceAcquiredFrameHandle.withIoSurfaceAcquiredFrameHandle.WithIoSurfacemln_acquired_frame_get_io_surfacemln_acquired_frame_get_io_surfaceAcquiredFrameHandle.withIoSurfaceAcquiredFrameHandle.withIoSurfaceAcquiredFrameHandle.io_surfacemln_acquired_frame_get_io_surfacemln_acquired_frame_get_io_surfaceAcquiredFrameHandle.withIoSurfaceAcquiredFrame.getIoSurface. To
resize the ring, use mln_render_session_resizeRenderSessionHandle.resizeRenderSessionHandle.ResizeAsyncRenderSessionHandle.Resizemaplibre_native_ffi_render_session_resize_asyncRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSessionHandle::resizeRenderSessionHandle.resizeRenderSessionHandle.resizeRenderSession.resize instead of replacing it.
Attach with three surfaces, and keep each frame acquired until your UI is done
with it.
Draw each frame
Section titled “Draw each frame”Run the window’s frame loop. Your renderer shows the frames instead of the session.
When a frame renders, acquire it with mln_render_session_acquire_frameRenderSessionHandle.acquireFrameRenderSessionHandle.AcquireFrameRenderSessionHandle.AcquireFramemaplibre_native_ffi_render_session_acquire_frameRenderSessionHandle.acquireFrameRenderSessionHandle.acquireFrameRenderSessionHandle.acquire_frameRenderSessionHandle::acquire_frameRenderSessionHandle.acquireFrameRenderSessionHandle.acquireFrameRenderSession.acquireFrame
and get its texture for your backend. It takes the newest frame, and drops older
ones that you haven’t acquired. The frame’s slot is the index of its texture in
your ring.
When you’re done with the frame, release it with
mln_acquired_frame_releaseAcquiredFrameHandle.releaseAcquiredFrameHandle.ReleaseAcquiredFrameHandle.Releasemaplibre_native_ffi_acquired_frame_releaseAcquiredFrameHandle.releaseAcquiredFrameHandle.releaseAcquiredFrameHandle.releaseAcquiredFrameHandle::releaseAcquiredFrameHandle.releaseAcquiredFrameHandle.releaseAcquiredFrame.release, so the session can render into that texture
again. Pass MLN_GPU_SYNC_CPU_COMPLETEGpuSyncKind.cpuCompleteGpuSyncKind.CpuCompleteGPUSyncKindCPUCompleteMAPLIBRE_NATIVE_FFI_GPU_SYNC_CPU_COMPLETEGpuSyncKind.CPU_COMPLETEGpuSyncKind.CPU_COMPLETEGpuSyncKind.CPU_COMPLETEGpuSyncKind::CpuCompleteGpuSyncKind.cpuCompleteGpuSyncKind.cpuCompleteGpuSyncKind.cpu_complete if your reads have finished, or,
if they may still run on the GPU, a sync point of a kind that
mln_render_session_capabilities.consumer_sync_kindsRenderSessionCapabilities.consumerSyncKindsRenderSessionCapabilities.ConsumerSyncKindsRenderSessionCapabilities.ConsumerSyncKindsMaplibreNativeFfiRenderSessionCapabilities.consumer_sync_kindsRenderSessionCapabilities.consumerSyncKindsRenderSessionCapabilities.consumerSyncKindsRenderSessionCapabilities.consumer_sync_kindsRenderSessionCapabilities::consumer_sync_kindsRenderSessionCapabilities.consumerSyncKindsRenderSessionCapabilities.consumerSyncKindsRenderSessionCapabilities.consumer_sync_kinds lists.
Keep at most one frame acquired, or the next frame waits for a free texture. Release every acquired frame before you release the session.
mln_acquired_frame frame = MLN_HANDLE_NULL;if ( mln_render_session_acquire_frame(session, &frame, NULL) != MLN_STATUS_OK || frame == MLN_HANDLE_NULL) { return; // The session failed, or no new frame is ready yet.}
mln_vulkan_texture_frame texture = { .size = sizeof(mln_vulkan_texture_frame)};mln_acquired_frame_get_vulkan_texture(frame, &texture, NULL);
// Sample the texture, and wait for those reads to finish.draw_map(texture.image_view);
const mln_gpu_sync done = { .size = sizeof(mln_gpu_sync), .kind = MLN_GPU_SYNC_CPU_COMPLETE};mln_acquired_frame_release(frame, &done, NULL);final frame = session.acquireFrame();if (frame == null) { return; // No new frame yet.}
// Sample the texture, and wait for those reads to finish.frame.withVulkanTexture((texture) => drawMap(texture.imageView));
frame.release(const GpuSync(kind: GpuSyncKind.cpuComplete));var frame = session.AcquireFrame();if (frame is null) return; // No new frame yet.
// Sample the texture, and wait for those reads to finish.frame.WithVulkanTexture(texture => DrawMap(texture.ImageView));
frame.Release(new GpuSync { Kind = GpuSyncKind.CpuComplete });frame, err := session.AcquireFrame()if err != nil { return err}if frame == nil { return nil // No new frame yet.}
// Sample the texture, and wait for those reads to finish.err = frame.WithVulkanTexture( func(texture maplibre.VulkanTextureFrameView) error { imageView, err := texture.ImageView() if err != nil { return err } drawMap(imageView) return nil },)
return errors.Join(err, frame.Release(maplibre.GPUSync{Kind: maplibre.GPUSyncKindCPUComplete}))var frame = session.acquire_frame ();if (frame == null) { return; // No new frame yet.}
// Sample the texture, and wait for those reads to finish.var texture = frame.get_vulkan_texture ();draw_map (texture.get_image_view ());
frame.release ({ kind: GpuSyncKind.CPU_COMPLETE });AcquiredFrameHandle frame = session.acquireFrame();if (frame == null) return; // No new frame yet.
// Sample the texture, and wait for those reads to finish.MaplibreNative.withVulkanTexture( frame, texture -> { drawMap(texture.getImageView()); return null; });
frame.release(GpuSync.of().withKind(GpuSyncKind.CPU_COMPLETE));val frame = session.acquireFrame() ?: return // No new frame yet.
// Sample the texture, and wait for those reads to finish.frame.withVulkanTexture { texture -> drawMap(texture.imageView) }
frame.release(GpuSync(kind = GpuSyncKind.CPU_COMPLETE))frame = session.acquire_frame()if frame is None: return # No new frame yet.
# Sample the texture, and wait for those reads to finish.with frame.vulkan_texture() as texture: draw_map(texture.image_view)
frame.release(mln.GpuSync(kind=mln.GpuSyncKind.CPU_COMPLETE))let Some(frame) = session.acquire_frame()? else { return Ok(()); // No new frame yet.};
// Sample the texture, and wait for those reads to finish.draw_map(frame.view()?.get_vulkan_texture()?.image_view);
let done = GpuSync { kind: GpuSyncKind::CpuComplete, ..GpuSync::default()};// SAFETY: a CPU-complete sync names no graphics object.unsafe { frame.release(&done) }?;guard let frame = try session.acquireFrame() else { return // No new frame yet.}
// Sample the texture, and wait for those reads to finish.try frame .withVulkanTexture { texture in try drawMap(texture.imageView) }
try frame.release(consumerCompletion: GpuSync(kind: .cpuComplete))const frame = session.acquireFrame();if (frame === undefined) { return; // No new frame yet.}
// Sample the texture, and wait for those reads to finish.frame.withVulkanTexture((texture) => drawMap(texture.imageView));
frame.release({ kind: GpuSyncKind.cpuComplete, handle: 0n, value: 0n,});// Return when there's no new frame yet.var frame = (try session.acquireFrame(null)) orelse return;
// Sample the texture, and wait for those reads to finish.try frame.getVulkanTexture({}, drawMap, null);
try frame.release(.{ .kind = .cpu_complete }, null);Resize the textures
Section titled “Resize the textures”The session renders at the size of your textures, so to resize, give it new ones. First, release every acquired frame. Then create as many textures as before at the new size, on the same device and in the same format, and set them as the session’s target. Once the replacement completes, destroy the old textures.
// Your new textures, at the new size.const mln_vulkan_borrowed_texture new_textures[2] = { {.image = new_images[0], .image_view = new_views[0]}, {.image = new_images[1], .image_view = new_views[1]},};const mln_vulkan_borrowed_texture_replacement resized = { .size = sizeof(mln_vulkan_borrowed_texture_replacement), .logical_size = {.width = 1024, .height = 768}, .physical_width = 2048, .physical_height = 1536, .textures = new_textures, .texture_count = 2,};
mln_render_session_set_vulkan_borrowed_texture_target( session, &resized, &done, NULL);
// vkDestroyImageView and vkDestroyImagedestroy_textures(images, views);final resized = VulkanBorrowedTextureReplacement( logicalSize: const LogicalSize(width: 1024, height: 768), physicalWidth: 2048, physicalHeight: 1536, // Your new textures, at the new size. textures: [ VulkanBorrowedTexture( image: newImages[0], imageView: newViews[0], ), VulkanBorrowedTexture( image: newImages[1], imageView: newViews[1], ), ],);
await session.setVulkanBorrowedTextureTarget(resized);// vkDestroyImageView and vkDestroyImagedestroyTextures(images, views);var resized = new VulkanBorrowedTextureReplacement{ LogicalSize = new LogicalSize(1024, 768), PhysicalWidth = 2048, PhysicalHeight = 1536, // Your new textures, at the new size. Textures = [ new VulkanBorrowedTexture(newImages[0], newViews[0]), new VulkanBorrowedTexture(newImages[1], newViews[1]), ],};
await session.SetVulkanBorrowedTextureTargetAsync(resized);// vkDestroyImageView and vkDestroyImageDestroyTextures(images, views);resized := maplibre.VulkanBorrowedTextureReplacement{ LogicalSize: maplibre.LogicalSize{Width: 1024, Height: 768}, PhysicalWidth: 2048, PhysicalHeight: 1536, // Your new textures, at the new size. Textures: []maplibre.VulkanBorrowedTexture{ {Image: newImages[0], ImageView: newViews[0]}, {Image: newImages[1], ImageView: newViews[1]}, },}
retargeted, err := session.SetVulkanBorrowedTextureTarget(resized)if err != nil { return err}if _, err := retargeted.Await(ctx); err != nil { return err}// vkDestroyImageView and vkDestroyImagedestroyTextures(images, views)var resized = new VulkanBorrowedTextureReplacement () { logical_size = { width: 1024, height: 768 }, physical_width = 2048, physical_height = 1536,};// Your new textures, at the new size.resized.set_textures ({ { image: new_images[0], image_view: new_views[0] }, { image: new_images[1], image_view: new_views[1] },});
yield session.set_vulkan_borrowed_texture_target_async (resized);// vkDestroyImageView and vkDestroyImagedestroy_textures (images, views);VulkanBorrowedTextureReplacement resized = VulkanBorrowedTextureReplacement.of( LogicalSize.of(1024, 768), 2048, 1536, // Your new textures, at the new size. List.of( VulkanBorrowedTexture.of( newImages.get(0), newViews.get(0)), VulkanBorrowedTexture.of( newImages.get(1), newViews.get(1))));
MaplibreNative.setVulkanBorrowedTextureTarget(session, resized) .join();// vkDestroyImageView and vkDestroyImagedestroyTextures(images, views);val resized = VulkanBorrowedTextureReplacement( logicalSize = LogicalSize(width = 1024u, height = 768u), physicalWidth = 2048u, physicalHeight = 1536u, // Your new textures, at the new size. textures = listOf( VulkanBorrowedTexture( image = newImages[0], imageView = newViews[0], ), VulkanBorrowedTexture( image = newImages[1], imageView = newViews[1], ), ), )
session.setVulkanBorrowedTextureTarget(resized).await()// vkDestroyImageView and vkDestroyImagedestroyTextures(images, views)resized = mln.VulkanBorrowedTextureReplacement( logical_size=mln.LogicalSize(width=1024, height=768), physical_width=2048, physical_height=1536, # Your new textures, at the new size. textures=[ mln.VulkanBorrowedTexture( image=new_images[0], image_view=new_views[0] ), mln.VulkanBorrowedTexture( image=new_images[1], image_view=new_views[1] ), ],)
session.set_vulkan_borrowed_texture_target(resized).result()# vkDestroyImageView and vkDestroyImagedestroy_textures(images, views)let resized = VulkanBorrowedTextureReplacement { logical_size: LogicalSize::new(1024, 768), physical_width: 2048, physical_height: 1536, // Your new textures, at the new size. textures: vec![ VulkanBorrowedTexture::new(new_images[0], new_views[0]), VulkanBorrowedTexture::new(new_images[1], new_views[1]), ],};
// SAFETY: the new textures outlive the session.unsafe { session.set_vulkan_borrowed_texture_target(&resized)? } .await?;// vkDestroyImageView and vkDestroyImagedestroy_textures(images, views);let resized = VulkanBorrowedTextureReplacement( logicalSize: LogicalSize(width: 1024, height: 768), physicalWidth: 2048, physicalHeight: 1536, // Your new textures, at the new size. textures: [ VulkanBorrowedTexture( image: newImages[0], imageView: newViews[0] ), VulkanBorrowedTexture( image: newImages[1], imageView: newViews[1] ), ])
try await session.setVulkanBorrowedTextureTarget( replacement: resized)// vkDestroyImageView and vkDestroyImagedestroyTextures(images, views)const resized: VulkanBorrowedTextureReplacement = { logicalSize: { width: 1024, height: 768 }, physicalWidth: 2048, physicalHeight: 1536, // Your new textures, at the new size. textures: [ { image: newImages[0], imageView: newViews[0] }, { image: newImages[1], imageView: newViews[1] }, ],};
await session.setVulkanBorrowedTextureTarget(resized);// vkDestroyImageView and vkDestroyImagedestroyTextures(images, views);// Your new textures, at the new size.const new_textures = [_]maplibre.VulkanBorrowedTexture{ .{ .image = new_images[0], .image_view = new_views[0] }, .{ .image = new_images[1], .image_view = new_views[1] },};const resized = maplibre.VulkanBorrowedTextureReplacement{ .logical_size = .{ .width = 1024, .height = 768 }, .physical_width = 2048, .physical_height = 1536, .textures = &new_textures,};
const replaced = try session.setVulkanBorrowedTextureTarget( allocator, resized, null,);try replaced.await(null);
// vkDestroyImageView and vkDestroyImagedestroyTextures(images, views);