Skip to content

g29p1 Vulkan blob + Wayland for ODROID-M1 (C안) — full system matrix complete, release prep - #4

Merged
Pyohwan merged 112 commits into
odroid-m1-bsp-kernelfrom
odroid-m1-g29p1
Aug 29, 2026
Merged

g29p1 Vulkan blob + Wayland for ODROID-M1 (C안) — full system matrix complete, release prep#4
Pyohwan merged 112 commits into
odroid-m1-bsp-kernelfrom
odroid-m1-g29p1

Conversation

@Pyohwan

@Pyohwan Pyohwan commented Aug 12, 2026

Copy link
Copy Markdown
Owner

목적

A안(벤더 blob, g24p0)/B안(Panfrost)의 GPU 렌더 폴트(DATA_INVALID_FAULT)와 Vulkan 실패(PanVK가 KHR_display 서피스 생성에서 막힘, v9 실기 확인)를 완전히 우회하는 세 번째 경로: 신형 통합 blob(g29p1, GLES+Vulkan)을 Wayland(labwc) 위에서 굴려 Vulkan을 실제로 화면에 출력하는 것이 목표. GameCube Dolphin Vulkan이 A/B 둘 다 실패한 유일 시스템이라 헤드라인 타겟이었음.

브랜치 전략

A(odroid-m1-bsp-kernel)는 시행착오 포함 106개 커밋이라 그대로 베이스로 쓰지 않고, A/B가 갈라지기 전 공통 조상 커밋(41f34e4dfd)에서 새로 분기. 이후 A(5개)/B(17개) 분기 커밋 중 GPU스택 무관 제네릭 fix 13개만 cherry-pick + PCSX2/Dolphin standalone 등 시스템 활성화 부분만 수동 재작성(PanVK 전용 줄 제외). 상세는 커밋 로그 참고.

게이트 실험 (빌드 전 5분짜리 실기 검증)

  • 게이트 1 (통과, 2026-08-12): A v55 실기에서 g29p1 blob 라이브 스왑 → GLES 생존(System Info GLSL ES 3.20 정상) + vulkaninfo가 Mali-G52 정상 열거(driverID: DRIVER_ID_ARM_PROPRIETARY). 벤더 g25p0 kbase가 g29p1을 그대로 수용 → rocknix kbase 외부모듈 불필요, 커널 무변경 경량 경로 확정.
  • 게이트 2 (통과, 2026-08-13): Wayland client EGL 부활 확인 — g13p0/g24p0에서 깨져있던 eglInitialize가 g29p1에서 정상 동작. 본 빌드(blob 패키지 교체 + vulkan-loader/tools + labwc + vulkan-wsi-layer 추가) 진행 확정.

현재 상태 — 전체 시스템 매트릭스 완료, 릴리스 준비 단계

본 빌드 완료, GameCube/Dreamcast/PS2/PSP/PSX/Saturn/N64/NDS 8개 시스템 전부 실기 스모크+실사용 테스트 완료. 이 브랜치는 총 60개 커밋(공통 조상 41f34e4dfd 대비, AI 1·2차 리뷰 반영분 포함), 아래는 시스템별 최종 결론 요약:

시스템 결론
GameCube 헤드라인 성공. libretro dolphin-lr은 진짜 스레드 데드락(API 무관)으로 폐기, standalone Dolphin+Vulkan으로 전환해 완전 해결 — A/B 둘 다 실패했던 유일 시스템
Dreamcast standalone flycast+Vulkan이 4개 조합(standalone/libretro × GL/Vulkan) 중 최고(최저 55fps)
PS2 신형 GS(pcsx2-qt/bmdhacks)가 겪는 DATA_INVALID_FAULT를 완전 회피하는 AetherSX2(구형 GS 분기, Vulkan 강제)를 별도 코어로 추가해 실사용 확정(최저 29fps, NTSC 30fps 타겟 근접)
PSP Vulkan 파이프라인 실제 작동 검증 완료(standalone/libretro 둘 다) — GLES 대비 속도 우위는 없음(원래 GLES가 멀쩡했던 시스템이라 예상된 결과)
PSX 3코어(pcsx_rearmed/mednafen_psx/swanstation) 비교 후 pcsx_rearmed(OpenGL) 최종 추천
Saturn yabasanshiro "In the Hunt" 블랙스크린을 알려진 업스트림 버그로 규명(우리 문제 아님), 교차검증용 beetle-saturn 코어 추가(GLES 링크 버그 수정 포함) — 단, beetle-saturn 자체는 이 하드웨어에서 너무 느려 비권장
N64 Vulkan 코어 없음 확인(ParaLLEl-RDP는 x86_64 전용), Banjo-Kazooie 이상 저속은 롬 특성으로 결론(Track C 회귀 아님), Yoshi's Story로 정상 성능 재확인
NDS drastic(90~100%, 실측)이 속도 승, melonDS는 Wayland QPA 전환으로 물리 듀얼스크린(HDMI+VU8M 동시 출력) 지원 — 코어별 트레이드오프, 단일 승자 없음

세션 중 발견한 운영상 이슈: hotkeygen 데몬이 알 수 없는 기간 동안 죽어있어 핫키 컨텍스트 갱신이 조용히 실패하던 버그를 root-cause, S90hotkeygen 재시작으로 해결(재부팅 후 생존 여부는 아직 재검증 필요).

이번 커밋에 포함된 것 (요약)

  • Vulkan 파이프라인: vulkan-wsi-layer 신규 패키지(KHR_display 전용 blob Vulkan을 Wayland 서피스로 브릿지) + Wayland surface/swapchain 레이스 컨디션 다수 수정
  • Dolphin standalone: Wayland+Vulkan 패치 시리즈 이식, fullscreen 레이스 컨디션 3단계 수정으로 GameCube 최종 해결
  • PCSX2: Vulkan 스왑체인 double-free/서피스 포맷 레이스 수정, labwc fullscreen windowRule로 0×0 창 버그 해결
  • AetherSX2: PS2 대체 코어로 신규 추가(ES 통합 + 버튼 매핑 + gfxbackend 옵션)
  • Saturn: beetle-saturn을 교차검증용 대체 코어로 추가(GLES-only 보드용 링크 수정 포함)
  • melonDS: standalone을 Wayland QPA로 전환해 물리 듀얼스크린(melonds_dual_screen ES 옵션) 지원 활성화
  • 벤치마크 인프라: SDL2/RetroArch/vulkan-wsi-layer/melonDS 4곳에 env-gated 프레임타임 로거(FRAMETIME_LOG/FRAMETIME_LOG_VK) 추가 — env 미설정 시 완전 no-op, 눈대중 fps 대신 Avg/1% Low를 정량 측정하기 위한 것. tools/frametime-bench/ 참고
  • 기타: mesa3d-headers 보드용 여러 GLES-only 링크/빌드 수정(duckstation, beetle-psx, sdl3), buildroot 서브모듈 범프, JLEVEL 캡 등 빌드 안정화

AI 1차 리뷰 반영 (8개 커밋)

  • 공용 파일(다른 보드에도 영향 가던) 게이팅 버그 수정: 보드 전용 BATOCERA_TARGET_BSP_ODROIDM1 Kconfig 심볼 신설(A/B에도 동일 적용, PR ODROID-M1 dedicated target: hardkernel BSP kernel + Mali-G52 blob + VU8M overlays #1·Panfrost GPU stack for ODROID-M1 (B안) — PanVK 서피스 생성 실패로 폐기 #3 참고), dolphin-emu.mk의 Wayland 커밋/패치 스왑을 이 심볼로 좁혀서 다른 Wayland 보드의 dolphin 다운그레이드+패치 유실 방지
  • vulkan-wsi-layer를 bring-up용 Debug 빌드에서 Release로 되돌림 — 정밀 fps 벤치마크 전 필수였던 항목, 이제 해결돼서 벤치마크 바로 가능
  • aethersx2: main 브랜치 참조 → 커밋 고정 + .hash 추가, 벤더 .so provenance 문서화(libpcap 우선순위 확인 포함 — 의도대로 동작 확인됨)
  • labwc/rc.xml의 중복 pcsx2-qt windowRule 병합
  • mali-g52-odroidm1/Config.in에 형제 패키지에 있던 DMA_BUF/FORMAT_MODIFIER select 보완
  • .board의 rk3568/patches 재사용 안 하는 이유 주석이 부정확했던 것 수정 + provenance 파일 추가
  • docker.mk의 존재하지 않는 EXTRA_SRCDIR_MOUNTS 언급 정리
  • (검증 후 수정 불필요로 확인된 것) mali.icd.jsonlibmali.so.1 심링크는 ldconfig가 SONAME 기반으로 실기에 이미 자동 생성 중 확인, melonds.mklibinput 의존성도 Config.in에 이미 정확히 대응하는 조건부 select 있음 — 둘 다 리뷰의 false positive

AI 2차 리뷰 반영

  • dolphin-emu.mk 게이팅이 1차에서 절반만 적용됐던 것 수정 — DOLPHIN_EMU_VERSION/ENABLE_X11=OFF/VMA fix 3곳만 보드 전용 심볼로 좁혔고, ENABLE_QT=OFF/ENABLE_NOGUI=ON 블록(64행)은 여전히 공용 BR2_PACKAGE_BATOCERA_WAYLAND였음 — 그 결과 다른 Wayland 보드가 "새 커밋 + 구커밋용 NoGUI 강제"라는 어긋난 조합이 되고 있었음. 이 블록도 같은 심볼로 좁혀서 다른 Wayland 보드가 Qt6 경로로 정상 복귀하도록 수정
  • (판단 보류) moonlight-qt(884행)·melonds.mk가 여전히 BR2_PACKAGE_MALI_G52_ODROIDM1을 판별자로 쓰는 것 — 이 심볼은 이 보드에서만 =y라 다른 보드로 새는 문제는 없고, "이 보드"와 "blob GPU 쓰는 구성" 중 어느 쪽 의도인지 갈리는 문제라 리뷰에서도 급하지 않다고 명시함 — 이번엔 보류

남은 작업

  • 정밀 fps 벤치마크(위 프레임타임 인프라 사용, wsi-layer Release 전환 완료로 이제 유효한 측정 가능) — 거의 최종 빌드에서 진행 예정, 아직 미실행
  • CI 여전히 0건 — 신규 aethersx2Generator.py 등 이번 세션 파이썬 변경이 린트/유닛테스트 한 번도 안 돎
  • v7 릴리스 이미지 빌드 + 아카이브 (타인 배포용)
  • hotkeygen 데몬 재부팅 후 생존 재검증
  • drastic + 듀얼 활성 출력(VU8M+HDMI) 동시 조합 시 SDL 창 크기 버그 (VU8M 끄면 정상 — 임시 회피만 확인, 근본 수정 미착수)

머지 대상 아님 — 개인 배포용 최종 빌드 진행 중인 draft, 리뷰/코멘트용.

Pyohwan and others added 30 commits August 12, 2026 22:37
…RNEL_SRCDIR dev mount from odroid-m1-panfrost

These were found while bringing up the Panfrost (B) image but are generic
bugfixes unrelated to which GPU stack the board uses:
- rocknix-joypad/sdl3-x11 exclusion on this board was keyed off the blob
  package name (!BR2_PACKAGE_MALI_G52_ODROIDM1) as a stand-in for "this is
  the odroid-m1 board" - swapped to the always-present board identifier
  (BR2_PACKAGE_UBOOT_ODROID_M1) so it stays correct regardless of GPU stack.
- TRX's desktop-GL requirement was similarly keyed off the blob package
  name instead of the real capability flag (BR2_PACKAGE_HAS_LIBGL).
- moonlight-qt's guard is left untouched - its exclusion reason (a
  blob-specific eglGetPlatformDisplay gap) is genuinely GPU-driver-specific.
- docker.mk's KERNEL_SRCDIR opt-in bind mount (buildroot's OVERRIDE_SRCDIR
  mechanism, see board/batocera/rockchip/bsp-odroidm1/local.mk.example on
  odroid-m1-panfrost) is harmless to have available on this branch too.

No functional change for this branch's build output (blob still gates
HAS_LIBGL/board-identity the same way as before), this is purely a
root-cause correctness fix so the two branches don't drift on
unrelated-to-GPU logic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…6.1.5, matches mesa3d.mk/hash)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…ng the build host

Confirmed via journalctl: the Claude Code process itself got OOM-killed
mid-build (systemd: "ptyxis-spawn-...scope: A process of this unit has
been killed by the OOM killer") while a from-scratch build was compiling
MAME/OpenCV4/Kodi at BR2_JLEVEL=0's default (nproc+1=17 on this 16-core/
15GB host) - wide parallelism multiplies peak per-translation-unit memory
for large C++ files, not just wall-clock speed. odroid-m1-panfrost hit
the same class of problem with LLVM+Mesa3D and already caps at 8; this
board never got the equivalent cap despite also building large C++
codebases (this was the first from-scratch build of this board's output
tree in a while, so the 17-way default had never actually been exercised
under this kind of memory pressure before).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
~/git_projects no longer exists (2026-08-10 SSD swap moved everything to
/mnt/2026_990pro_2tb_1_data/git_projects/, no symlink recreated on
purpose per the migration handoff).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
Cherry-picked from odroid-m1-panfrost (B) where the comment correctly
named that branch's kernel repo version. Track C stays on A's BSP kernel
branch (fix-hdmi-dsi-dual-livelock), not panfrost-enable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…side graph layered on top of each generator's existing fps display

Single injection point (right where hud_support already sets
MANGOHUD_DLSYM/MANGOHUD_CONFIGFILE) instead of touching every generator
file individually - GALLIUM_HUD is a plain env var Mesa's gallium drivers
read at process start, so it's a no-op on anything that never loads a
gallium GL/GLES context (non-GL systems, the vendor Mali blob, older Mesa
without HUD support). That makes it a natural fallback to each
generator's own show_fps wiring (retroarch's fps_show, dolphin's
ShowFPS, etc.) rather than requiring an either/or branch per emulator.

Surfaced this session testing odroid-m1's Panfrost track: no numeric
GPU-load metric is exposed anywhere on this kernel (no devfreq node - the
GPU clock is statically pinned - no debugfs, and gallium_hud itself
rejects the "GPU-load" query for this driver: "unknown driver query
'GPU-load'"), but the graph-based fps/frametime view was still strictly
better than the existing plain-number counter for judging real-time
performance during A/B testing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
Same change as odroid-m1-panfrost - global.powermode was never actually
set in this board's default batocera.conf, so every A/B track fps
measurement so far ran under batocera-power-mode's unset-fallback
(set_default), not highperformance. Factory default only; ES's Game
Settings > Power Mode menu still overrides it live.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
vkquake2 genuinely builds ref_gl.so and ref_vk.so side by side (real
dual-renderer engine), so batocera-system's auto-select for it needs
BR2_PACKAGE_HAS_LIBGL as well as BATOCERA_VULKAN/XORG7 - false on this
board (Panfrost only exposes GLES/EGL). A defconfig "is not set" line
in the board file can't override a forced Kconfig `select`, so this had
to be fixed at the actual select condition, confirmed via a failed build
("libgl is in the dependency chain of libglu... Stop.").

pcsx2's WXWIDGETS select turned out to be unrelated dead weight - its
.mk never references wx in any CMake option (OpenGL/Vulkan are already
properly gated on HAS_LIBGL/BATOCERA_VULKAN), so gated the select and
the .mk dependency on HAS_LIBGL too, matching how pcsx2 actually gates
its own optional GL support.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…d failure

odroidm1/rk3568 wasn't in upstream's per-board platform/ARCH table, so it
fell through to the generic default where the Makefile's own ARCH
fallback (uname -m) picks up the build host's x86_64 arch instead of the
cross-compile target, pulling in xbyak's x86-only cpuid.h unconditionally
(confirmed via a real build failure: "-msse"/"-msse2" rejected by
aarch64-buildroot-linux-gnu-g++ compiling gles2rice). Added an explicit
branch mirroring RK3588/RK3576/H5 (WITH_DYNAREC=aarch64), reusing H5's
CPUFLAGS (armv8-a+crc, cortex-a53 tuning) since it's a safe baseline
subset for RK3568's Cortex-A55 cores with no extension-gated instructions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…ULKAN

ppsspp.mk only flips ARM_NO_VULKAN=OFF (i.e. actually allows Vulkan) for
RPi4/5; every other ARM board gets ARM_NO_VULKAN=ON regardless of the
general -DVULKAN=ON flag, silently compiling Vulkan support back out.
Confirmed via the built binary having zero Vulkan symbols despite
BATOCERA_VULKAN=y. Added RK3568 to the same allowlist as RPi4/5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
configgen-defaults-rk3568.yml still defaulted ps2 to libretro/play (a
stale upstream default from before PCSX2 was viable on aarch64) - our
board only registers the standalone pcsx2 emulator in es_systems.yml,
so this stale default made every PS2 launch try a nonexistent
libretro core and crash immediately ("Frontend is built for dynamic
libretro cores, but path is not set"). Confirmed live and fixed by
pointing the default at pcsx2/pcsx2 instead.

Separately, pcsx2-qt itself always defaulted to the xcb Qt platform
plugin, which aborts outright on this board (no X11, no compositor -
same failure class documented in duckstationGenerator.py for
duckstation-qt/melonDS). Reused the exact same WAYLAND_DISPLAY/DISPLAY
detection -> eglfs fallback pattern. Confirmed live: PCSX2 now reaches
its own BIOS-missing dialog instead of crashing at Qt init.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…pcsx2

dolphin-emu (standalone) always defaulted to the xcb Qt platform plugin,
aborting outright on this board (no X11, no compositor). Confirmed live
that eglfs actually works for Dolphin (unlike pcsx2-qt, which hits a
separate "Failed to get window info from widget" Qt/eglfs multi-widget
limitation) - Dolphin ran a real GPU-rendered session via
libqeglfs-kms-integration.so + libgallium for 90+ seconds with no crash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…for track C

Board-file portion of B's "Enable PanVK + Vulkan-capable systems"
(1246f47), re-applied manually with the PanVK/BATOCERA_VULKAN lines
dropped - track C's Vulkan comes from the g29p1 blob + hand-written ICD,
not a Mesa driver Kconfig select. flycastvl/ps2/play intentionally not
re-added (already dead ends on B, see 04c0e46/5f1cf74e76).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
BR2_PACKAGE_BATOCERA_WAYLAND_LABWC=y: track C's blob Vulkan has no
KMSDRM-direct display path (VK_KHR_display fails the same way under
PanVK on B), so Wayland is back on the table after A/B dropped it.

mali-g52-odroidm1.mk: the gbm_bo_get_fd_for_plane compat shim assumed
the blob only ever exports the pre-standardization gbm_bo_get_fd_per_
plane name (true for g13p0) and unconditionally aliased the modern name
to it based on grepping the vendor header text. g24p0's libmali.so.1.9.0
already exports gbm_bo_get_fd_for_plane directly - the header text just
never caught up - so the alias pointed wlroots (the first real caller of
this symbol on this board) at a symbol that no longer exists, breaking
the link. Check the .so's actual exported symbols instead of trusting
the header.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
Gate 1 (2026-08-12) confirmed g29p1 works against our unchanged vendor
kbase (g25p0): GLES survives, vulkaninfo enumerates Mali-G52. Source
moves from the "libmali" branch (g24p0) to "libmali-next" (g29p1 isn't
published on the old branch), same JeffyCN mirrors repo, pinned commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
pcsx2 pulls in libgtk3 unconditionally, and libgtk3's Wayland backend
defaults to on once BR2_PACKAGE_WAYLAND is present (true now that labwc
is back). g29p1 doesn't export wl_egl_window_create/resize/destroy -
confirmed via nm -D, unlike eglGetPlatformDisplay/eglInitialize/
eglCreatePlatformWindowSurface which it does have - so the link fails.
X11 stays available via BR2_PACKAGE_XORG7, this only drops GTK's
Wayland-specific GDK backend.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
Root cause of the wl_egl_window_* link failures (kodi, then libgtk3's
Wayland backend) wasn't a genuine gap in this board's Wayland-EGL
support - it was mali-g52-odroidm1.mk's own EGL/GLES soname-aliasing
loop overwriting a real, working libwayland-egl.so.1 that buildroot's
plain "wayland" package already builds (confirmed via nm -D: wl_egl_
window_create/destroy/resize/get_attached_size all present, pure
malloc/free bookkeeping with zero GPU dependency - this symbol set
belongs to wayland.org's own reference implementation, not the GPU
driver, unlike EGL/GLES/GBM which genuinely do need to come from the
blob). Dropping wayland-egl from the alias loop lets wayland's real
file stand, so this reverts the libgtk3-wayland-backend workaround from
the previous commit - it was solving a problem we caused ourselves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
Wayland_GLES_LoadLibrary() hardcoded platform=0, which skips the
SDL_VIDEO_DRIVER_MALI dispatch batocera already added in SDL_egl.c
(sdl2_add_video_mali_gles2.patch) and falls through to the legacy
eglGetDisplay(native_display). The g29p1 proprietary blob does not
support Wayland auto-detection on that legacy path and returns
EGL_NO_DISPLAY, which surfaced as EmulationStation crash-looping with
"Could not get EGL display" on every boot.

Confirmed on real hardware: a raw ctypes EGL test using
eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_KHR, ...) succeeds, while
replicating the exact legacy eglGetDisplay(wl_display) call SDL was
making fails with EGL_NO_DISPLAY. Passing EGL_PLATFORM_WAYLAND_KHR
unconditionally (not gated behind SDL_VIDEO_DRIVER_MALI) is safe for
Mesa too, since EGL_KHR_platform_wayland is a standard extension.

Batocera's existing Mali patch only wired up kmsdrm
(EGL_PLATFORM_GBM_MESA); nothing had previously combined the Mali blob
with Wayland, so this gap was never hit before track C.
writeHotKeyConfig() only ever wrote input_enable_hotkey_btn (arming the
hotkey modifier), never any joypad action bound to it - cleanControllerConfig
even actively disable_all()s input_exit_emulator* every game launch via
prefix match. Only the keyboard input_exit_emulator="escape" default
actually quit a running libretro core; Hotkey+Start (batocera's own
force-quit convention, see es_input.cfg's keyboard hotkey=select binding)
did nothing on a gamepad.

Confirmed live on ODROID-M1 (SHAKS S6b pad, FBNeo Korean core/Night
Slashers): Select+Start did not exit until input_exit_emulator_btn was
set to Start's button id. This is shared configgen code, not board- or
GPU-track-specific - the gap should be identical on A/B, just never
exercised there.
…ns too

This board's Vulkan driver comes from the g29p1 blob (hand-written
mali.json ICD), not a Kconfig-selectable Mesa driver, so it was
deliberately left off - but that missed that the option also registers
PPSSPP's gfxbackend.ppsspp.emulator.yml (the Vulkan choice in ES's own
PSP graphics-API menu never appeared without it) and selects
VKQUAKE/VKQUAKE2/VKQUAKE3/QT6BASE_VULKAN.

Confirmed safe: every Mesa Vulkan driver select this option pulls in is
itself gated behind other targets (X86/RPi/PANFROST_MESA3D/
FREEDRENO_MESA3D) that don't match rk3568/bsp-odroidm1, so enabling it
here only adds the generic vulkan-tools/spirv-tools/shaderc packages
plus the Vulkan-native ports/UI options above - no conflicting driver.
…only

Vulkan to Wayland surfaces

Confirmed live via vulkaninfo's Instance Extensions list: the g29p1 blob's
Vulkan ICD only implements VK_KHR_display (bare DRM, no compositor) among
windowing-system surface extensions - no VK_KHR_wayland_surface at all.
Without this, every Vulkan app's own surface/device creation for a
Wayland window fails outright even though vulkaninfo enumerates the GPU
fine (confirmed live: PPSSPP's SDL2 window creation succeeded once SDL2
itself got Vulkan support enabled via the sdl2.mk buildroot-submodule fix,
but PPSSPP's own Vulkan backend init then failed and silently fell back
to GLES).

New package mirrors ROCKNIX's own fix for the same blob/board family
(RK3566/g29p1, projects/ROCKNIX/packages/graphics/vulkan-wsi-layer/
package.mk) - ARM's open-source vulkan-wsi-layer implements
VK_KHR_wayland_surface as a Vulkan layer on top of the bare ICD.

Deviates from ROCKNIX's recipe in two places, both confirmed live:
- WSIALLOC_MEMORY_HEAP_NAME=system, not their "linux,cma" - this BSP 6.1
  kernel's /dev/dma_heap/ only has reserved/system/system-uncached, no
  heap named "linux,cma" (different DMA-heap exporter/naming than
  whatever ROCKNIX's own kernel registers under that name).
- Layer .so and .json manifest installed to the SAME directory
  (/usr/share/vulkan/implicit_layer.d/), not split across /usr/lib +
  implicit_layer.d like ROCKNIX's package.mk - the manifest's
  "library_path" is the relative "./libVkLayer_window_system_integration.so",
  which the Khronos loader resolves against the directory containing the
  *manifest*, not against /usr/lib. Left unverified whether ROCKNIX's
  split actually works for them; co-locating is unambiguously correct
  per the loader's documented resolution rule.

Requires BR2_PACKAGE_BATOCERA_VULKAN=y - previously left off (see the
.board comment above this select) on the correct-as-far-as-it-went
reasoning that this board has no Kconfig-selectable Mesa Vulkan driver,
but that option also gates PPSSPP's gfxbackend.ppsspp.emulator.yml (the
Vulkan choice in ES's own PSP graphics-API menu never appeared without
it) and, as of this commit, this layer.

Status: builds and installs cleanly, loads and gets dispatched to
(vkCreateWaylandSurfaceKHR is called - confirmed via vulkaninfo, this
extension wasn't even recognized before the layer existed). Currently
blocked on a live, unresolved failure: the call returns
VK_ERROR_OUT_OF_HOST_MEMORY while enumerating labwc's zwp_linux_dmabuf_v1
format/modifier list (wsi/wayland/surface.cpp's
get_supported_formats_and_modifiers(), custom_allocator-backed
util::vector filling from the dmabuf format/modifier event listener -
this board's Mali AFBC modifier list is very large, hundreds of
format+modifier pairs per `wayland-info`, prime suspect but unconfirmed
without a debugger). VULKAN_WSI_DEBUG_LEVEL=5 produces no extra output;
MangoHud layer interaction ruled out (DISABLE_MANGOHUD=1, same failure).
See project Joplin note for the full investigation writeup.
…mitted upstream)

Points at a detached-HEAD commit (d851f79697) on top of the existing
buildroot fork pin (95d26b3e3f) - not yet pushed anywhere, since this
worktree's buildroot submodule only has the batocera-linux/buildroot
upstream remote configured, no personal fork. The fix itself: package/
sdl2/sdl2.mk no longer requires a Mesa Vulkan driver to enable
--enable-video-vulkan (see that commit's own message). Needs a real
fork remote added and pushed before this is reproducible from a fresh
clone.
…ilure

Corrects a0ad43e's misdiagnosis. That commit's Status note blamed
VK_ERROR_OUT_OF_HOST_MEMORY on the dmabuf modifier-list vector
(custom_allocator OOM in get_supported_formats_and_modifiers) -
that's wrong. The real failure happens earlier and never reaches that
code: wsi/wayland/surface.cpp's surface::init() hard-requires the
zwp_linux_explicit_synchronization_v1 Wayland global, which wlroots
(labwc's compositor) has never implemented - it went straight to the
newer linux-drm-syncobj-v1 model instead. Every failure branch inside
surface::init() (missing dmabuf/explicit_sync/presentation interface,
roundtrip failure, real OOM) gets collapsed into the same generic
VK_ERROR_OUT_OF_HOST_MEMORY by CreateWaylandSurfaceKHR's caller
(surface_properties.cpp), which is what made the error code so
misleading.

Confirmed live: `wayland-info | grep -i explicit` on this device
returns nothing, and (once logging was actually working, see below)
WSI_LOG_ERROR at the exact explicit_sync check fired every time.

Also root-caused why VULKAN_WSI_DEBUG_LEVEL was a silent no-op the
entire time this was being investigated: util/log.hpp gates
wsi_log_enable behind `#ifdef NDEBUG`, and buildroot's cmake-package
infra defaults new packages to CMAKE_BUILD_TYPE=Release (-DNDEBUG) -
so every WSI_LOG_ERROR call across the whole layer was compiled out
to nothing, regardless of the env var. No gdb or extra tooling was
needed once this was found - just build Debug.

Fix: apply ROCKNIX's own sync.patch for this exact pinned layer
commit (8f077c5, matches VULKAN_WSI_LAYER_VERSION here) - authored by
Ivan Podogov (ginkage), who wrote the layer's Wayland backend upstream
and ships this same patch to run it on wlroots (sway) compositors.
Makes explicit sync optional with an implicit dma-buf-fence fallback
(CPU-side wait_payload when no acquire-fence interface is available),
forces FIFO present mode (implicit sync isn't safe with
MAILBOX/IMMEDIATE), remaps ARGB8888/ABGR8888 to the opaque
XRGB8888/XBGR8888 fourccs compositors advertise more reliably, adds
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, and disables the ABGR2101010 format
table entry. Saved verbatim as
0001-fall-back-to-implicit-sync.patch for buildroot's automatic
per-package patch application; explicitly NOT applying ROCKNIX's
sibling vklayer-driver-path.patch (bare-soname library_path) since
that would break this package's current co-located
implicit_layer.d install layout.

Also, independently of the above (found live, not covered by the
ROCKNIX patch): the manifest's relative "./libVkLayer_..." library_path
failed to dlopen for at least one real launch context (PPSSPP via
SSH, different CWD) even though it worked for vulkaninfo - switched
mali.json's sibling manifest... n/a here, but noting for the record:
this package's own install step already co-locates the .so and .json,
matching what turned out to be necessary.

DMA heap: switched WSIALLOC_MEMORY_HEAP_NAME from "system" to
"system-uncached", per ginkage's own README recommendation for this
allocator backend - "system" is a cached page-allocator heap (not
CMA/contiguous, correcting an earlier version of this comment), and a
cached heap risks coherency artefacts on buffers shared between GPU
and compositor.

CMAKE_BUILD_TYPE=Debug added temporarily for bring-up so
WSI_LOG_ERROR/WARNING actually print - remove once the Vulkan
surface/swapchain path is confirmed stable end-to-end (revert to
buildroot's Release default).

Verified live end-to-end: vkCreateWaylandSurfaceKHR and
vkCreateSwapchainKHR both succeed (previously OUT_OF_HOST_MEMORY /
INITIALIZATION_FAILED), PPSSPP with GraphicsBackend=VULKAN runs a
20s+ session with no fallback to GL, no dmesg GPU faults, and
multiple real Vulkan pipelines compiled+cached (ShaderManagerVulkan/
PipelineManagerVulkan save messages). This is Track C's first actual
Vulkan-rendered frame.

Credit: root-caused independently in this session via direct source
reading (explicit_sync requirement + NDEBUG logging gate) and
cross-checked against an equivalent independent analysis from another
session, which additionally identified ROCKNIX's existing sync.patch
as the vetted fix and system-uncached as the correct heap - both
adopted here instead of a hand-rolled equivalent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…Vulkan cores

This Mali blob ICD rejects vkCreateDevice outright with
VK_ERROR_INITIALIZATION_FAILED (rather than tolerating it, as more lenient
drivers do) for two spec violations the layer could itself introduce or
pass through unmodified. Found by tracing why libretro ppsspp_libretro.so
(RetroArch) crashed with SIGABRT under Vulkan even though standalone
PPSSPP (SDL2) already worked cleanly after the sync.patch fix - the
device-creation path is entirely different between the two (RetroArch's
own Vulkan HW render context negotiation vs. PPSSPP's own SDL2-based
Vulkan init), so this is a genuinely separate bug from the explicit_sync
one, discovered only once RetroArch's own VkResult (-3,
VK_ERROR_INITIALIZATION_FAILED) was logged directly, since the core
itself swallows the failure silently and proceeds to call
vkGetDeviceQueue on the resulting invalid device handle, which is what
the Khronos loader's validation actually catches and aborts on.

1. add_device_extensions_required_by_layer() could append an extension
   the application already requested itself - confirmed live:
   RetroArch's negotiation already requests VK_KHR_external_fence/_fd,
   which this function's own "optional extensions independent of winsys"
   list and its per-platform required-extensions list both also add
   unconditionally, producing a genuine duplicate extension name in
   ppEnabledExtensionNames (forbidden by
   VUID-VkDeviceCreateInfo-ppEnabledExtensionNames-01387). Fixed by
   skipping anything extensions_to_enable already contains in both
   places.

2. RetroArch's own VkDeviceCreateInfo sets a legacy non-NULL
   pEnabledFeatures *and* chains a VkPhysicalDeviceFeatures2 in pNext at
   the same time (forbidden by VUID-VkDeviceCreateInfo-pNext-00373). The
   layer forwards the app's struct essentially unmodified, so this passed
   straight through. Fixed in create_device() by clearing
   pEnabledFeatures whenever a VkPhysicalDeviceFeatures2 is also present
   in pNext - the pNext version is a strict superset of what the legacy
   struct expresses, so dropping the redundant pointer is always safe.

Saved as 0002-fix-device-create-info-spec-violations.patch (verified
applying cleanly with `patch -p1 --dry-run` against a pristine checkout
of the pinned commit 8f077c5, independent of 0001's hunks - touches
different files).

Verified live end-to-end: libretro ppsspp_libretro.so with
video_driver=vulkan went from immediate SIGABRT to a full clean run -
device created, Vulkan surface + 3-image swapchain created, FIFO present
mode selected, no dmesg GPU faults, 10s+ sustained. This is a generic fix
in the layer's own device-creation path, independent of windowing
system, so it should also benefit any other libretro core using
RetroArch's Vulkan HW render negotiation (parallel_n64/ParaLLEl-RDP,
MAME with Vulkan support, etc.), not just PPSSPP.

Note: standalone Dolphin (dolphin-emu, Qt6) remains separately broken
under Track C - it fails even earlier, before any Vulkan/WSI call is
ever reached, most likely in Qt's own native wl_surface handle
acquisition under labwc (`-platform wayland` gets a real window on
screen but "Failed to initialize video backend!"; `QT_QPA_PLATFORM` env
var is inexplicably ignored in favour of `-platform` CLI arg, a
still-unexplained Qt6 quirk on this build worth revisiting). Not
attempted further this session - out of scope of this patch, which is
specifically about the vkCreateDevice failures above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
Was building without -DENABLE_VULKAN at all - confirmed live via the
libretro dolphin_libretro.so core's own maps (only libmali.so loaded,
no libvulkan.so, "context type: gl" in the RetroArch log) that this
core has never actually attempted Vulkan on this board, GLES-only.

