diff --git a/library/modules/gpu/gles3.sx b/library/modules/gpu/gles3.sx index 1f7dfc4..d1f2e82 100644 --- a/library/modules/gpu/gles3.sx +++ b/library/modules/gpu/gles3.sx @@ -31,6 +31,7 @@ // surface/context creation lives in platform/android.sx. eglGetProcAddress :: (name: [*]u8) -> *void #foreign; + // Functions absent from opengl.sx (it was authored against the SDL // desktop subset). We don't currently call these — destroy_* is best // effort here; future work can wire `glDeleteProgram` etc. through @@ -83,23 +84,11 @@ impl GPU for Gles3Gpu { self.gl_loaded = true; } - // One shared VAO with the renderer's 12-float layout. Bound here - // once; set_vertex_buffer just rebinds the VBO against it. + // Create the VAO. Attribute layout is deferred to set_vertex_buffer + // because `glVertexAttribPointer` captures the currently-bound + // GL_ARRAY_BUFFER into the VAO state — we don't know which VBO + // the renderer wants until it tells us via set_vertex_buffer. glGenVertexArrays(1, @self.vao); - glBindVertexArray(self.vao); - // pos (2 floats) - glVertexAttribPointer(0, 2, GL_FLOAT, 0, 48, xx 0); - glEnableVertexAttribArray(0); - // uv (2 floats) - glVertexAttribPointer(1, 2, GL_FLOAT, 0, 48, xx 8); - glEnableVertexAttribArray(1); - // color (4 floats) - glVertexAttribPointer(2, 4, GL_FLOAT, 0, 48, xx 16); - glEnableVertexAttribArray(2); - // params (4 floats) - glVertexAttribPointer(3, 4, GL_FLOAT, 0, 48, xx 32); - glEnableVertexAttribArray(3); - glBindVertexArray(0); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -135,10 +124,7 @@ impl GPU for Gles3Gpu { create_shader :: (self: *Gles3Gpu, vsrc: string, fsrc: string) -> ShaderHandle { inline if OS != .android { return 0; } - // opengl.sx::create_program takes [:0]u8 (NUL-terminated). The - // string literals from the renderer (#string GLSL blocks) are - // already NUL-terminated by the sx string-pool emitter, so the - // cast is safe. + prog := create_program(xx vsrc, xx fsrc); if prog == 0 { return 0; } slot : Gles3ShaderSlot = .{ @@ -267,6 +253,18 @@ impl GPU for Gles3Gpu { if buf == 0 { return; } glBindVertexArray(self.vao); glBindBuffer(GL_ARRAY_BUFFER, buf); + // glVertexAttribPointer captures the currently-bound + // GL_ARRAY_BUFFER into the VAO state. Reconfigure each frame + // so the VAO knows about the live buffer; otherwise drawing + // sources attribs from the zero-buffer (invisible geometry). + glVertexAttribPointer(0, 2, GL_FLOAT, 0, 48, xx 0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 2, GL_FLOAT, 0, 48, xx 8); + glEnableVertexAttribArray(1); + glVertexAttribPointer(2, 4, GL_FLOAT, 0, 48, xx 16); + glEnableVertexAttribArray(2); + glVertexAttribPointer(3, 4, GL_FLOAT, 0, 48, xx 32); + glEnableVertexAttribArray(3); } set_texture :: (self: *Gles3Gpu, slot: u32, handle: TextureHandle) { @@ -305,10 +303,12 @@ impl GPU for Gles3Gpu { set_scissor :: (self: *Gles3Gpu, x: s32, y: s32, w: s32, h: s32) { inline if OS != .android { return; } - glEnable(GL_SCISSOR_TEST); - // GL scissor origin is bottom-left; renderer.sx feeds top-left - // pixel coordinates, so flip Y. - glScissor(x, self.pixel_h - (y + h), w, h); + // TODO: re-enable once we figure out why the renderer passes + // a 0×0 clip rect on Android (chess's ScrollView path). The + // bounds the renderer feeds us land outside the framebuffer + // and clip everything off-screen. + // glEnable(GL_SCISSOR_TEST); + // glScissor(x, self.pixel_h - (y + h), w, h); } disable_scissor :: (self: *Gles3Gpu) { diff --git a/library/modules/platform/android.sx b/library/modules/platform/android.sx index 21d642a..290706b 100644 --- a/library/modules/platform/android.sx +++ b/library/modules/platform/android.sx @@ -85,14 +85,45 @@ TimeSpec :: struct { sec: s64; nsec: s64; } g_android_app : *void = null; g_android_plat : *AndroidPlatform = null; +// `app->activity` (ANativeActivity*) at byte 24. +// `activity->assetManager` at byte 64 inside ANativeActivity. +APP_OFF_activity :s64: 24; +ACTIVITY_OFF_assetManager :s64: 64; +ACTIVITY_OFF_internalData :s64: 32; + +// AAssetManager handle the user's `android_main` stashes via the +// bootstrap. Consumers that want to read bundled APK assets (font.ttf, +// piece sprites, level data, ...) read this and feed it into their own +// file-IO shim — e.g. chess wires it through `vendors/file_utils.c` +// which routes `read_file_bytes` through AAssetManager_open on Android. +g_android_asset_manager : *void = null; +g_android_internal_path : *u8 = null; + // ── Bootstrap (called by user's `android_main`) ──────────────────────── // Stashes the NDK app pointer the OS handed to `android_main(app)` so // the rest of the platform module can find it. Single responsibility — // the user's `android_main` calls this once, then calls their own // `main()` to enter the normal cross-platform setup flow. +// +// Also extracts `AAssetManager*` and `internalDataPath` from the +// ANativeActivity that the app pointer carries — these are the two +// pieces consumers need to touch APK assets / per-app storage on +// Android. Reachable as `g_android_asset_manager` / `g_android_internal_path`. sx_android_bootstrap :: (app: *void) { - g_android_app = app; + inline if OS == .android { + g_android_app = app; + base : s64 = xx app; + activity_pp : **void = xx (base + APP_OFF_activity); + activity_ptr := activity_pp.*; + if activity_ptr != null { + act_base : s64 = xx activity_ptr; + mgr_pp : **void = xx (act_base + ACTIVITY_OFF_assetManager); + g_android_asset_manager = mgr_pp.*; + path_pp : **u8 = xx (act_base + ACTIVITY_OFF_internalData); + g_android_internal_path = path_pp.*; + } + } } // ── Helpers ──────────────────────────────────────────────────────────── diff --git a/src/target.zig b/src/target.zig index e69151a..2b33cc8 100644 --- a/src/target.zig +++ b/src/target.zig @@ -331,6 +331,16 @@ pub fn createApk(allocator: std.mem.Allocator, io: std.Io, so_path: []const u8, // and zip is on every macOS/Linux host by default. try runProcessIn(allocator, io, stage, &.{ "zip", "-q", "-r", unaligned, "lib/" }); + // Bundle the project's `./assets/` directory (if present) at the APK's + // top level so AAssetManager_open(path) at runtime can read them. + // Resolves relative to the user's CWD at invocation time — matches the + // convention chess uses (assets/ next to main.sx). + if (std.Io.Dir.openDir(.cwd(), io, "assets", .{})) |dir_handle| { + var dh = dir_handle; + dh.close(io); + try runProcess(allocator, io, &.{ "zip", "-q", "-r", unaligned, "assets/" }); + } else |_| {} + // zipalign → aligned apk. const aligned = try std.fmt.allocPrint(allocator, "{s}.aligned", .{apk_path}); try runProcess(allocator, io, &.{ zipalign, "-f", "4", unaligned, aligned });