Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ebfce0e
Ship Apple native in MapLibreNative.Maui.Vulkan package (parity with …
acalcutt Jul 7, 2026
2a772f0
ci: build + verify Vulkan iOS XCFramework in the pack-vulkan job
acalcutt Jul 7, 2026
46e7112
native: backend-agnostic frontend C ABI + shared Vulkan frontend base
acalcutt Jul 7, 2026
ae799d3
native(android): real Vulkan frontend rendering into the ANativeWindow
acalcutt Jul 7, 2026
ffeb87e
native(windows): real offscreen Vulkan frontend with CPU read-back
acalcutt Jul 7, 2026
22ee59f
native: include <stddef.h> in mln_cabi.h for size_t
acalcutt Jul 7, 2026
06903f1
native(apple): MoltenVK Vulkan frontend behind the renderer flag (Met…
acalcutt Jul 7, 2026
c731477
ci: build Apple Vulkan (MoltenVK) native and pack it into the Vulkan …
acalcutt Jul 7, 2026
c973760
maui: drive the Vulkan frontend from the shared C# layer
acalcutt Jul 7, 2026
badd2ca
ci: publish a Vulkan Windows sample app in the Windows samples bundle
acalcutt Jul 7, 2026
4c28395
ci(apple-vulkan): drop invalid MoltenVK SDK component
acalcutt Jul 7, 2026
6037bc0
ci(apple-vulkan): disable -Werror for the Apple Vulkan native build
acalcutt Jul 7, 2026
8331f3c
Make WPF + ConsoleExample backend-aware; ship Vulkan variants of all …
acalcutt Jul 8, 2026
87cc65c
fix(windows-vulkan): heap corruption from wrong backend scope on read…
acalcutt Jul 8, 2026
7df839e
diag(windows-vulkan): lifecycle tracing to localise the heap-corrupti…
acalcutt Jul 8, 2026
4f3bc67
Merge remote-tracking branch 'origin/main' into feat/vulkan-frontends
acalcutt Jul 16, 2026
599c8fd
Fix heap corruption: don't free native-owned const char* returns
acalcutt Jul 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 82 additions & 29 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ jobs:
needs: version
uses: ./.github/workflows/native-android-vulkan.yml

build-apple-vulkan:
name: Build Apple Vulkan native
needs: version
uses: ./.github/workflows/native-apple-vulkan.yml

# ── NuGet pack (verify packability, no publish) ───────────────────────────
pack:
name: Pack NuGet
Expand Down Expand Up @@ -163,7 +168,7 @@ jobs:
pack-wpf:
name: Pack WPF NuGet + Windows samples
runs-on: windows-latest
needs: [version, build-windows, pack]
needs: [version, build-windows, build-windows-vulkan, pack]

steps:
- uses: actions/checkout@v4
Expand All @@ -182,6 +187,12 @@ jobs:
pattern: native-windows-*
path: native-artifacts

- name: Download Windows Vulkan native
uses: actions/download-artifact@v4
with:
pattern: native-mln-windows-x64-vulkan
path: vulkan-native

- name: Stage native DLLs
shell: bash
run: |
Expand Down Expand Up @@ -269,6 +280,27 @@ jobs:
-p:UseLocalPackages=true \
-o publish/MauiSample-win-x64

# Vulkan variants: the managed layer is backend-agnostic, so each published app
# runs on Vulkan simply by swapping in the Vulkan-built mln-cabi.dll — at runtime
# the renderers (MapImageView, MlnMapImage, ConsoleExample) detect the "vulkan"
# backend and use the offscreen read-back path.
- name: Create Vulkan sample variants (win-x64)
shell: bash
run: |
DLL=$(find vulkan-native -name mln-cabi.dll | head -1)
PDB=$(find vulkan-native -name mln-cabi.pdb | head -1)
if [ -z "$DLL" ]; then echo "Vulkan mln-cabi.dll not found"; exit 1; fi
echo "Overlaying Vulkan native: $DLL"
for app in ConsoleExample WpfExample MauiSample; do
cp -r "publish/${app}-win-x64" "publish/${app}-vulkan-win-x64"
# The RID publish flattens runtimes/win-x64/native/ to the app root; replace
# both locations if present so whichever the loader picks is the Vulkan build.
cp "$DLL" "publish/${app}-vulkan-win-x64/mln-cabi.dll"
[ -n "$PDB" ] && cp "$PDB" "publish/${app}-vulkan-win-x64/mln-cabi.pdb"
find "publish/${app}-vulkan-win-x64" -path "*runtimes/win-x64/native/mln-cabi.dll" \
-exec cp "$DLL" {} \;
done

- name: Publish MauiSample (win-arm64)
shell: bash
run: |
Expand All @@ -287,7 +319,8 @@ jobs:
@(
'ConsoleExample-win-x64','ConsoleExample-win-arm64',
'WpfExample-win-x64','WpfExample-win-arm64',
'MauiSample-win-x64','MauiSample-win-arm64'
'MauiSample-win-x64','MauiSample-win-arm64',
'ConsoleExample-vulkan-win-x64','WpfExample-vulkan-win-x64','MauiSample-vulkan-win-x64'
) | ForEach-Object {
Compress-Archive -Path "publish/$_/*" -DestinationPath "samples/$_.zip"
}
Expand Down Expand Up @@ -382,7 +415,7 @@ jobs:
pack-vulkan:
name: Pack NuGet (Vulkan)
runs-on: macos-latest
needs: [version, build-windows-vulkan, build-android-vulkan, build-apple]
needs: [version, build-windows-vulkan, build-android-vulkan, build-apple-vulkan]

steps:
- uses: actions/checkout@v4
Expand All @@ -397,48 +430,48 @@ jobs:
9.0.x
10.0.x

- name: Download MLN native artifacts (Windows + Android)
# Vulkan native artifacts for every platform share the native-mln-*-vulkan
# naming (Windows/Android/Apple), so one pattern collects them all.
- name: Download MLN Vulkan native artifacts
uses: actions/download-artifact@v4
with:
pattern: native-mln-*
path: mln-artifacts

- name: Download Apple native artifacts
uses: actions/download-artifact@v4
with:
pattern: native-ios-arm64
path: mln-artifacts

- name: Download Apple iOS simulator native artifacts
uses: actions/download-artifact@v4
with:
pattern: native-iossimulator-arm64
path: mln-artifacts

- name: Download Apple macCatalyst native artifacts
uses: actions/download-artifact@v4
with:
pattern: native-maccatalyst
path: mln-artifacts

- name: Arrange native libs for Vulkan bindings project
run: |
N=bindings/native-vulkan
mkdir -p $N/win-x64 $N/win-arm64
mkdir -p $N/android-arm64 $N/android-x64
mkdir -p $N/ios-arm64 $N/iossimulator-arm64 $N/maccatalyst

find mln-artifacts/native-mln-windows-x64-vulkan -name "mln-cabi.dll" -exec cp {} $N/win-x64/ \; 2>/dev/null || true
find mln-artifacts/native-mln-windows-arm64-vulkan -name "mln-cabi.dll" -exec cp {} $N/win-arm64/ \; 2>/dev/null || true
find mln-artifacts/native-mln-android-arm64-v8a -name "libmln-cabi.so" -exec cp {} $N/android-arm64/ \; 2>/dev/null || true
find mln-artifacts/native-mln-android-x86_64 -name "libmln-cabi.so" -exec cp {} $N/android-x64/ \; 2>/dev/null || true
find mln-artifacts/native-ios-arm64 -name "libmln-cabi.a" -exec cp {} $N/ios-arm64/ \; 2>/dev/null || true
find mln-artifacts/native-iossimulator-arm64 -name "libmln-cabi.a" -exec cp {} $N/iossimulator-arm64/ \; 2>/dev/null || true
find mln-artifacts/native-maccatalyst -name "libmln-cabi.a" -exec cp {} $N/maccatalyst/ \; 2>/dev/null || true
find mln-artifacts/native-mln-windows-x64-vulkan -name "mln-cabi.dll" -exec cp {} $N/win-x64/ \; 2>/dev/null || true
find mln-artifacts/native-mln-windows-arm64-vulkan -name "mln-cabi.dll" -exec cp {} $N/win-arm64/ \; 2>/dev/null || true
find mln-artifacts/native-mln-android-arm64-v8a -name "libmln-cabi.so" -exec cp {} $N/android-arm64/ \; 2>/dev/null || true
find mln-artifacts/native-mln-android-x86_64 -name "libmln-cabi.so" -exec cp {} $N/android-x64/ \; 2>/dev/null || true
find mln-artifacts/native-mln-ios-arm64-vulkan -name "libmln-cabi.a" -exec cp {} $N/ios-arm64/ \; 2>/dev/null || true
find mln-artifacts/native-mln-iossimulator-arm64-vulkan -name "libmln-cabi.a" -exec cp {} $N/iossimulator-arm64/ \; 2>/dev/null || true
find mln-artifacts/native-mln-maccatalyst-vulkan -name "libmln-cabi.a" -exec cp {} $N/maccatalyst/ \; 2>/dev/null || true

echo "Vulkan native libs arranged:"
find $N -type f

- name: Create iOS XCFramework
run: |
N=bindings/native-vulkan
mkdir -p $N/ios
XCFW_ARGS=""
[ -f $N/ios-arm64/libmln-cabi.a ] && XCFW_ARGS="$XCFW_ARGS -library $N/ios-arm64/libmln-cabi.a"
[ -f $N/iossimulator-arm64/libmln-cabi.a ] && XCFW_ARGS="$XCFW_ARGS -library $N/iossimulator-arm64/libmln-cabi.a"
if [ -n "$XCFW_ARGS" ]; then
xcodebuild -create-xcframework \
$XCFW_ARGS \
-output $N/ios/libmln-cabi.xcframework
echo "XCFramework created at $N/ios/libmln-cabi.xcframework"
else
echo "No iOS libraries found; skipping XCFramework creation."
fi

- name: Restore workloads
run: dotnet workload restore maplibre-maui.sln

Expand All @@ -450,6 +483,26 @@ jobs:
-p:_MlnNativeDir=$(pwd)/bindings/native-vulkan/ \
-o artifacts/

- name: Verify Vulkan package ships Apple native (parity with base)
run: |
PKG=$(ls artifacts/MapLibreNative.Maui.Vulkan.*.nupkg | head -1)
echo "Inspecting $PKG"
CONTENTS=$(unzip -Z1 "$PKG")
echo "$CONTENTS"
fail=0
for entry in \
"buildTransitive/MapLibreNative.Maui.Vulkan.targets" \
"buildTransitive/native/maccatalyst/libmln-cabi.a" \
"buildTransitive/native/ios/libmln-cabi.xcframework/Info.plist"; do
if echo "$CONTENTS" | grep -q "$entry"; then
echo "OK $entry"
else
echo "MISSING $entry"
fail=1
fi
done
exit $fail

- name: Upload NuGet Vulkan artifact
uses: actions/upload-artifact@v4
with:
Expand Down
153 changes: 153 additions & 0 deletions .github/workflows/native-apple-vulkan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
name: Native Apple Vulkan (MoltenVK)

on:
workflow_call:

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
build:
name: ${{ matrix.name }}
runs-on: macos-14
strategy:
fail-fast: false
matrix:
include:
- name: iOS arm64 Vulkan
cmake-system: iOS
cmake-sysroot: iphoneos
cmake-archs: arm64
extra-flags: ""
artifact: native-mln-ios-arm64-vulkan

- name: iOS Simulator arm64 Vulkan
cmake-system: iOS
cmake-sysroot: iphonesimulator
cmake-archs: arm64
extra-flags: ""
artifact: native-mln-iossimulator-arm64-vulkan

- name: macCatalyst arm64+x64 Vulkan
cmake-system: Darwin
cmake-sysroot: macosx
cmake-archs: "x86_64;arm64"
extra-flags: -DCMAKE_XCODE_ATTRIBUTE_SUPPORTS_MACCATALYST=YES -DCMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET=15.0 -DMLN_WITH_GLFW=OFF
artifact: native-mln-maccatalyst-vulkan

steps:
- name: Enable Git long paths
run: git config --global core.longpaths true

- uses: actions/checkout@v4
with:
submodules: recursive

# Vulkan headers + VMA are vendored by maplibre-native, and mln-cabi is a static
# archive that uses Vulkan's dynamic dispatch loader — so no Vulkan library is
# linked at build time (MoltenVK links later, in the app). The only build-time
# need is satisfying platform/macos/macos.cmake's find_package(Vulkan REQUIRED)
# on the macCatalyst leg, which the Vulkan-Loader component covers. (MoltenVK is
# not a component this action can build.)
- name: Install Vulkan SDK (headers + loader)
uses: humbletim/setup-vulkan-sdk@v1.2.1
with:
vulkan-query-version: latest
vulkan-components: Vulkan-Headers, Vulkan-Loader
vulkan-use-cache: true

- name: Patch darwin.cmake for macCatalyst
if: ${{ matrix.artifact == 'native-mln-maccatalyst-vulkan' }}
run: |
echo 'target_link_libraries(mbgl-core PRIVATE mbgl-vendor-filesystem)' \
>> dependencies/maplibre-native/platform/darwin/darwin.cmake

- name: Configure CMake
if: ${{ matrix.artifact != 'native-mln-maccatalyst-vulkan' }}
run: |
cmake -B build -G Xcode \
-DCMAKE_SYSTEM_NAME=${{ matrix.cmake-system }} \
-DCMAKE_OSX_ARCHITECTURES="${{ matrix.cmake-archs }}" \
-DCMAKE_OSX_SYSROOT=${{ matrix.cmake-sysroot }} \
-DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 \
${{ matrix.extra-flags }} \
-DMLN_WITH_METAL=OFF \
-DMLN_WITH_VULKAN=ON \
-DMLN_WITH_WERROR=OFF

- name: Configure and build macCatalyst (macabi)
if: ${{ matrix.artifact == 'native-mln-maccatalyst-vulkan' }}
run: |
SYSROOT=$(xcrun --sdk macosx --show-sdk-path)
IOSUPPORT_FWKS="$SYSROOT/System/iOSSupport/System/Library/Frameworks"
IOSUPPORT_INC="$SYSROOT/System/iOSSupport/usr/include"
CLANG=$(xcrun -f clang)
CLANGXX=$(xcrun -f clang++)
for ARCH in x86_64 arm64; do
TRIPLE="${ARCH}-apple-ios15.0-macabi"
EXTRA_FLAGS="-iframework $IOSUPPORT_FWKS -I$IOSUPPORT_INC -Wno-overriding-t-option"
cmake -B "build-${ARCH}" \
-G Ninja \
-DCMAKE_SYSTEM_NAME=Darwin \
-DCMAKE_OSX_SYSROOT=macosx \
-DCMAKE_OSX_ARCHITECTURES="" \
-DCMAKE_OSX_DEPLOYMENT_TARGET="" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER="$CLANG" \
-DCMAKE_CXX_COMPILER="$CLANGXX" \
-DCMAKE_OBJC_COMPILER="$CLANG" \
-DCMAKE_OBJCXX_COMPILER="$CLANGXX" \
"-DCMAKE_C_COMPILER_TARGET=${TRIPLE}" \
"-DCMAKE_CXX_COMPILER_TARGET=${TRIPLE}" \
"-DCMAKE_OBJC_COMPILER_TARGET=${TRIPLE}" \
"-DCMAKE_OBJCXX_COMPILER_TARGET=${TRIPLE}" \
"-DCMAKE_C_FLAGS=${EXTRA_FLAGS}" \
"-DCMAKE_CXX_FLAGS=${EXTRA_FLAGS}" \
"-DCMAKE_OBJC_FLAGS=${EXTRA_FLAGS}" \
"-DCMAKE_OBJCXX_FLAGS=${EXTRA_FLAGS}" \
-DMLN_WITH_GLFW=OFF \
-DMLN_WITH_METAL=OFF \
-DMLN_WITH_VULKAN=ON \
-DMLN_WITH_WERROR=OFF
cmake --build "build-${ARCH}" --target mln-cabi --config Release --parallel
CABI=$(find "build-${ARCH}" -name "libmln-cabi.a" | head -1)
ALL_LIBS=$(find "build-${ARCH}" -name "*.a" -type f | sort)
# shellcheck disable=SC2086
libtool -static -o "${CABI}.merged" ${ALL_LIBS}
mv "${CABI}.merged" "$CABI"
done
X86=$(find build-x86_64 -name "libmln-cabi.a" | head -1)
ARM=$(find build-arm64 -name "libmln-cabi.a" | head -1)
mkdir -p build
lipo -create "$X86" "$ARM" -output build/libmln-cabi.a
echo "Universal macCatalyst binary:"
lipo -info build/libmln-cabi.a

- name: Build mln-cabi
if: ${{ matrix.artifact != 'native-mln-maccatalyst-vulkan' }}
run: cmake --build build --target mln-cabi --config Release

- name: Merge into self-contained archive
if: ${{ matrix.artifact != 'native-mln-maccatalyst-vulkan' }}
run: |
CABI=$(find build -name "libmln-cabi.a" | head -1)
echo "Target archive: $CABI"
ALL_LIBS=$(find build -name "*.a" -type f | sort)
# shellcheck disable=SC2086
libtool -static -o "${CABI}.merged" ${ALL_LIBS}
mv "${CABI}.merged" "$CABI"
echo "Merged object count: $(ar -t "$CABI" | wc -l)"

- name: Locate built library
id: find-lib
run: |
LIB=$(find build -name "libmln-cabi.a" | head -1)
echo "path=$LIB" >> "$GITHUB_OUTPUT"
echo "Found: $LIB"

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: ${{ steps.find-lib.outputs.path }}
retention-days: 7
5 changes: 4 additions & 1 deletion .github/workflows/native-windows-vulkan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,8 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: native-mln-windows-${{ matrix.arch }}-vulkan
path: build/**/mln-cabi.dll
# Include the .pdb so a crash dump from the sample can be symbolicated.
path: |
build/**/mln-cabi.dll
build/**/mln-cabi.pdb
retention-days: 7
Loading
Loading