Skip to content

Feat/vulkan frontends - #21

Open
acalcutt wants to merge 17 commits into
mainfrom
feat/vulkan-frontends
Open

Feat/vulkan frontends#21
acalcutt wants to merge 17 commits into
mainfrom
feat/vulkan-frontends

Conversation

@acalcutt

@acalcutt acalcutt commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

acalcutt and others added 17 commits July 7, 2026 08:36
…base)

The Vulkan bindings package packed only the Windows DLLs, so unlike the base
MapLibreNative.Maui package it delivered no native library on iOS/macCatalyst -
a transitive consumer (via the handlers) linked nothing. Bring it to parity:

- Pack the iOS XCFramework (device + simulator) and macCatalyst .a into
  buildTransitive/native/, and prefer the XCFramework for local iOS builds.
- Add buildTransitive/MapLibreNative.Maui.Vulkan.targets (mirrors the base
  package's targets) to re-add the Apple NativeReferences - with the Metal
  frameworks and -lsqlite3 -lz -lc++ linker flags - for transitive consumers.
- release.yml: build the Vulkan iOS XCFramework in the pack-vulkan job before
  packing, matching the base pack job.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The CI pack-vulkan job (which runs on PRs) arranged the Apple slices but never
built the iOS XCFramework, so with the new packaging the iOS native would be
silently skipped (file-existence gated) - the parity change would go untested.
Add the Create iOS XCFramework step (mirroring the base pack job) and a
verification step that fails the build if the packed Vulkan .nupkg is missing
the buildTransitive targets, the macCatalyst .a, or the iOS XCFramework.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundation for real Vulkan frontends. Adds, without changing existing GL/Metal
behaviour (compiles on all backends):
- mbgl_get_render_backend() -> "opengl"|"vulkan"|"metal" so the shared managed
  layer can pick the right surface handshake at runtime.
- mbgl_frontend_create() (backend-agnostic) with mbgl_frontend_create_gl() kept
  as a thin alias.
- mbgl_frontend_read_pixels() + PlatformFrontend::readPixels() (default no-op)
  for the offscreen (Vulkan Windows) read-back path.
- platform_frontend_vulkan_common.hpp: VulkanFrontendT<Backend> implementing the
  PlatformFrontend/RendererFrontend interface once; each platform supplies only a
  surface-specific mbgl::vulkan::RendererBackend. Not yet wired in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the throwing stub with an ANativeWindow surface backend mirroring
maplibre-native's android_vulkan_renderer_backend (VK_KHR_surface +
VK_KHR_android_surface, swapchain presents into the TextureView surface, matching
today's EGL path), driven through the shared VulkanFrontendT<Backend>.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the throwing stub with an offscreen frontend that reuses
mbgl::vulkan::HeadlessBackend (already compiled into mbgl-core on Windows Vulkan
builds via platform/windows/windows.cmake): renders into a headless color
texture and returns the pixels through mbgl_frontend_read_pixels(), which the
managed layer blits into the WriteableBitmap. No HWND/window surface, so the
airspace-free in-tree model of the WGL path is preserved. CMake adds
platform/default/include for the headless header on Vulkan builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mbgl_frontend_read_pixels uses size_t but the header only pulled in <stdint.h>.
Apple clang doesn't get size_t transitively there, so every target including the
header (incl. the Metal iOS/macOS builds) failed with "unknown type name
'size_t'". Add <stddef.h>.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…al default)

Wraps the existing Metal frontend in #if MLN_RENDER_BACKEND_METAL and adds a
#elif MLN_RENDER_BACKEND_VULKAN path: renders Vulkan into a CAMetalLayer-backed
UIView via VK_EXT_metal_surface (vk::MetalSurfaceCreateInfoEXT +
createMetalSurfaceEXTUnique), driven through the shared VulkanFrontendT, and
returns the view from getNativeView() like the MTKView. Apple keeps defaulting to
Metal; the Vulkan path only compiles under -DMLN_WITH_VULKAN=ON. CMake selects
link libs accordingly (Metal/MetalKit vs QuartzCore/UIKit; MoltenVK is linked by
the consuming app since mln-cabi is a static archive on Apple).

Not yet wired into CI — the native-apple-vulkan build job comes next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…package

Adds native-apple-vulkan.yml (mirrors native-apple.yml: iOS device/simulator +
macCatalyst) building mln-cabi with -DMLN_WITH_METAL=OFF -DMLN_WITH_VULKAN=ON.
Vulkan headers + VMA are vendored by maplibre-native, so only a Vulkan SDK
(MoltenVK) is installed to satisfy find_package(Vulkan) on the Darwin path;
mln-cabi is a static archive on Apple so MoltenVK links in the consuming app.
Artifacts: native-mln-{ios-arm64,iossimulator-arm64,maccatalyst}-vulkan.

Wires build-apple-vulkan into ci.yml + release.yml and repoints pack-vulkan to
consume the Vulkan Apple slices (was reusing the Metal build-apple output), so
the Vulkan package now ships a real Vulkan Apple binary. The base package still
uses the Metal build-apple.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Bindings: add mbgl_get_render_backend, the backend-agnostic mbgl_frontend_create,
  and mbgl_frontend_read_pixels P/Invokes. MbglFrontend now creates via the generic
  entry point, exposes the static RenderBackend (OpenGL/Vulkan/Metal, queried once),
  and adds ReadPixels() for the offscreen path.
- Windows MapImageView: branch on backend. OpenGL keeps the WGL context + glReadPixels;
  Vulkan skips WGL entirely, renders headless, and copies frames back via
  frontend.ReadPixels into the WriteableBitmap. The GL vertical flip (ScaleY = -1) is
  undone for Vulkan since the headless read-back is already top-down.

Android/Apple need no change: they pass the ANativeWindow / consume getNativeView(),
which the generic create routes to the Vulkan frontends. (WPF MlnMapImage still assumes
GL — a Vulkan branch there is a follow-up.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds MauiSample-vulkan-win-x64 to the "Pack WPF NuGet + Windows samples" job
output (ci.yml + release.yml). Because the managed layer is backend-agnostic,
the Vulkan sample is the normal win-x64 MauiSample publish with the Vulkan-built
mln-cabi.dll overlaid — at runtime MapImageView detects the "vulkan" backend and
drives the offscreen Vulkan frontend. Downloads the native-mln-windows-x64-vulkan
artifact (adds build-windows-vulkan to the job's needs) and zips the result
alongside the existing Windows samples.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
humbletim/setup-vulkan-sdk cannot build MoltenVK (it errored with "unknown
component: MoltenVK"). MoltenVK isn't needed at build time anyway: mln-cabi is a
static archive using Vulkan's dynamic dispatch loader with vendored headers, so
no Vulkan library links here (MoltenVK links later in the app). Install only
Vulkan-Headers + Vulkan-Loader, which is enough to satisfy find_package(Vulkan)
on the macCatalyst leg.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
maplibre-native's own mbgl/vulkan/*.cpp has latent -Wshorten-64-to-32
(size_t -> uint32_t) truncations that only surface under Apple's strict flags,
since upstream never compiles the Vulkan backend for Apple (upload_pass.cpp:54
was the first of many). Set -DMLN_WITH_WERROR=OFF so these upstream warnings
don't fail our build; our own code is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Windows samples

- WPF MlnMapImage: branch on MbglFrontend.RenderBackend like MapImageView — OpenGL
  keeps the WGL context + glReadPixels; Vulkan skips WGL, renders headless, and copies
  frames back via frontend.ReadPixels into the WriteableBitmap (flip undone for the
  top-down read-back).
- ConsoleExample: add a RunVulkan() headless render path (no WGL/Win32) selected when
  the loaded native reports the Vulkan backend.
- ci.yml + release.yml: the Windows-samples job now overlays the Vulkan mln-cabi.dll
  onto all three win-x64 apps and zips ConsoleExample-vulkan / WpfExample-vulkan /
  MauiSample-vulkan alongside the existing samples, so the windows-samples bundle has
  a working Vulkan build of each renderer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-back

The offscreen frontend rendered under BackendScope::Implicit and then read pixels
back in a *separate* scope. Implicit never calls HeadlessBackend::activate() (which
creates the backend impl and validates the Vulkan context), and reading in a second
scope tears the just-rendered frame's resources down before readStillImage() copies
getAcquiredImage() — reading/freeing invalid Vulkan memory and corrupting the heap
(0xc0000374, crashing MauiSample/WpfExample on the Vulkan native).

Mirror maplibre-native's HeadlessFrontend: render under the default (Explicit) scope
and read the still image back inside the SAME scope, caching the RGBA frame so
readPixels() just copies it out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on crash

The offscreen Vulkan path still crashes with heap corruption (0xc0000374) and
guessing hasn't pinned it, so instrument each stage of VulkanOffscreenFrontend
(create / ctor / update / render steps / readStillImage / readPixels / dtor) to
%TEMP%\mln_vulkan_diag.log, flushed per line so the last entry survives the crash
and tells us exactly which step dies. Also ship mln-cabi.pdb in the Windows Vulkan
artifact and alongside the overlaid sample DLL so the crash dump can be symbolicated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	handlers/Windows/MapImageView.Windows.cs
#	wpf/MlnMapImage.cs
mbgl_get_render_backend and mbgl_get_last_error return pointers to memory
the native library owns — a static string literal and a thread_local
std::string buffer respectively. Marshalling their return as a `string`
makes the source-generated P/Invoke free that pointer with FreeCoTaskMem,
corrupting the heap (0xC0000374). mbgl_get_render_backend is the first
native call at startup (static RenderBackend initializer), so every build
— GL and Vulkan — crashed on load before showing a window.

Return IntPtr and copy with Marshal.PtrToStringUTF8, which does not free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant