feat(bgfx): desktop GLFW gamepad reading (buttons + axes) - #315
Conversation
The bgfx desktop input path only had `isGamepadAvailable` (joystick presence); buttons/axes were `return false`/`0` TODO stubs, so a bgfx game on desktop knew a pad was plugged in but received no input. Implement reading via `glfw.getGamepadState`, translating the engine's canonical raylib-compatible numbering (buttons [0,17], axes LX/LY/RX/RY/LT/RT = [0,5]) to GLFW's standard layout: - `canonToGlfwButton` maps face/dpad/bumper/thumb/middle buttons; the digital LEFT/RIGHT_TRIGGER_2 (10/12) are derived from the analog trigger axes. - axes are 1:1 (canonical 0..5 == GLFW 0..5). - `isGamepadButtonPressed` rising edges are snapshotted once per gamepad per frame in `newFrame` (`snapshotGamepads`), mirroring the keyboard/mouse `*_pressed` pattern; `getGamepadState` is called once per slot, not per query. Android path (#310) unchanged. A controller GLFW can't map (Switch-mode Nintendo Pro Controller — only SDL HIDAPI decodes those) reports present but yields no gamepad state (buttons/axes read released/0); use an X-input pad or the SDL backend. On macOS, input reading also needs Input Monitoring permission. Verified: bgfx desktop test + Android compile-check + example build green.
PR SummaryMedium Risk Overview Each frame, Reviewed by Cursor Bugbot for commit 1f37804. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Code Review
This pull request implements desktop gamepad support using GLFW in the bgfx backend. The review feedback identifies two critical compilation issues: first, snapshotGamepads() will fail to compile on Android targets because glfw is an empty struct, which requires a comptime guard; second, referencing glfw.Gamepad.Axis.count will cause a compilation error on desktop platforms because standard zglfw enums do not expose a count field, and the length of state.axes should be used instead.
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.
| fn snapshotGamepads() void { | ||
| var g: u32 = 0; | ||
| while (g < MAX_GAMEPADS) : (g += 1) { |
There was a problem hiding this comment.
On Android targets, glfw is defined as an empty struct (struct {}), which does not contain getGamepadState. Since snapshotGamepads() is referenced in newFrame(), the compiler will perform semantic analysis on its body and fail to compile on Android.
Adding a comptime check at the beginning of snapshotGamepads() ensures that the compiler discards the rest of the function body during semantic analysis on Android, preventing compilation errors.
fn snapshotGamepads() void {
if (comptime is_android) return;
var g: u32 = 0;
| if (gamepad >= MAX_GAMEPADS or axis >= glfw.Gamepad.Axis.count) return 0; | ||
| const state = glfw.getGamepadState(@enumFromInt(gamepad)) catch return 0; | ||
| return state.axes[axis]; |
There was a problem hiding this comment.
Using glfw.Gamepad.Axis.count will cause a compilation error on desktop platforms because standard zglfw enums do not expose a count field. Instead, you should fetch the gamepad state first and guard the axis index using the actual length of the state.axes array (state.axes.len). This is more robust and idiomatic Zig.
if (gamepad >= MAX_GAMEPADS) return 0;
const state = glfw.getGamepadState(@enumFromInt(gamepad)) catch return 0;
if (axis >= state.axes.len) return 0;
return state.axes[axis];
|
Bot triage — both declined as false positives (each contradicted by a passing build):
Both are the generic-knowledge-vs-this-fork mismatch; CI + the local Android compile-check are authoritative. |
…ad support) (#318) * feat(bgfx): route desktop gamepad through shared SDL HIDAPI source Mirror the raylib/sokol pattern so bgfx desktop reads controllers GLFW can't decode (e.g. Switch-mode Nintendo Pro Controllers) through the shared `backends/sdl_gamepad` source. Keep the #315 GLFW path as the `.gamepad = .none` fallback. Android (#310, android_gamepad) untouched. - deps_linker: add `.bgfx` to the `.auto`-gated sdl_gamepad staging arm. - build template: forward `gamepad_enabled` to the bgfx dep and add the core↔sdl_gamepad unification block in the `backend_bgfx` section. - build_files: render `backend_bgfx` with the gamepad_enabled flag. - backends/bgfx/build.zig.zon: declare `../sdl_gamepad` path dep. - backends/bgfx/build.zig: gamepad_enabled option, resolve+unify the sdl_gamepad module on desktop, import build_options, link SDL2. - backends/bgfx/src/input.zig: comptime `use_sdl_gamepad` (build_options + desktop predicate) routes the gamepad getters through sdl_gp.Source and pumps it in newFrame; GLFW path stays as the opt-out fallback. Desktop bgfx builds now pull SDL2 when `.gamepad = .auto` (default), matching raylib/sokol. * fix(bgfx-sdl-gamepad): install SDL2 in bgfx CI job + build.zig review fixes - CI: the dedicated `bgfx-build` (macOS) job didn't install SDL2, but bgfx desktop now links it when `.gamepad = .auto` (default) — add `brew install sdl2`, matching the build-and-test job. (This was the CI failure.) - build.zig (Gemini review): `dirExists`/`sdlLibPath` now take the build graph's `Io` (`b.graph.io`) instead of spinning up a fresh `std.Io.Threaded` thread pool per probe. - LABELLE_SDL2_LIB is gated on the TARGET os only (was target AND host), so cross-compiling to Windows from a non-Windows host honors it. Verified: bgfx `.auto` + `.none` tests green; ci.yml parses.
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.
Implement bgfx desktop GLFW gamepad reading (buttons + axes). The bgfx desktop input path only had
isGamepadAvailable(joystick presence);isGamepadButtonDown/Pressed/getGamepadAxisValuewerereturn false/0TODO stubs — so a bgfx game on desktop knew a pad was plugged in but received no input from it.What
Read via
glfw.getGamepadState, translating the engine's canonical raylib-compatible numbering — buttons[0,17], axesLX/LY/RX/RY/LT/RT = [0,5](matchingandroid_gamepad+ the raylib/sdl backends) — to GLFW's standard layout:canonToGlfwButtonmaps face/dpad/bumper/thumb/middle buttons; the digitalLEFT/RIGHT_TRIGGER_2(10/12) are derived from the analog trigger axes (> 0).isGamepadButtonPressedrising edges are snapshotted once per gamepad per frame innewFrame(snapshotGamepads), mirroring the keyboard/mouse*_pressedpattern — onegetGamepadStateper slot, not per query.The Android path (#310) is untouched; the
agp-vs-GLFW split is comptime-gated.Verification
zig build test+ Android compile-check + example build → green.Honest caveat
Live button/axis input wasn't end-to-end confirmed — the only controller paired to this machine is the Pro Controller, which GLFW doesn't map (above), and on macOS reading input also needs Input Monitoring permission for the host binary (run from a real Terminal, not a sandboxed shell). The mapping + edge logic is build-verified and matches the canonical numbering used by the other backends; confirming with an Xbox pad on a permitted Terminal is the remaining runtime check.