Skip to content

feat(bgfx): compile bx/bgfx/bimg for Android via NDK sysroot (phase 1, #300) - #304

Merged
apotema merged 2 commits into
mainfrom
feat/bgfx-android-p1-ndk-sysroot
Jun 13, 2026
Merged

feat(bgfx): compile bx/bgfx/bimg for Android via NDK sysroot (phase 1, #300)#304
apotema merged 2 commits into
mainfrom
feat/bgfx-android-p1-ndk-sysroot

Conversation

@apotema

@apotema apotema commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Phase 1 of the bgfx-on-Android bring-up. Closes #300. Part of #296.

What

Make bgfx's C++ (bx, bgfx, bimg) compile clean for aarch64-linux-android against the local NDK. Build-system only — no device, no runtime. The earlier cross-compile spike showed zbgfx does compile bgfx for the Android target; it only failed on missing NDK sysroot headers (unknown type name 'ldiv_t') because Zig's bundled libc++ reaches into the system C <stdlib.h>, which lives in the NDK sysroot.

Change (only backends/bgfx/build.zig, +135)

  • Sysroot detectiongetAndroidNdkSysroot(b) + ndkHostTag(), copied from the sokol-Android plumbing in src/templates/build_zig.txt (reads ANDROID_NDK_HOME, then ANDROID_HOME/ndk/<latest>; Zig-0.16 b.graph.environ_map / std.Io.Dir APIs).
  • Android gatingis_android = os.tag == .linux and (abi == .android or .androideabi) (isAndroid() doesn't exist). Every Android-specific addition is behind it; desktop/cross builds fall through unchanged.
  • Per-module sysroot wiring (applyNdkSysroot) — addSystemIncludePath(sysroot/usr/include) + .../<triple> (the ldiv_t fix), addLibraryPath(.../usr/lib/<triple>/28), addCMacro("__ANDROID_API__", "28"), pic = true. zbgfx builds bx/bimg/bgfx as three separate libs but only exposes bgfx; include paths don't cross linkLibrary, so it walks bgfx_artifact.root_module.link_objects (the .other_step entries = bx, bimg) and applies the sysroot to each.
  • API 28 (not 21) — once ldiv_t was fixed, bx's file.cpp references stdout/stderr, which Bionic only exposes as real symbols from API 23 (below that they alias __sF[], __REMOVED_IN(23)). 28 matches the toolkit's default min_sdk_version.
  • zglfw isolation — for an Android target the build installs ONLY the bgfx artifact and returns before constructing the zglfw-dependent input/window modules + glfw artifact (zglfw is desktop-only → phase 2, bgfx Android (phase 2): native ANativeWindow surface (replace zglfw) #301). Desktop path is byte-for-byte unchanged.

Verification (independent of the author)

  • Android (the goal): ANDROID_NDK_HOME=.../27.0.12077973 zig build -Dtarget=aarch64-linux-android → exit 0, produces zig-out/lib/libbgfx.a. bx/bimg/bgfx all compile clean (bimg went 424 → 0 errors). Objects are real aarch64 ELF (-target aarch64-linux-android; Zig can't emit host objects for that target). -Dtarget=x86_64-linux-android also compiles clean.
  • Desktop unchanged: zig build test exit 0; cd example && zig build links the macOS demo. CI's bgfx-build (macOS) job exercises both.

Scope / next

This proves the renderer C++ compiles for Android. The full backend still won't link a .so (zglfw → phase 2 #301; the GLESv3/EGL/android/log link step → later, mirroring sokol's android_link). Out of scope here by design.

Phase 1 of bgfx-on-Android bring-up. When the target ABI is Android,
feed the NDK sysroot system-include paths, arch/API library path, and
__ANDROID_API__ to all three zbgfx C/C++ libs (bx, bimg, bgfx) so they
find Bionic's <stdlib.h> etc. — without this, Zig's bundled libc++
headers fail with 'unknown type name ldiv_t'.

- Detect the sysroot the same way sokol-Android does
  (getAndroidNdkSysroot/ndkHostTag, reading ANDROID_NDK_HOME /
  ANDROID_HOME), copied into this build.zig.
- zbgfx builds bx/bimg/bgfx as three separate *Compile artifacts but
  only exposes 'bgfx'; reach bx/bimg by walking bgfx's link_objects
  (other_step) and apply the sysroot to each, since include paths don't
  propagate across linkLibrary.
- Use __ANDROID_API__=28 (toolkit default min_sdk): bx's file.cpp uses
  stdout/stderr, real Bionic symbols only from API 23 (below that they
  alias __sF[], marked __REMOVED_IN(23)).
- Isolate from desktop-only zglfw (phase 2, #301): for an Android target
  install ONLY the bgfx artifact and skip the zglfw-dependent
  modules/glfw artifact. Desktop builds unchanged.

Verified: 'zig build -Dtarget=aarch64-linux-android' and
'-Dtarget=x86_64-linux-android' compile bx/bgfx/bimg clean (real
aarch64 ELF objects); host 'zig build test' and example 'zig build'
on macOS still pass.
@cursor

cursor Bot commented Jun 12, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Build-script-only but adds new cross-compile paths and early-exit behavior for Android; mistakes could break desktop builds or Android CI while runtime/device behavior is still out of scope.

Overview
Android cross-build support is added in backends/bgfx/build.zig so zbgfx’s bx / bimg / bgfx C++ can compile for Linux-with-Android ABI targets. Desktop and non-Android cross builds are unchanged.

When the target is Android, the build resolves the NDK sysroot (ANDROID_NDK_HOME or latest under ANDROID_HOME/ndk), picks the arch triple and API 28, and applies applyNdkSysroot (Bionic include paths, API lib path, __ANDROID_API__, PIC) to the bgfx artifact and linked bx/bimg compile steps so types like ldiv_t resolve. getAndroidNdkSysroot / ndkHostTag follow the same pattern as sokol in src/templates/build_zig.txt.

For Android phase 1, the build installs only libbgfx.a and returns before input/window modules and the glfw artifact (zglfw is desktop-only); full Android linking and windowing stay for later work.

Reviewed by Cursor Bugbot for commit 47f9ad7. Bugbot is set up for automated code reviews on this repo. Configure here.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements phase-1 of Android support for the bgfx backend in build.zig by resolving and applying the Android NDK sysroot paths to the bgfx C/C++ compilation modules. Feedback on these changes highlights a compile error in the tagged union comparison of LinkObject (which should use a switch statement instead of ==) and recommends mapping the .thumb architecture to the 32-bit ARM triple to prevent unexpected compile-time panics.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread backends/bgfx/build.zig
Comment on lines +55 to +59
for (bgfx_artifact.root_module.link_objects.items) |lo| {
if (lo == .other_step) {
applyNdkSysroot(lo.other_step.root_module, inc_common, inc_arch, lib_path, android_api);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

In Zig, 'std.Build.Module.LinkObject' is a tagged union. Comparing a tagged union directly with an enum literal using '==' (e.g., 'lo == .other_step') is a compile error. Additionally, accessing 'lo.other_step' directly without pattern matching is not safe.

Using a 'switch' statement is the idiomatic and correct way to inspect and unwrap tagged unions in Zig.

        for (bgfx_artifact.root_module.link_objects.items) |lo| {
            switch (lo) {
                .other_step => |other| applyNdkSysroot(other.root_module, inc_common, inc_arch, lib_path, android_api),
                else => {},
            }
        }

Comment thread backends/bgfx/build.zig
Comment on lines +30 to +36
const ndk_arch_triple: []const u8 = switch (target.result.cpu.arch) {
.aarch64 => "aarch64-linux-android",
.x86_64 => "x86_64-linux-android",
.arm => "arm-linux-androideabi",
.x86 => "i686-linux-android",
else => @panic("unsupported Android arch for bgfx"),
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In Zig, 32-bit ARM targets can also be represented by the '.thumb' CPU architecture. To prevent unexpected compile-time panics when targeting 32-bit ARM in thumb mode, map '.thumb' to the 'arm-linux-androideabi' triple alongside '.arm'.

        const ndk_arch_triple: []const u8 = switch (target.result.cpu.arch) {
            .aarch64 => \"aarch64-linux-android\",
            .x86_64 => \"x86_64-linux-android\",
            .arm, .thumb => \"arm-linux-androideabi\",
            .x86 => \"i686-linux-android\",
            else => @panic(\"unsupported Android arch for bgfx\"),
        };

Gemini review: 32-bit ARM can be represented as the .thumb CPU arch, which
would hit the else => @Panic in the NDK triple switch. Map .thumb alongside
.arm. (Declined the companion 'lo == .other_step is a compile error' finding:
comparing a tagged union to an enum literal is valid Zig — CI's bgfx-build
job compiles this build.zig and passes.)
@apotema

apotema commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Bot triage:

  • .thumb arch mapping (Gemini, medium) — valid, fixed in 47f9ad7. 32-bit ARM can be the .thumb CPU arch, which would hit the else => @panic in the NDK-triple switch; now mapped alongside .arm to arm-linux-androideabi. (It gets past the switch; full 32-bit ARM bgfx compilation is a separate concern out of scope for this aarch64-focused phase.)
  • "lo == .other_step is a compile error" (Gemini, critical) — declined, false positive. Comparing a tagged union value to an enum literal with == is valid Zig (it compares the active tag). The proof is concrete: CI's bgfx-build job runs zig build test, which compiles this build.zig (the is_android block is always compiled by the build runner) — and it passed, as did every local build. A genuine compile error there would fail every zig build invocation before anything runs.

Desktop unchanged (zig build test green); aarch64 Android compile still produces libbgfx.a.

@apotema
apotema merged commit 10a9825 into main Jun 13, 2026
5 checks passed
@apotema
apotema deleted the feat/bgfx-android-p1-ndk-sysroot branch June 13, 2026 00:02
apotema added a commit that referenced this pull request Jun 13, 2026
Backend work since v0.39.1:
- wgpu: macOS Metal surface + textured sprite rendering (#290, #291)
- bgfx: macOS bring-up to on-device Android (#296 epic, #304/#305/#307/#308/#309)
- #310 AndroidBackendContext adapters: sokol (#312) + bgfx (#313) register the
  core seam; bgfx-Android gamepad via the shared android_gamepad sub-package
- bgfx desktop gamepad: GLFW (#315) + SDL HIDAPI / Switch-pad support (#318)
- cached bgfx CI job (#295)

Android codegen now calls core.registerAndroidBackend → requires
labelle-core >= v1.17.0 + labelle-engine >= v1.50.0.
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.

bgfx Android (phase 1): wire NDK sysroot + compile bx/bgfx/bimg clean for aarch64-linux-android

1 participant