Repository navigation
feat(bgfx): route desktop gamepad through SDL HIDAPI source (Switch-pad support) - #318
Conversation
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.
PR SummaryMedium Risk Overview The assembler stages CI: the dedicated bgfx macOS job Reviewed by Cursor Bugbot for commit 88869c2. 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 integrates the shared windowless-SDL desktop gamepad source into the bgfx backend, mirroring the implementation in the raylib and sokol backends. This allows the bgfx backend to route gamepad getters through SDL's HIDAPI drivers on desktop targets to decode controllers (like Switch-mode Nintendo Pro Controllers) that GLFW cannot. Feedback on the changes highlights an inefficiency in dirExists where a new std.Io.Threaded instance is initialized on every call instead of reusing the existing b.graph.io context. Additionally, the cross-compilation check for Windows incorrectly gates the LABELLE_SDL2_LIB search on the host OS being Windows, which breaks cross-compilation from other platforms.
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 sdlLibPath(target_os: std.Target.Os.Tag, host_os: std.Target.Os.Tag) ?[]const u8 { | ||
| if (target_os != .macos or host_os != .macos) return null; | ||
| if (dirExists("/opt/homebrew/lib")) return "/opt/homebrew/lib"; | ||
| if (dirExists("/usr/local/lib")) return "/usr/local/lib"; | ||
| return null; | ||
| } | ||
|
|
||
| fn dirExists(path: []const u8) bool { | ||
| var threaded = std.Io.Threaded.init(std.heap.page_allocator, .{}); | ||
| defer threaded.deinit(); | ||
| const io = threaded.io(); | ||
| std.Io.Dir.accessAbsolute(io, path, .{}) catch return false; | ||
| return true; | ||
| } |
There was a problem hiding this comment.
In dirExists, initializing a new std.Io.Threaded instance via std.Io.Threaded.init(std.heap.page_allocator, .{}) on every call is highly inefficient. It spawns OS worker threads and allocates memory on the heap just to check if a directory exists.
Since b.graph.io (of type std.Io) is already initialized and available in the build function, we should pass it down to sdlLibPath and dirExists to perform the directory checks using the existing I/O context.
fn sdlLibPath(io: std.Io, target_os: std.Target.Os.Tag, host_os: std.Target.Os.Tag) ?[]const u8 {
if (target_os != .macos or host_os != .macos) return null;
if (dirExists(io, "/opt/homebrew/lib")) return "/opt/homebrew/lib";
if (dirExists(io, "/usr/local/lib")) return "/usr/local/lib";
return null;
}
fn dirExists(io: std.Io, path: []const u8) bool {
std.Io.Dir.accessAbsolute(io, path, .{}) catch return false;
return true;
}
| // link + (on macOS Homebrew) the library path matters. Mirrors raylib/sokol. | ||
| if (sdl_gp_mod != null) { | ||
| input_mod.link_libc = true; | ||
| if (sdlLibPath(target.result.os.tag, builtin.target.os.tag)) |p| { |
| // (`windows-gnu`) toolchain, so honor `LABELLE_SDL2_LIB` — the dir | ||
| // holding the import lib (`libSDL2.dll.a`). `SDL2.dll` must be on PATH | ||
| // (or beside the exe) at runtime. | ||
| if (target.result.os.tag == .windows and builtin.target.os.tag == .windows) { |
There was a problem hiding this comment.
The check for LABELLE_SDL2_LIB is gated on builtin.target.os.tag == .windows. However, when cross-compiling to Windows from a non-Windows host (e.g., Linux or macOS), builtin.target.os.tag will be the host OS, which prevents LABELLE_SDL2_LIB from being honored.
Since LABELLE_SDL2_LIB is specifically used to provide the path to the Windows SDL2 import library when cross-compiling or compiling for Windows, we should only check if the target OS is Windows (target.result.os.tag == .windows), regardless of the host OS.
if (target.result.os.tag == .windows) {
… 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.
|
CI failure + bot triage — fixed in 88869c2:
Verified: bgfx |
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.
Route bgfx desktop gamepad input through the shared
sdl_gamepadsource (SDL2 + HIDAPI), so bgfx-desktop reads controllers GLFW can't decode — notably Switch-mode Nintendo Pro Controllers / Joy-Cons. Mirrors the raylib/sokol pattern exactly. The GLFW path (#315) is preserved as the.gamepad = .nonefallback; Android (#310) is untouched.What (mirrors raylib/sokol)
src/deps_linker.zig— added.bgfxto the.raylib, .sokol => if (cfg.gamepad == .auto)arm that stagesbackends/sdl_gamepad(labelle_sdl_gamepad→ linksdl_gamepad).src/templates/build_zig.txt—backend_bgfxnow takes.gamepad_enabledand replicates raylib/sokol'sif (backend_input.import_table.get("sdl_gamepad")) |m| overrideImport(m, "labelle_core", core_mod)unification block.src/build_files.zig— desktop bgfx renders the section with.gamepad_enabled.backends/bgfx/build.zig.zon— declares.labelle_sdl_gamepad = .{ .path = "../sdl_gamepad" }(non-lazy, like raylib/sokol).backends/bgfx/build.zig— agamepad_enabledbuild option, desktop-gated resolution of thesdl_gamepadmodule (core unified onto it), abuild_optionsmodule carrying the flag ontoinput, and the SDL2 link on desktop (Homebrew /LABELLE_SDL2_LIB).backends/bgfx/src/input.zig— detection identical to raylib/sokol:build_options.gamepad_enabled+ a comptime desktop predicate gate@import("sdl_gamepad")and ause_sdl_gamepadflag. The four gamepad getters route throughsdl_gp.Source.*ahead of the GLFW path;newFramepumpssdl_gp.Source.update()(lazy-init, hotplug, edge snapshot) when wired..none→ the SDL module is absent and the getters fall through to GLFW. Android (agp) branch unchanged.Verification (independent)
zig build+zig build test→ green.zig build testgreen for:.auto(default, SDL),.none(-Dgamepad_enabled=false, GLFW),aarch64-linux-android, andx86_64-windows-gnucross..autogenerate+build (bgfx desktop): generatedbuild.zigstages + wiressdl_gamepad, linkslibSDL2, references_SDL_GameController*..nonegenerate+build: nosdl_gamepad, no SDL2 linked, GLFW (glfwGetGamepadState) present.backend_bgfx_androiduntouched (no SDL).sdl_gamepad.SourceAPI bgfx now routes to (slot 0 AVAILABLE + live axis reads) — same source raylib already uses to read it.Honest notes
.autobgfx desktop game from your own Terminal with the controller to confirm in-game.inputwiring, so it routes all desktop (incl. Linux) through SDL on.auto. So bgfx-Linux desktop pulls SDL2 where raylib-Linux uses udev. Documented in code; a udev-parity path for bgfx-Linux is a possible follow-up..gamepad = .auto(default), matching raylib/sokol;.nonekeeps the SDL-free GLFW path.