Files
sx/examples/99-android-egl-clear.sx
agra 561ad03a7c android: Platform-owned entry bridge + .android OS enum variant
User writes BOTH `main` and a 3-line `android_main(app)` trampoline.
The library provides `sx_android_bootstrap(app)` (stashes the NDK app
pointer into a platform-owned global) and `AndroidPlatform` impl of
the Platform protocol. The library NEVER references `main` — the OS-
shape entry symbol lives in user code where the other entry symbols
already live. iOS / SDL3 keep their existing shape; only Android adds
the trampoline.

Cross-cutting bits this commit ships:

  library/modules/compiler.sx
    Add `android` variant to `OperatingSystem`.

  src/ir/lower.zig
    - injectComptimeConstants: map TargetConfig.isAndroid() → .android.
    - New Pass 4 `checkRequiredEntryPoints`: emit a clean diagnostic
      when `--target android` is requested but `android_main` isn't
      defined, instead of letting the user crash on a dlopen-time
      missing-symbol error.

  library/modules/platform/android.sx
    AndroidPlatform impl of the Platform protocol — EGL bringup on
    `APP_CMD_INIT_WINDOW`, ALooper(0) polling, dispatches the user's
    frame closure each ~16 ms tick. `sx_android_bootstrap(app)` is the
    only function exposed for the entry trampoline.

  examples/99-android-egl-clear.sx
    Rewritten to use the new pattern: minimum `main` + `android_main`
    pair, AndroidPlatform-driven render loop. Doubles as the usage
    reference users hand off to the compiler diagnostic.

Verified on Pixel 7 Pro: purple clear-color frame, periodic
`rendered 60 frames` logcat lines. iOS-sim chess + 86/86 regression
tests pass.
2026-05-19 00:23:33 +03:00

80 lines
2.9 KiB
Plaintext

// Android-only: pure-sx NativeActivity that brings up EGL on the
// ANativeWindow delivered by native_app_glue and clears the screen
// every frame via GLES3. Equivalent of `examples/63-metal-clear.sx`
// for the Android target.
//
// Entry-point contract (the "via Platform" shape):
// - User writes BOTH `main` and `android_main` at top level.
// - `android_main(app)` calls `sx_android_bootstrap(app)` and then
// `main()`. The library never names `main`; the OS-shape entry
// symbol lives in user code, where the other entry symbols are.
// - `main` instantiates `AndroidPlatform`, calls `init`, then
// `run_frame_loop` which drives the looper until destroyRequested.
//
// This exercises end-to-end the Android pipeline shipped in Session 70:
// - `sx build --target android` toolchain (NDK clang + glue link).
// - `--apk` APK assembly (manifest + aapt2 + zipalign + apksigner).
// - `android_main` (user-written here) gets external LLVM linkage
// via the `isExportedEntryName` allowlist in lower.zig.
// - `AndroidPlatform.run_frame_loop` drains ALooper events,
// creates EGL on `APP_CMD_INIT_WINDOW`, ticks the closure every
// 16 ms.
//
// Build + install on a connected device:
//
// /Users/agra/projects/sx/zig-out/bin/sx build --target android \
// --apk /tmp/sxhello.apk --bundle-id co.swipelab.sxhello \
// -o /tmp/libsxhello.so examples/99-android-egl-clear.sx
// adb install -r /tmp/sxhello.apk
// adb shell am start -n co.swipelab.sxhello/android.app.NativeActivity
// adb logcat -d --pid=$(adb shell pidof co.swipelab.sxhello)
//
// Expected: solid purple frame on the device. Periodic
// `rendered 60 frames` lines in logcat.
#import "modules/std.sx";
#import "modules/compiler.sx";
#import "modules/platform/api.sx";
#import "modules/platform/android.sx";
// GLES3 (linked via -lGLESv3)
glClearColor :: (r: f32, g: f32, b: f32, a: f32) #foreign;
glClear :: (mask: u32) #foreign;
GL_COLOR_BUFFER_BIT :u32: 0x4000;
frame_count : s32 = 0;
g_plat : *AndroidPlatform = null;
frame_tick :: () {
fc := g_plat.begin_frame();
glClearColor(0.5, 0.2, 0.8, 1.0); // purple
glClear(GL_COLOR_BUFFER_BIT);
g_plat.end_frame();
frame_count += 1;
if (frame_count % 60) == 0 {
__android_log_print(4, "sxhello".ptr, "rendered 60 frames\n".ptr);
}
}
main :: () -> s32 {
inline if OS == .android {
plat : AndroidPlatform = .{};
plat.init("sxhello", 0, 0);
g_plat = @plat;
plat.run_frame_loop(() => frame_tick());
}
0;
}
// OS-shape entry symbol. native_app_glue's
// `ANativeActivity_onCreate` ultimately calls this on the worker
// thread. We hand the app pointer to the platform module and then
// let user `main` drive the normal cross-platform setup path.
android_main :: (app: *void) {
inline if OS == .android {
sx_android_bootstrap(app);
main();
}
}