Skip to content

Attach a render target

A render session draws one map into one render target. A map has at most one live render session. Its style, sources, layers, and camera exist independently of that session and remain after the session is released.

The thread that attaches a session becomes its owner thread for the session’s lifetime. Attach on the thread that will draw the frames. Another thread can own the runtime and map.

A native surface draws and presents each frame to a window or view. The host creates the window, graphics context, and surface. Use this target for an on-screen map.

A session-owned texture draws offscreen into a texture that the session allocates. Use it for image generation or when compositing into a scene without a host-owned destination texture. The host can acquire the last frame for GPU sampling or copy it into host memory. Render a static image covers readback and static map mode.

A borrowed texture draws offscreen into a host-owned texture, such as a material in a 3D scene, a compositor frame, or an XR swapchain image. The host controls the format and lifetime. Use this target when the destination already exists.

Each render backend has its own descriptor type and attach function. This snippet uses OpenGL through EGL. The descriptor contains the logical extent, scale factor, graphics context, and presentation surface.

render-target.c
mln_opengl_surface_descriptor descriptor =
mln_opengl_surface_descriptor_default();
descriptor.extent.width = width;
descriptor.extent.height = height;
descriptor.extent.scale_factor = scale_factor;
descriptor.context = *context;
descriptor.surface = egl_surface;
mln_render_session session = MLN_HANDLE_NULL;
const mln_status status =
mln_opengl_surface_attach(map, &descriptor, &session);

An OpenGL surface target also chooses context ownership. The default shares the thread with the host’s own context. A dedicated session instead creates its own context and keeps it current, for a thread that draws one map and nothing else. Concepts explains both modes.

A session-owned target takes an extent and a context. The session allocates the texture in the context’s share group. The host can then sample it.

render-target.c
mln_opengl_owned_texture_descriptor descriptor =
mln_opengl_owned_texture_descriptor_default();
descriptor.extent.width = width;
descriptor.extent.height = height;
descriptor.extent.scale_factor = scale_factor;
descriptor.context = *context;
mln_render_session session = MLN_HANDLE_NULL;
const mln_status status =
mln_opengl_owned_texture_attach(map, &descriptor, &session);

A borrowed target also contains the texture and its physical size in device pixels. The physical size is separate from the logical extent. OpenGL ES cannot verify the texture dimensions. The descriptor values must match the actual texture to prevent clipped or corrupt output.

render-target.c
mln_opengl_borrowed_texture_descriptor descriptor =
mln_opengl_borrowed_texture_descriptor_default();
descriptor.extent.width = logical_width;
descriptor.extent.height = logical_height;
descriptor.extent.scale_factor = scale_factor;
// These must equal the texture's level-0 dimensions; the session cannot
// verify them on ES 3.0.
descriptor.physical_width = (uint32_t)(logical_width * scale_factor);
descriptor.physical_height = (uint32_t)(logical_height * scale_factor);
descriptor.context = *context;
descriptor.texture = texture;
descriptor.target = texture_target; // GL_TEXTURE_2D
mln_render_session session = MLN_HANDLE_NULL;
const mln_status status =
mln_opengl_borrowed_texture_attach(map, &descriptor, &session);

Resize surface and session-owned texture targets in place on the session’s owner thread. This preserves the target and session.

render-target.c
// Surface and session-owned texture targets resize in place. A borrowed
// texture takes its size from the host's texture and reports an unsupported
// status here.
return mln_render_session_resize(session, width, height, scale_factor);

A borrowed texture gets its size from the host allocation. Resizing reports an unsupported status. Allocate a texture at the new size and replace the target.

A host can recreate a surface while the map remains live. Android rotation, Flutter surface lifecycle changes, and reallocating resizes use this path. Replacing the target in place preserves the session’s rendering resources, including loaded tiles and feature state. Use the same operation after allocating a replacement borrowed texture.

render-target.c
mln_opengl_surface_descriptor descriptor =
mln_opengl_surface_descriptor_default();
descriptor.extent.width = width;
descriptor.extent.height = height;
descriptor.extent.scale_factor = scale_factor;
descriptor.context = *context;
descriptor.surface = egl_surface;
return mln_opengl_surface_set_target(session, &descriptor);

Changing the scale factor recreates the session’s rendering resources because its shaders use a fixed pixel ratio. Feature state then starts empty.

Release the session before the map. Destroying a map while a session is attached reports an invalid-state status.

When the session and map have different owner threads, send the release request to the session’s owner thread. Use a bounded wait. Teardown can then recover if that thread has failed.

render-target.c
mln_render_session_destroy(session);
mln_map_destroy(map);