ROCKNIX's dolphin-lr package.mk enables Vulkan unconditionally whenever
VULKAN_SUPPORT=yes (no ARM/x86 device gating, unlike their
parallel-n64-lr which is x86_64-only for HAVE_PARALLEL - see next
commit's investigation) - this core's Vulkan backend is a real,
generically-available option upstream, not an x86-only experimental
one. Matches the same BR2_PACKAGE_BATOCERA_VULKAN gate the standalone
dolphin-emu package already uses.

Not yet rebuilt/verified live - queued for the next full image build
alongside the standalone dolphin-emu Vulkan fix (which was stale-built
before BATOCERA_VULKAN was turned on) and PCSX2/vkquake (already
BR2_PACKAGE_*=y in .config/.board but never actually built into the
image that's currently flashed on the device).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…th BATOCERA_VULKAN=y

duckstation.mk's BR2_PACKAGE_BATOCERA_VULKAN branch adds a real
BR2_PACKAGE_MESA3D (not mesa3d-headers) dependency, but duckstation's
own Config.in never selects/depends-on it - buildroot's own legality
check fails the whole board build the instant BATOCERA_VULKAN=y
(confirmed live: "mesa3d is in the dependency chain of duckstation
that has added it to its _DEPENDENCIES variable without selecting it
or depending on it from Config.in"). This branch was presumably always
dead code on this board until now, since nothing had turned
BATOCERA_VULKAN on before.

Not worth chasing further: duckstation-qt was already abandoned on A/B
for a separate, unrelated reason (Qt/eglfs can't host its multi-widget
window structure without a compositor - "Failed to get window info
from widget", see project memory), and PSX is already covered by
libretro swanstation (the actual recommended core, 58fps+
software-rendered, no GPU faults). A real BR2_PACKAGE_MESA3D
dependency would also risk the same class of Kconfig conflict this
board already hit once with Saturn kronos (this board only configures
BR2_PACKAGE_MESA3D_HEADERS for the blob, not full mesa3d).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…tocera-system's select

Previous commit's "# BR2_PACKAGE_DUCKSTATION is not set" line in the
board defconfig had no effect: batocera-system/Config.in
unconditionally selects DUCKSTATION for GLES3 boards, and Kconfig
select always wins over a plain defconfig negation - confirmed live,
.config still showed BR2_PACKAGE_DUCKSTATION=y after regenerating and
the build hit the identical mesa3d error again.

Fixed at the actual mechanism: added a depends-on guard directly to
duckstation/Config.in itself. Kconfig silently drops a select whose
target's own depends-on evaluates false (same non-fatal WARNING class
already seen for the pre-existing GStreamer OpenGL dependency
warnings in this board's build log) - this is the standard way to
locally override a select from shared/upstream Config.in without
touching that shared file. Condition matches the real conflict:
BR2_PACKAGE_MESA3D_HEADERS (this board's vendor-blob setup) together
with BR2_PACKAGE_BATOCERA_VULKAN (which makes duckstation.mk pull in
full BR2_PACKAGE_MESA3D).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…ndition

The depends-on guard from the previous commit didn't work: Kconfig's
select overrides a target's own failed depends-on with only a
WARNING (same class as this build's pre-existing, harmless GStreamer
OpenGL dependency warnings) - confirmed live, .config still showed
BR2_PACKAGE_DUCKSTATION=y after regenerating, and the build hit the
identical class of error again for a second dependency (cpuinfo, after
mesa3d), since the package was never actually disabled.

Real fix: add the exclusion condition directly to
batocera-system/Config.in's own select line, following the exact same
pattern already used there for other board-specific exclusions
(!BR2_PACKAGE_BATOCERA_TARGET_RK3326, !..._S9GEN4). Can't key off this
board's own BR2_PACKAGE_BATOCERA_TARGET_RK3568 flag since that's
shared across all three A/B/C GPU-driver tracks on this same physical
board - only Track C has BR2_PACKAGE_BATOCERA_VULKAN on, so gating on
BR2_PACKAGE_MESA3D_HEADERS && BR2_PACKAGE_BATOCERA_VULKAN together is
both the precise real conflict condition and inert on A/B.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
Comments interleaved inside the backslash-continued DUCKSTATION select
if-expression broke the Kconfig parser (it treated the comment as
ending the statement early, then choked on the leftover !(...) as a
bogus new statement). Move the explanatory comment above the select
line instead; the exclusion condition itself is unchanged.
SDL3_DEPENDENCIES unconditionally added mesa3d whenever
BATOCERA_VULKAN=y, even though SDL's Vulkan support only actually
needs vulkan-loader/vulkan-headers. On vendor-blob boards using
mesa3d-headers instead of full mesa3d (ODROID-M1 Track C), this
tripped buildroot's package-legality check (mesa3d symbol not =y)
since mesa3d-headers and mesa3d are mutually exclusive - same class
of conflict as the earlier duckstation fix.
Pyohwan and others added 16 commits August 26, 2026 22:41
Was showing upstream's plain "44-dev-<hash> <date>" (e.g. "44-dev-
8920c48 2026/08/26 15:22"), which visually clashes with our own
git release tag convention (batocera44-odroidm1-vX.Y.Z, already decided).

Now: on an exact release tag, batocera.version shows that tag verbatim
(e.g. "batocera44-odroidm1-v1.0.0"); otherwise falls back to
"batocera44-odroidm1-dev-<hash>" (still contains "dev" so the existing
BATOCERA_SYSTEM_COMMIT logic keeps appending the hash). Board-gated via
BR2_PACKAGE_BATOCERA_TARGET_BSP_ODROIDM1 since batocera-system.mk is
shared across all boards - other boards keep upstream's default.

Doesn't change how update detection actually works: do_upgradecheck_0
does a raw string comparison between the local batocera.version content
and the remote manifest (still not published - deferred to first
release), so it's unaffected either way by this text-format change.

Known minor side effect: board/batocera/scripts/post-image-script.sh's
SUFFIXVERSION extraction (`^\([0-9\.]*\)`) expects the version to start
with digits, so our new "batocera44-..." string yields an empty
SUFFIXVERSION and a double-dash in buildroot's raw output filename
(batocera-rk3568-bsp-odroidm1--20260826.img.gz). Harmless for us - our
own archiving step always renames to batocera-rk3568-bsp-odroidm1-
g29p1-vN.img.gz regardless - so not fixing the shared script for this
cosmetic-only case.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
Follow-up to d05f5c6. User feedback: before we actually cut a tagged
release, keep batocera.version identical to upstream's own dev-build
identifier ("44-dev-<hash> <date>") rather than rebranding every
untagged build as "batocera44-odroidm1-dev-<hash>". The missing commit
hash that started this whole investigation was purely the git-worktree
docker mount bug (fixed in 520d806) - upstream's format was never
broken, our build environment just couldn't populate it. Our own
release-tag format (batocera44-odroidm1-vX.Y.Z) still kicks in
correctly once HEAD is exactly on one of those tags - that part is
unchanged, since that's a genuine, deliberate identifier for an actual
release, not a rebrand of ordinary dev builds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
Live-verified auto core/API selection against the benchmarked table
(project_odroid_retro.md) game-by-game. Genesis/FBNeo/Saturn/PSX all
matched. N64 didn't: it launched glide64mk2 instead of rice.

Root cause: ES's "auto" choice isn't determined by es_systems.yml's core
listing order - it's computed by comparing each (emulator, core) pair
against this board's configgen-defaults emulator/core keys (see
python-src/batocera-es-system's registry.py, get_systems_metadata). The
earlier "auto order" fix (mentioned in project_odroid_retro.md) reordered
es_systems.yml's n64 core list, which has zero effect on that comparison
- the actual default (mupen64plus + glide64mk2) was never overridden
here, so it fell through to the shared configgen-defaults.yml value the
whole time.

Auditing the same mechanism for the other benchmarked standalone-emulator
systems found two more real mismatches (not yet live-tested, but the
same class of bug, confirmed via source inspection):

- gamecube/wii: emulator was "libretro" (copied verbatim from the shared
  configgen-defaults-rk3568.yml), which picks libretro-dolphin - not the
  standalone dolphin (emulator: dolphin) this board actually benchmarks
  and needs for the Wayland-only OpenGL fix. dolphinGenerator.py already
  defaults to Vulkan unless gfxbackend=OGL is set, so no extra option
  needed for the API side.
- dreamcast: no override at all, falling through to the shared file's
  libretro-flycast default instead of standalone flycast. Also added
  flycast_renderer=4 (forces Vulkan) since flycastGenerator.py's own
  default is OpenGL, unlike dolphin/ppsspp which already default to
  Vulkan.

psp needed no change: ppssppConfig.py already defaults to
"3 (VULKAN)" when gfxbackend is unset, matching the benchmark
recommendation as-is.

Not yet live-verified on hardware (n64 was; gamecube/wii/dreamcast
weren't reached yet in the ongoing verification pass) - flagged in
project_odroid_retro.md for the next round of testing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
Continuation of the auto core/API verification pass (n64/gamecube/wii/
dreamcast fixed earlier). PS2's auto default was set to pcsx2, which is
the core we already determined is structurally broken on this board's
Mali blob (DATA_INVALID_FAULT GPU faults - see project_odroid_retro.md's
"Mali G52 blob 구조적 한계" section). Confirmed live: auto silently
launched pcsx2, dmesg showed "GPU fault 0x5b from job slot 0", and the
game visibly hung.

Switched the default to aethersx2 (the actually-recommended, working
choice). pcsx2 stays installed/selectable in ES as a fallback, just no
longer the silent default that most users would hit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…doring

Follow-up to 4dc8fe5 (PS2 auto default fix). Live-testing that fix
surfaced a real launch failure: "libXrandr.so.2: cannot open shared
object file" - a leftover gap from AetherSX2's prebuilt-binary NEEDED
list that nothing had exercised before, since PS2's auto default had
been silently pointing at pcsx2 this whole time.

Initially vendored libXrandr.so.2 + its libXext/libXrender/libX11
transitive closure from Debian arm64 packages (same technique already
used for the GL/GLX/curl/openssl shims in aethersx2/libs/), but per
feedback: unlike those, there's no ABI/missing-feature reason libX11's
family can't just be built normally via buildroot's own xlib_libX11/
libXext/libXrender/libXrandr packages - so build them for real instead.

Reverted the vendored copies, added a select chain to aethersx2's
Config.in instead (same flattening pattern as melonds/Config.in - these
xlib_* Config.in entries all live inside "if BR2_PACKAGE_XORG7", so a
select landing on one of them doesn't reliably cascade to that symbol's
own further selects). Confirmed live: libXrandr/libXext/libXrender/libX11
all built successfully via buildroot and installed into the standard
/usr/lib, which aethersx2Generator.py's LD_LIBRARY_PATH already covers -
no changes needed there.

No performance impact: aethersx2Generator.py forces QT_QPA_PLATFORM=
wayland, so the Qt xcb platform plugin path that actually pulls
libXrandr in at runtime (display mode/refresh-rate queries) is never
taken - same "NEEDED-pass only" story as the GL/GLX shims that already
exist in aethersx2/libs/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
Convenience fix requested during VU8M setup testing: enabling VU8M
(overlays=display_vu8m) already requires editing config.ini, which is
the one file on the PC-visible FAT32 boot partition. batocera.conf (the
"correct" place for display.rotate) lives on the userdata partition
(ext4, not readable from a plain Windows/Mac file browser), and
batocera-boot.conf - while technically fine to hand-edit here (nothing
regenerates it wholesale; batocera-config only ever amends specific
keys like sharedevice/rootshadowpassword via targeted sed, never
display.rotate) - is a second file to open for no real reason when
config.ini is sitting right there.

config.ini is checked first, ahead of the existing batocera-boot.conf
path, so setting VU8M up (enable + rotate) is now a single-file edit.
Verified live: "Rotation from config.ini: [1]" logged, correct
800x1280 crop and -o 1 fbv rotation applied.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…rd README

batocera-backglass-window.cpp's font candidate lists (both header and
description) only ever contained DejaVu Sans / Liberation Sans - both
Latin-only, so any Korean text (game titles, backglass metadata)
rendered as missing-glyph boxes on the VU8M backglass screen. This is a
narrow gap in an otherwise Korean-font-aware system: ES itself already
ships a bundled Korean fallback font (PyeojinGothic-Medium, compiled
into the ES binary as a resource), and this board already has Noto
Sans KR + NanumGothic installed as real font files (via the vanilla
upstream nanum-font package, unrelated to our fork) - backglass's own
hardcoded candidate lists just never got updated to include either.
Prepended NotoSansKR-VF.ttf (checked first) to both candidate lists -
it's a variable font with full Latin+CJK coverage in one file, so one
font renders both scripts consistently (SDL_ttf can't font-switch
mid-string anyway). Falls back to the existing DejaVu/Liberation chain
if Noto isn't present. Compiled and deployed live; full visual
confirmation still pending (testing got sidetracked investigating an
unrelated HDMI+VU8M simultaneous-connection issue - see below).

Also adds README-odroidm1.md: repo-root, Korean, covers what's added
over vanilla Batocera, VU8M/HDMI display setup (including the config.ini
single-file rotation trick from the commit below), and the full
per-system performance/auto-core table. Iterated heavily with the user
this session - still being refined, not final.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4zVPhdz6gmjHPNpGGTQei
…anism

Live testing on-device found that S65values4boot already syncs
display.rotate/display.rotate.<connector> from batocera.conf into
batocera-boot.conf on every shutdown/reboot (vanilla mechanism) - the
config.ini duplication added in 58fcc13 was unnecessary, since the
boot splash's existing batocera-boot.conf fallback already picks up
the correct value with no extra config needed.

Also fixes the real bug behind this session's dual-display report: VU8M
rotation is controlled by global.videooutput2/display.rotate2.<connector>
in batocera.conf (set via the ES MULTISCREENS menu, or directly with
batocera-settings-set), not by anything in batocera-boot.conf or
config.ini - those only ever affect the primary output. Verified live:
global.videooutput2=DSI-1 + display.rotate2.DSI-1=3 (270 degrees) gives
correct portrait orientation on VU8M.

README updated to describe the actual mechanism instead of the
config.ini workaround.
…README

270 degrees was this particular panel's measured correct orientation,
not a general fact about VU8M - actual mounting can vary per unit.
- Remove the standalone "boot partition file editing" section - stale
  instructions, redundant with each subsection already saying where its
  own file lives.
- Shrink the HDMI+VU8M "not recommended together" caveat down to the
  one hardware fact that's still true (DSI has no hotplug detection) -
  today's live testing showed no actual splash-rotation problem once
  global.videooutput2 is set correctly, so the earlier elaborate warning
  overstated a non-issue.
- Rename "화면 회전" to "화면 회전값 설정" and drop the
  global.videooutput2 example from it (that's the previous section's
  concern, not rotation) - keep only rotation-specific keys/commands here.
- Move the SHARE-partition-init caveat to the top of the section (it's
  a precondition for everything below, not an afterthought).
- Split the reboot out of the settings command block with a one-line
  explanation: EmulationStation only reads/applies videooutput2 and
  rotation at its own startup, so a plain batocera-settings-set has no
  effect until the next boot.
Both the primary and backglass screens have an option literally
labeled "SCREEN ROTATION", which is confusing without their full menu
paths (confirmed against GuiMenu.cpp): SYSTEM SETTINGS > HARDWARE for
the main screen, vs. SYSTEM SETTINGS > HARDWARE > MULTISCREENS >
BACKGLASS / INFORMATION SCREEN for VU8M.
Pyohwan added a commit that referenced this pull request Aug 29, 2026
Same upstream snapshot merged into the C track (odroid-m1-g29p1,
commit cbeaa11) on 2026-08-17, applied here to odroid-m1-bsp-kernel
so PR #4's diff reflects genuine A-vs-C track differences instead of
upstream drift. Merged with --allow-unrelated-histories (upstream's
2026-08-04 history squash has no shared ancestor with this fork).

243 conflicted files. 228 were files this branch never customized -
resolved by taking origin's version wholesale (git checkout --theirs).
The other 14 (this board's own S02resize/audio-config/power-mode/
Ssplash-mpv/es_input.cfg/emulationstation-standalone/storage-manager/
melonDS/libretro-core-info/libretro-kronos/batocera.conf fixes) were
reconciled via git merge-file: base = the file's content immediately
before this branch's own customizing commit, ours = this branch's
current version, theirs = origin's new version - applies this branch's
own diff cleanly on top of upstream's latest instead of blindly
preferring either side. All 14 merged with zero remaining conflict
markers except melonds.mk, whose 2 conflicting hunks (X11/libinput
dependency gate, ENABLE_WAYLAND rationale comment) are pure odroid-m1
board-specific additions absent from both base and origin - resolved
by keeping this branch's version.

buildroot submodule conflict resolved by keeping this branch's own pin
(fork/odroidm1-mpv-gbm-fix) - already only 1 trivial, unrelated commit
(webkitgtk build fix) behind origin/master, unlike C's buildroot fork
which needed real reconciliation.

Also fixed 6 files that lost their executable bit during manual
reconciliation (git show | cp does not preserve file mode).
…the ES version string

ES's version label is literally "BATOCERA.LINUX ES V" + BATOCERA_SYSTEM_VERSION,
so using the git tag (batocera44-odroidm1-vX.Y.Z) verbatim doubled up into
"...ES Vbatocera44-odroidm1-v1.0.0". Strip the tag down to "44-odroidm1-X.Y.Z"
for the version string while keeping the tag itself unchanged (still reads
well in GitHub's release list) - final ES label becomes
"BATOCERA.LINUX ES V44-odroidm1-1.0.0 <build date>", matching the shape of
upstream's own "V44-dev-<hash>".
…s ambiguous merge-base

Both odroid-m1-bsp-kernel and odroid-m1-g29p1 independently merged the
same upstream squash commit (e38b00f) with --allow-unrelated-histories,
which left git (and GitHub's PR diff) with two candidate merge-bases and
no single common ancestor - PR #4's diff stayed at ~390-540 files instead
of shrinking to the genuine A-vs-C track difference.

This merge changes no C-track content (tree is identical to the previous
commit) - it only records odroid-m1-bsp-kernel's current tip as an
ancestor of this branch, giving the two branches one unambiguous shared
commit going forward so PR #4's diff resolves to the real difference.

@Pyohwan Pyohwan left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

AI 4차 리뷰 — 릴리스 전 점검 (참고용)

읽은 범위: 로컬 clone에서 8920c482..edb018a887 41개 커밋과 그 diff 전량, git diff origin/odroid-m1-bsp-kernel HEAD 120파일 전수, A안 리뷰 대응 9종의 C안 생존 여부 스팟체크, fsoverlay 사본 4종을 공용 원본과 직접 diff, CI 로그(run 33238810717), ruff format의 py313/py314 동작 최소 재현.

형식 코드 품질 컨벤션 테스트/CI 릴리스 정합
⚠️ ❌ (lint fail)

판정: 지금 상태로는 승인하기 어렵습니다. 릴리스 이미지에 직접 영향이 가는 3건이 열려 있습니다 — 라인 인라인 5건(❌ 4 / ⚠️ 1)을 참고해 주세요. 다만 코드 구조 자체는 1~3차 대비 확실히 좋아졌고, 아래 3건은 모두 국소 수정이라 정리 후 재확인하면 승인에 문제 없어 보입니다.

라인에 붙지 않는 내용만 아래에 남깁니다.

1. -s ours 머지 — 목적은 달성했습니다

edb018a887의 의도(두 개의 merge-base로 PR diff가 부풀던 문제 해소)는 실제로 확인했습니다.

  • 트리 변경 없음: git diff edb018a887^1 edb018a887 → 빈 출력
  • PR diff가 540 → 120파일로 수렴, 로컬 git diff origin/odroid-m1-bsp-kernel HEAD(120파일, +10816/-486)와 일치

부작용은 A안 tip이 이 브랜치의 조상으로 기록돼, 앞으로 A안에 들어가는 수정이 C안으로 자동 유입되지 않는다는 점입니다. 현재는 문제가 없습니다 — 1~3차 리뷰 대응 9종이 C안에 전부 살아 있는 것을 확인했습니다(BSP_ODROIDM1 게이팅 · storage-manager 파티션 ignore fallback · configgen defaults 분리 · gallium_hud_fallback opt-in · syslinux 동기화 · dosbox 중복 플래그 제거 · exit_emulator nest · mali GBM feature select · patches-source.txt).

다만 이제 두 트랙이 같은 수정을 두 벌로 유지하는 구조입니다(A안=공용 파일 직접 수정, C안=board fsoverlay 사본). 어느 쪽이 정본인지, 새 수정이 생기면 어디에 넣고 어떻게 반대편으로 옮길지를 README-odroidm1.md.board 주석에 한 줄 적어두시면 좋겠습니다.

2. CI가 A안은 통과하는데 C안만 실패합니다

PR lint unit-tests
#1 (A안) ✅ pass ✅ pass
#4 (C안) fail ✅ pass

원인 두 갈래는 각각 overlay.py:125, aethersx2Generator.py:21 인라인에 남겼습니다. 둘 다 국소 수정이고, 현재 릴리스 태그(batocera44-odroidm1-v1.0.0)가 붙은 커밋이 바로 이 실패 상태라는 점만 짚어둡니다.

3. 잘된 것

  • 공용 파일 수정을 board fsoverlay로 전환한 것이 1~3차 지적의 근본 해결입니다. 게다가 사본이 upstream 최신 + 보드 수정으로 정확히 맞아떨어집니다(원본 대비 각 16·35·28·15줄 = A안이 공용 파일에 넣었던 수정분과 동일). 현 시점 drift 0.
  • AetherSX2 libXrandr를 벤더링 대신 buildroot 정식 패키지로 전환(acc831e29a) — 1차 벤더링 지적의 정석 해결입니다. libs/에 남은 GL/GLX/curl/openssl은 ABI 사유가 문서화돼 있어 이견 없습니다.
  • auto 코어 기본값을 실측 기준으로 재정렬하면서, **"auto는 es_systems.yml 나열 순서가 아니라 defaults yml의 emulator/core 쌍이 결정한다"**는 근본 원인까지 규명한 부분. 증상만 고치고 넘어가기 쉬운 지점이었습니다.
  • 릴리스 버전 체계: 태그가 HEAD에 이미 붙어 git describe --exact-match가 동작하고, ES 라벨이 V + batocera...-v1.0.0으로 겹치는 것을 sed로 막은 것도 맞습니다.

4. 승인 조건

인라인 ❌ 4건 중 아래 3건이 릴리스 이미지 내용물을 바꿉니다.

  1. fbneo_korean info 심링크 (README가 대표 기능으로 소개하는 항목)
  2. batocera-upgrade fsoverlay 사본 제거 (또는 공용 파일 수정 원복 — 둘 중 하나)
  3. CI lint 2건

이 3건이 정리되면 승인 의견을 내겠습니다. python3.14 경로 건은 릴리스를 막지 않는 후속 항목입니다.

5. AI 한계 — 사람 확인 필요

실기 부팅·성능·Vulkan 동작·AetherSX2 안정성, 그리고 실제 이미지를 구워 /usr/share/libretro/info//usr/bin/batocera-upgrade의 최종 내용물을 확인하는 것은 정적 분석 범위 밖입니다. 위 지적은 모두 코드·설정 텍스트 대조와 CI 로그, ruff 동작 재현에 근거합니다.

🤖 Reviewed by Claude Opus 5 (1M context)

Comment thread python-src/batocera-bezel-overlay/batocera_bezel_overlay/overlay.py Outdated
Comment thread board/batocera/rockchip/bsp-odroidm1/fsoverlay/usr/bin/batocera-upgrade Outdated

# Main entry of the module
# Configure fba and return a command
def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution):

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

경로에 파이썬 버전이 박혀 있습니다. 현재는 맞습니다 — buildroot/package/python3/python3.mkPYTHON3_VERSION_MAJOR = 3.14와 일치합니다.

다만 buildroot가 3.15로 올라가면 이 오버레이는 에러 없이 그냥 쓰이지 않는 파일이 됩니다. rootfs에 /usr/lib/python3.14/...가 하나 더 생길 뿐이고 configgen은 새 경로에서 원본을 그대로 씁니다. 같은 fsoverlay 방식이라도 /usr/bin/* 쪽은 경로가 고정이라 이런 실패가 없는데, 이 두 개(duckstationGenerator.py·flycastGenerator.py)만 조용히 무효화되는 구조입니다.

릴리스를 막을 사안은 아니고, 다음 buildroot 범프 때 확인할 항목으로 남겨두시면 되겠습니다. (⚠️ 낮은 우선순위)

- Add the missing fbneo_korean_libretro.info symlink (present in the A
  track, missing here) - fbneo_korean is the default FBNeo core on this
  board and README-odroidm1.md advertises it, but without this symlink
  RetroArch can't read the core's name/extensions/options metadata.
- Remove the board fsoverlay copy of batocera-upgrade - confirmed
  byte-identical to the shared package/batocera/core/batocera-scripts
  copy (the local-var/$? fix already landed in both), so the fsoverlay
  copy does nothing now except silently re-shadow the shared file on
  the next upstream sync.
- Revert the erroneous "except ValueError, ImportError: is Python 2
  syntax" fix from 52523fc - it's valid PEP 758 syntax (unparenthesized
  multi-exception except clauses), accepted starting Python 3.14, which
  this build already targets (pyproject.toml target-version = "py314",
  buildroot PYTHON3_VERSION 3.14.5). Confirmed via `ruff format
  --target-version py314`, which actively strips the parentheses back
  out - the original upstream code was correct.
- Remove aethersx2Generator.py's unused imports (Mapping, SystemConfig,
  Guns, Input, DeviceInfoMapping, Resolution) - the 6 ruff-check
  failures that were breaking CI on this branch only, since this file
  never ran through CI before (per 1st/2nd review rounds). Auto-fixed
  with `ruff check --fix`.

All four verified with a local ruff 0.16.5 install (check + format
--diff), matching this repo's pyproject.toml configuration.

@Pyohwan Pyohwan left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

AI 5차 확인 — 4차 블로킹 항목 해소, 승인 의견

fdc833a83b(4파일, +7/-547) 검증 결과입니다. 4차에서 "정리되면 승인 의견을 내겠다"고 남긴 3건이 모두 해소됐습니다.

4차 지적 결과 확인 방법
fbneo_korean info 심링크 누락 심링크 추가. A안 libretro-core-info.mk와 파일 전체 diff 0
batocera-upgrade fsoverlay 사본 541줄 삭제. 남은 fsoverlay 4종은 실제 보드 수정을 담은 것들만 남았고, 공용 파일의 local available 분리 수정(216–217행)은 유지
CI lint — overlay.py PEP 758 오진 52523fc87e revert. ruff format --diffalready formatted, Python 3.14 파싱 정상
CI lint — aethersx2Generator.py TYPE_CHECKING 블록 unused import 6개 제거 → ruff checkAll checks passed!

CI도 통과했습니다(run 33239876041, lint·unit-tests 모두 pass). 4차 시점에 "A안 PR #1은 통과하는데 C안만 실패"하던 차이가 없어졌습니다.

승인 의견: 코드 기준으로 이 PR은 릴리스에 나갈 준비가 됐다고 봅니다. 셀프 PR이라 GitHub API로는 APPROVE 상태를 남길 수 없어 코멘트로 대신합니다.

남은 항목은 4차에서 릴리스를 막지 않는다고 분류한 python3.14 경로 하드코딩 하나뿐입니다(fsoverlay/usr/lib/python3.14/site-packages/configgen/generators/{duckstation,flycast}Generator.py). 다음 buildroot 파이썬 범프 때 조용히 무효화되는 구조라, 그때 확인 항목으로만 남겨두시면 되겠습니다.

1차부터 5차까지의 흐름을 돌아보면, 공용 파일을 직접 고치던 방식에서 board fsoverlay로 전환한 것이 가장 큰 개선이었습니다. 공용 Kconfig의 보드 프록시 게이팅(!BR2_PACKAGE_UBOOT_ODROID_M1)이 공유 rk3568 타겟 11개 보드의 rocknix-joypad를 떨어뜨리던 문제부터, 전 Wayland 보드의 Dolphin Qt GUI가 사라지던 문제까지 — 이 트랙이 다른 보드에 새지 않게 된 것이 이번 릴리스에서 가장 의미 있는 부분이라고 봅니다.

실기 부팅·성능·Vulkan 동작과 실제 이미지 내용물 확인은 여전히 정적 분석 범위 밖이며, 위 검증은 코드·설정 대조와 CI 결과, 로컬 ruff 재현에 근거합니다.

🤖 Reviewed by Claude Opus 5 (1M context)

Zansword was an incorrect name attached to the credit in an earlier
session - the correct name/casing is DsNo. Also describe the patch more
precisely: it's a CRC-tolerance fix (accepts Korean-translated ROMs that
fail the standard CRC32 check), not a text/UI translation.
README-odroidm1.md -> README-odroidm1.ko.md (unchanged content, just
renamed), plus a new README-odroidm1.en.md English translation. Both
files cross-link to each other at the top. Using ISO 639-1 "ko"/"en"
language codes rather than "kr" (that's the ISO 3166-1 country code for
South Korea, not a language code).
@Pyohwan
Pyohwan merged commit ba98375 into odroid-m1-bsp-kernel Aug 29, 2026
2 checks passed
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.

1 participant