mirror of
https://github.com/archr-linux/Arch-R.git
synced 2026-03-31 14:41:55 -07:00
Build pipeline now produces a working bootable image. Found and fixed 11 gaps between the manually-built working SD and build-all.sh output: Boot partition: - extlinux.conf as primary boot method (U-Boot loads first) - PanCho removed from boot.ini and build-image.sh - Stale uInitrd removed (caused wrong boot path) - logo.bmp (U-Boot native BMP) replaces broken splash-1.raw - fstab uses LABEL=ROMS instead of /dev/mmcblk1p3 - Only R36S DTB copied (no extra r35s/rg351mp-linux) Root filesystem: - emulationstation.service created and enabled - getty@tty1 disabled (ES takes over tty1) - archr-boot-setup: Before=emulationstation.service, simplified - All services use After=local-fs.target (not removed getty) - boot-timing captures ES profiling data New files added to repo: - build-mesa.sh, build-retroarch.sh (were untracked) - Custom DTS, ALSA config, controller autoconfig - Runtime scripts (retroarch-launch, pmic-poweroff, hotkeys) - VLC stub source, timezone data Repo cleanup: - README.md rewritten with build instructions + architecture - .gitignore expanded (test scripts, failed approaches, logs) - splash-show.sh removed (failed splash approach) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
987 lines
42 KiB
Bash
Executable File
987 lines
42 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
#==============================================================================
|
||
# Arch R - EmulationStation Build Script
|
||
#==============================================================================
|
||
# Builds EmulationStation-fcamod (christianhaitian fork) for aarch64
|
||
# inside the rootfs chroot environment.
|
||
#
|
||
# Patches applied (21 total):
|
||
# 1-5: Context fixes (go2, MINOR, ES profile, null safety, MakeCurrent)
|
||
# 6-7: Safety fixes (getShOutput, language restart)
|
||
# 8-12: Performance (no depth, stencil, disable depth test)
|
||
# 13-14: Eliminate popen, reduce polling intervals
|
||
# 15: Cache getThemeSets() (19+ dir scans → 1)
|
||
# 16: Remove dead readList() call
|
||
# 17: NanoSVG static rasterizer
|
||
# 18: Boot profiling (5 timestamps → es-debug.log)
|
||
# 19: ThreadPool VSync reduction (10→500ms, ~1.5s saved)
|
||
# 20: Skip non-existent ROM directories
|
||
# 21: MameNames lazy init (call_once)
|
||
#
|
||
# This runs AFTER build-rootfs.sh and BEFORE build-image.sh
|
||
# Requires: rootfs at output/rootfs with build deps installed
|
||
#==============================================================================
|
||
|
||
set -e
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
OUTPUT_DIR="$SCRIPT_DIR/output"
|
||
ROOTFS_DIR="$OUTPUT_DIR/rootfs"
|
||
CACHE_DIR="$SCRIPT_DIR/.cache"
|
||
|
||
# CRITICAL: Always clean up bind mounts on exit (success OR failure).
|
||
# Without this trap, a build error leaves /dev /proc /sys /run mounted
|
||
# inside the rootfs — this can break the host system.
|
||
cleanup_mounts() {
|
||
echo "[ES-BUILD] Cleaning up bind mounts..."
|
||
umount -l "$ROOTFS_DIR/run" 2>/dev/null || true
|
||
umount -l "$ROOTFS_DIR/sys" 2>/dev/null || true
|
||
umount -l "$ROOTFS_DIR/proc" 2>/dev/null || true
|
||
umount -l "$ROOTFS_DIR/dev/pts" 2>/dev/null || true
|
||
umount -l "$ROOTFS_DIR/dev" 2>/dev/null || true
|
||
}
|
||
trap cleanup_mounts EXIT
|
||
|
||
# EmulationStation source
|
||
ES_REPO="https://github.com/christianhaitian/EmulationStation-fcamod.git"
|
||
ES_BRANCH="351v"
|
||
ES_CACHE="$CACHE_DIR/EmulationStation-fcamod"
|
||
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
NC='\033[0m'
|
||
log() { echo -e "${GREEN}[ES-BUILD]${NC} $1"; }
|
||
warn() { echo -e "${YELLOW}[ES-BUILD] WARNING:${NC} $1"; }
|
||
error() { echo -e "${RED}[ES-BUILD] ERROR:${NC} $1"; exit 1; }
|
||
|
||
#------------------------------------------------------------------------------
|
||
# Checks
|
||
#------------------------------------------------------------------------------
|
||
if [ "$EUID" -ne 0 ]; then
|
||
error "This script must be run as root (for chroot)"
|
||
fi
|
||
|
||
if [ ! -d "$ROOTFS_DIR/usr" ]; then
|
||
error "Rootfs not found at $ROOTFS_DIR. Run build-rootfs.sh first!"
|
||
fi
|
||
|
||
log "=== Building EmulationStation-fcamod ==="
|
||
log "Branch: $ES_BRANCH"
|
||
|
||
#------------------------------------------------------------------------------
|
||
# Step 1: Clone / update EmulationStation source on host
|
||
#------------------------------------------------------------------------------
|
||
log ""
|
||
log "Step 1: Getting EmulationStation source..."
|
||
|
||
mkdir -p "$CACHE_DIR"
|
||
|
||
if [ -d "$ES_CACHE/.git" ]; then
|
||
log " Updating existing clone..."
|
||
cd "$ES_CACHE"
|
||
git fetch origin
|
||
git checkout "$ES_BRANCH"
|
||
git reset --hard "origin/$ES_BRANCH"
|
||
git submodule update --init --recursive
|
||
cd "$SCRIPT_DIR"
|
||
else
|
||
log " Cloning EmulationStation-fcamod..."
|
||
git clone --depth 1 --recurse-submodules -b "$ES_BRANCH" "$ES_REPO" "$ES_CACHE"
|
||
fi
|
||
|
||
log " ✓ Source ready"
|
||
|
||
#------------------------------------------------------------------------------
|
||
# Step 2: Copy source into rootfs for native build
|
||
#------------------------------------------------------------------------------
|
||
log ""
|
||
log "Step 2: Setting up build environment in rootfs..."
|
||
|
||
BUILD_DIR="$ROOTFS_DIR/tmp/es-build"
|
||
rm -rf "$BUILD_DIR"
|
||
cp -a "$ES_CACHE" "$BUILD_DIR"
|
||
|
||
log " ✓ Source copied to rootfs"
|
||
|
||
#------------------------------------------------------------------------------
|
||
# Step 2b: GLES 1.0 native — no gl4es needed
|
||
#------------------------------------------------------------------------------
|
||
log ""
|
||
log "Step 2b: GLES 1.0 native mode — gl4es NOT needed"
|
||
log " ES will be built with -DGLES=ON (Renderer_GLES10.cpp)"
|
||
log " Rendering: ES (GLES 1.0) → Mesa EGL → Panfrost (Mali-G31)"
|
||
log " Mesa provides GLES 1.0 headers (GLES/gl.h) and libGLESv1_CM.so"
|
||
|
||
#------------------------------------------------------------------------------
|
||
# Step 3: Setup chroot
|
||
#------------------------------------------------------------------------------
|
||
log ""
|
||
log "Step 3: Setting up chroot..."
|
||
|
||
# Copy QEMU
|
||
if [ -f "/usr/bin/qemu-aarch64-static" ]; then
|
||
cp /usr/bin/qemu-aarch64-static "$ROOTFS_DIR/usr/bin/"
|
||
else
|
||
error "qemu-aarch64-static not found. Install: sudo apt install qemu-user-static"
|
||
fi
|
||
|
||
# Bind mounts
|
||
mount --bind /dev "$ROOTFS_DIR/dev" 2>/dev/null || true
|
||
mount --bind /dev/pts "$ROOTFS_DIR/dev/pts" 2>/dev/null || true
|
||
mount --bind /proc "$ROOTFS_DIR/proc" 2>/dev/null || true
|
||
mount --bind /sys "$ROOTFS_DIR/sys" 2>/dev/null || true
|
||
mount --bind /run "$ROOTFS_DIR/run" 2>/dev/null || true
|
||
cp /etc/resolv.conf "$ROOTFS_DIR/etc/resolv.conf"
|
||
|
||
log " ✓ Chroot ready"
|
||
|
||
#------------------------------------------------------------------------------
|
||
# Step 4: Build inside chroot
|
||
#------------------------------------------------------------------------------
|
||
log ""
|
||
log "Step 4: Building EmulationStation inside chroot..."
|
||
|
||
cat > "$ROOTFS_DIR/tmp/build-es.sh" << 'BUILD_EOF'
|
||
#!/bin/bash
|
||
set -e
|
||
|
||
# Disable pacman Landlock sandbox (fails in QEMU chroot)
|
||
pacman() { command pacman --disable-sandbox "$@"; }
|
||
|
||
echo "=== ES Build: Installing dependencies ==="
|
||
|
||
# Build dependencies (freeimage excluded — built from source below)
|
||
# Do NOT use --needed! build-rootfs.sh bloat cleanup deletes gcc/make/cmake/git
|
||
# binaries to save space, but pacman DB still thinks they're installed.
|
||
# Without --needed, pacman force-reinstalls and restores the actual binaries.
|
||
#
|
||
# Mesa 26 conflict: gcc/base-devel pull in pacman's mesa as dependency,
|
||
# which conflicts with our custom Mesa 26 files (GBM, EGL headers, drirc).
|
||
# Solution: save Mesa 26 files → let pacman overwrite → restore immediately.
|
||
|
||
echo " Saving ALL Mesa 26 files (EGL, GLES, GBM, gallium, DRI)..."
|
||
mkdir -p /tmp/mesa26-save/lib/gbm /tmp/mesa26-save/lib/dri /tmp/mesa26-save/lib/pkgconfig \
|
||
/tmp/mesa26-save/include/EGL /tmp/mesa26-save/include/GLES /tmp/mesa26-save/include/GLES2 \
|
||
/tmp/mesa26-save/include/GLES3 /tmp/mesa26-save/include/GL/internal \
|
||
/tmp/mesa26-save/include/KHR /tmp/mesa26-save/share/drirc.d
|
||
# Libraries (real files + symlinks)
|
||
cp -a /usr/lib/libEGL.so* /tmp/mesa26-save/lib/ 2>/dev/null || true
|
||
cp -a /usr/lib/libGLESv1_CM.so* /tmp/mesa26-save/lib/ 2>/dev/null || true
|
||
cp -a /usr/lib/libGLESv2.so* /tmp/mesa26-save/lib/ 2>/dev/null || true
|
||
cp -a /usr/lib/libgbm* /tmp/mesa26-save/lib/ 2>/dev/null || true
|
||
cp -a /usr/lib/libgallium-26*.so* /tmp/mesa26-save/lib/ 2>/dev/null || true
|
||
cp -a /usr/lib/gbm/* /tmp/mesa26-save/lib/gbm/ 2>/dev/null || true
|
||
cp -a /usr/lib/dri/* /tmp/mesa26-save/lib/dri/ 2>/dev/null || true
|
||
# pkg-config
|
||
cp -a /usr/lib/pkgconfig/gbm.pc /usr/lib/pkgconfig/dri.pc \
|
||
/usr/lib/pkgconfig/egl.pc /usr/lib/pkgconfig/glesv1_cm.pc \
|
||
/usr/lib/pkgconfig/glesv2.pc /tmp/mesa26-save/lib/pkgconfig/ 2>/dev/null || true
|
||
# Headers
|
||
cp -a /usr/include/EGL/* /tmp/mesa26-save/include/EGL/ 2>/dev/null || true
|
||
cp -a /usr/include/GLES/* /tmp/mesa26-save/include/GLES/ 2>/dev/null || true
|
||
cp -a /usr/include/GLES2/* /tmp/mesa26-save/include/GLES2/ 2>/dev/null || true
|
||
cp -a /usr/include/GLES3/* /tmp/mesa26-save/include/GLES3/ 2>/dev/null || true
|
||
cp -a /usr/include/GL/internal/* /tmp/mesa26-save/include/GL/internal/ 2>/dev/null || true
|
||
cp -a /usr/include/KHR/* /tmp/mesa26-save/include/KHR/ 2>/dev/null || true
|
||
cp -a /usr/include/gbm.h /usr/include/gbm_backend_abi.h /tmp/mesa26-save/include/ 2>/dev/null || true
|
||
# drirc
|
||
cp -a /usr/share/drirc.d/* /tmp/mesa26-save/share/drirc.d/ 2>/dev/null || true
|
||
|
||
# Overwrite patterns: let pacman install its mesa files, we'll restore ours after
|
||
MESA_OVERWRITE=(
|
||
--overwrite '/usr/lib/libEGL*'
|
||
--overwrite '/usr/lib/libGLES*'
|
||
--overwrite '/usr/lib/libGL.so*'
|
||
--overwrite '/usr/lib/libOpenGL*'
|
||
--overwrite '/usr/lib/libGLX*'
|
||
--overwrite '/usr/lib/libgallium*'
|
||
--overwrite '/usr/lib/libgbm*'
|
||
--overwrite '/usr/lib/gbm/*'
|
||
--overwrite '/usr/lib/dri/*'
|
||
--overwrite '/usr/lib/pkgconfig/dri.pc'
|
||
--overwrite '/usr/lib/pkgconfig/gbm.pc'
|
||
--overwrite '/usr/lib/pkgconfig/egl.pc'
|
||
--overwrite '/usr/lib/pkgconfig/glesv*'
|
||
--overwrite '/usr/include/EGL/*'
|
||
--overwrite '/usr/include/GLES/*'
|
||
--overwrite '/usr/include/GLES2/*'
|
||
--overwrite '/usr/include/GLES3/*'
|
||
--overwrite '/usr/include/GL/internal/*'
|
||
--overwrite '/usr/include/KHR/*'
|
||
--overwrite '/usr/include/gbm*'
|
||
--overwrite '/usr/share/drirc.d/*'
|
||
)
|
||
|
||
pacman -S --noconfirm "${MESA_OVERWRITE[@]}" base-devel
|
||
|
||
# Then install specific build dependencies
|
||
# glibc + linux-api-headers: rootfs bloat cleanup deletes /usr/include entirely
|
||
pacman -S --noconfirm "${MESA_OVERWRITE[@]}" \
|
||
make \
|
||
gcc \
|
||
glibc \
|
||
linux-api-headers \
|
||
cmake \
|
||
git \
|
||
unzip \
|
||
sdl2 \
|
||
sdl2_mixer \
|
||
freetype2 \
|
||
curl \
|
||
rapidjson \
|
||
boost \
|
||
pugixml \
|
||
alsa-lib \
|
||
libdrm
|
||
|
||
echo " Restoring Mesa 26 files..."
|
||
# Remove pacman's conflicting Mesa files first
|
||
rm -f /usr/lib/libEGL.so.1.1.0 /usr/lib/libEGL_mesa.so* 2>/dev/null || true
|
||
rm -f /usr/lib/libGLESv2.so.2.1.0 2>/dev/null || true
|
||
rm -f /usr/lib/libGL.so* /usr/lib/libOpenGL.so* /usr/lib/libGLX.so* 2>/dev/null || true
|
||
rm -f /usr/lib/libgallium-[0-9]*.so 2>/dev/null || true
|
||
# Restore ALL Mesa 26 files (cp -a preserves symlinks correctly)
|
||
cp -a /tmp/mesa26-save/lib/libEGL.so* /usr/lib/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/lib/libGLESv1_CM.so* /usr/lib/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/lib/libGLESv2.so* /usr/lib/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/lib/libgbm* /usr/lib/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/lib/libgallium-26*.so* /usr/lib/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/lib/gbm/* /usr/lib/gbm/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/lib/dri/* /usr/lib/dri/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/lib/pkgconfig/*.pc /usr/lib/pkgconfig/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/include/EGL/* /usr/include/EGL/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/include/GLES/* /usr/include/GLES/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/include/GLES2/* /usr/include/GLES2/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/include/GLES3/* /usr/include/GLES3/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/include/GL/internal/* /usr/include/GL/internal/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/include/KHR/* /usr/include/KHR/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/include/gbm.h /tmp/mesa26-save/include/gbm_backend_abi.h /usr/include/ 2>/dev/null || true
|
||
cp -a /tmp/mesa26-save/share/drirc.d/* /usr/share/drirc.d/ 2>/dev/null || true
|
||
rm -rf /tmp/mesa26-save
|
||
# Verify critical symlinks
|
||
echo " EGL: $(readlink /usr/lib/libEGL.so.1 2>/dev/null) (expect: libEGL.so.1.0.0)"
|
||
echo " GLES2: $(readlink /usr/lib/libGLESv2.so.2 2>/dev/null) (expect: libGLESv2.so.2.0.0)"
|
||
echo " gallium: $(ls /usr/lib/libgallium-26*.so 2>/dev/null | head -1)"
|
||
echo " dri_gbm: $(readelf -d /usr/lib/gbm/dri_gbm.so 2>/dev/null | grep gallium | awk '{print $NF}')"
|
||
ldconfig
|
||
echo " ✓ Mesa 26 files restored (EGL, GLES, GBM, gallium, DRI)"
|
||
# NOTE: vlc removed — it pulls in mesa (conflicts with our Mesa 26) and 865MB of bloat.
|
||
# ES cmake has FATAL_ERROR if VLC not found, so we install a stub libvlc.so + headers
|
||
# below (before cmake). At runtime, libvlc_new()→NULL, video backgrounds disabled.
|
||
|
||
# GPU: Mesa 26 Panfrost is pre-installed by build-mesa.sh (NOT from pacman!)
|
||
# Do NOT install mesa from pacman — it conflicts with our custom Mesa 26.
|
||
# Our Mesa provides: libEGL.so, libGLESv1_CM.so, libGLESv2.so, Panfrost driver
|
||
|
||
# Build FreeImage from source (not available in ALARM aarch64 repos)
|
||
if ! pacman -Q freeimage &>/dev/null; then
|
||
echo "=== Building FreeImage from source ==="
|
||
cd /tmp
|
||
rm -rf FreeImage FreeImage3180.zip
|
||
curl -L -o FreeImage3180.zip \
|
||
"https://downloads.sourceforge.net/project/freeimage/Source%20Distribution/3.18.0/FreeImage3180.zip"
|
||
unzip -oq FreeImage3180.zip
|
||
cd FreeImage
|
||
# Patch Makefile for modern GCC compatibility (FreeImage 3.18.0 is old):
|
||
# - C++14: bundled OpenEXR uses throw() specs removed in C++17
|
||
# - unistd.h: bundled ZLib uses lseek/read/write/close without including it
|
||
# - Wno flags: suppress implicit-function-declaration errors in bundled libs
|
||
cat >> Makefile.gnu << 'MKPATCH'
|
||
override CFLAGS += -include unistd.h -Wno-implicit-function-declaration -Wno-int-conversion -DPNG_ARM_NEON_OPT=0
|
||
override CXXFLAGS += -std=c++14 -include unistd.h -DPNG_ARM_NEON_OPT=0
|
||
MKPATCH
|
||
make -j2
|
||
make install
|
||
ldconfig
|
||
cd /tmp && rm -rf FreeImage FreeImage3180.zip
|
||
echo " FreeImage built and installed"
|
||
fi
|
||
|
||
echo "=== ES Build: Rebuilding SDL3 with KMSDRM support ==="
|
||
|
||
# CRITICAL: ALARM's SDL3 package is built WITHOUT KMSDRM video backend.
|
||
# Without KMSDRM, SDL can only use x11/wayland/offscreen/dummy — none work
|
||
# on our console-only RK3326. We need to rebuild SDL3 with -DSDL_KMSDRM=ON.
|
||
# sdl2-compat (provides libSDL2) wraps SDL3, so it gains KMSDRM automatically.
|
||
|
||
if ! grep -ao 'kmsdrm' /usr/lib/libSDL3.so* 2>/dev/null | grep -qi kmsdrm; then
|
||
echo " SDL3 missing KMSDRM support — rebuilding from source..."
|
||
pacman -S --noconfirm --needed cmake meson ninja pkgconf libdrm
|
||
|
||
# Get the installed SDL3 version to build the matching release
|
||
SDL3_VER=$(pacman -Q sdl3 2>/dev/null | awk '{print $2}' | cut -d- -f1)
|
||
echo " System SDL3 version: $SDL3_VER"
|
||
|
||
cd /tmp
|
||
rm -rf SDL3-kmsdrm-build
|
||
|
||
# Clone matching version (or latest release if version detection fails)
|
||
if [ -n "$SDL3_VER" ]; then
|
||
git clone --depth 1 -b "release-${SDL3_VER}" \
|
||
https://github.com/libsdl-org/SDL.git SDL3-kmsdrm-build 2>/dev/null \
|
||
|| git clone --depth 1 https://github.com/libsdl-org/SDL.git SDL3-kmsdrm-build
|
||
else
|
||
git clone --depth 1 https://github.com/libsdl-org/SDL.git SDL3-kmsdrm-build
|
||
fi
|
||
|
||
cd SDL3-kmsdrm-build
|
||
cmake -B build \
|
||
-DCMAKE_BUILD_TYPE=Release \
|
||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||
-DSDL_KMSDRM=ON \
|
||
-DSDL_KMSDRM_SHARED=OFF \
|
||
-DSDL_WAYLAND=OFF \
|
||
-DSDL_X11=OFF \
|
||
-DSDL_VULKAN=OFF \
|
||
-DSDL_PIPEWIRE=OFF \
|
||
-DSDL_PULSEAUDIO=OFF \
|
||
-DSDL_ALSA=ON \
|
||
-DSDL_TESTS=OFF \
|
||
-DSDL_INSTALL_TESTS=OFF
|
||
|
||
cmake --build build -j$(nproc)
|
||
|
||
# Only replace the shared library (keep headers/pkgconfig from package)
|
||
install -m755 build/libSDL3.so.0.* /usr/lib/
|
||
ldconfig
|
||
|
||
cd /tmp && rm -rf SDL3-kmsdrm-build
|
||
|
||
# Verify KMSDRM is now available
|
||
if grep -ao 'kmsdrm' /usr/lib/libSDL3.so* 2>/dev/null | grep -qi kmsdrm; then
|
||
echo " SDL3 rebuilt with KMSDRM support — VERIFIED"
|
||
else
|
||
echo " WARNING: SDL3 rebuild done but KMSDRM still not found!"
|
||
fi
|
||
else
|
||
echo " SDL3 already has KMSDRM support — skipping rebuild"
|
||
fi
|
||
|
||
echo "=== ES Build: Verifying GLES 1.0 support ==="
|
||
|
||
# GLES 1.0 native — ES needs GLES/gl.h header and libGLESv1_CM.so library
|
||
# Mesa 26 must be built with -Dgles1=enabled -Dglvnd=false (run build-mesa.sh first!)
|
||
# With glvnd=false, Mesa directly installs libGLESv1_CM.so (not via libglvnd dispatch)
|
||
|
||
# If GLES 1.0 headers missing, install from Khronos registry
|
||
if [ ! -f /usr/include/GLES/gl.h ]; then
|
||
echo " GLES/gl.h not found — installing Khronos GLES 1.0 headers..."
|
||
mkdir -p /usr/include/GLES
|
||
curl -sL "https://registry.khronos.org/OpenGL/api/GLES/gl.h" -o /usr/include/GLES/gl.h
|
||
curl -sL "https://registry.khronos.org/OpenGL/api/GLES/glext.h" -o /usr/include/GLES/glext.h
|
||
curl -sL "https://registry.khronos.org/OpenGL/api/GLES/glplatform.h" -o /usr/include/GLES/glplatform.h
|
||
echo " Installed GLES 1.0 headers from Khronos"
|
||
fi
|
||
|
||
echo " GLES headers: $(ls /usr/include/GLES/gl.h 2>/dev/null || echo 'MISSING!')"
|
||
echo " libGLESv1_CM: $(ls /usr/lib/libGLESv1_CM.so* 2>/dev/null | head -1 || echo 'MISSING!')"
|
||
echo " libEGL: $(ls /usr/lib/libEGL.so* 2>/dev/null | head -1)"
|
||
|
||
if [ ! -f /usr/include/GLES/gl.h ]; then
|
||
echo "ERROR: GLES/gl.h not found! Run build-mesa.sh first."
|
||
exit 1
|
||
fi
|
||
if [ ! -f /usr/lib/libGLESv1_CM.so ] && [ ! -f /usr/lib/libGLESv1_CM.so.1 ]; then
|
||
echo "ERROR: libGLESv1_CM.so not found! Mesa must be built with -Dgles1=enabled -Dglvnd=false"
|
||
exit 1
|
||
fi
|
||
echo " ✓ GLES 1.0 verified"
|
||
|
||
echo "=== ES Build: Patching Renderer_GLES10.cpp for Panfrost ==="
|
||
|
||
cd /tmp/es-build
|
||
|
||
# Renderer_GLES10.cpp uses GLES 1.0 fixed-function pipeline (glVertexPointer etc.)
|
||
# Mesa Panfrost provides GLES 1.0 via internal TNL (fixed-function → shader translation)
|
||
# This is MUCH faster than gl4es: single translation at driver level vs double translation
|
||
|
||
# Patch 1: Remove go2/audio.h dependency (OGA-specific, all go2 code already commented out)
|
||
sed -i 's|#include <go2/audio.h>|// #include <go2/audio.h> // Removed: OGA-specific, unused|' \
|
||
es-core/src/renderers/Renderer_GLES10.cpp
|
||
echo " Patch 1: Removed go2/audio.h dependency"
|
||
|
||
# Patch 2: Fix CONTEXT_MAJOR_VERSION bug in setupWindow().
|
||
# Original: sets MAJOR_VERSION twice. Second should be MINOR_VERSION.
|
||
# GLES 1.0 context: MAJOR=1, MINOR=0
|
||
sed -i 's/SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 0);/SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);/' \
|
||
es-core/src/renderers/Renderer_GLES10.cpp
|
||
echo " Patch 2: Fixed CONTEXT_MAJOR→MINOR bug"
|
||
|
||
# Patch 3: Add GLES context profile (needed for SDL3/sdl2-compat on KMSDRM)
|
||
# Without this, SDL may request Desktop GL context → EGL fails on Panfrost
|
||
sed -i '/SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);/i\\t\tSDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);' \
|
||
es-core/src/renderers/Renderer_GLES10.cpp
|
||
echo " Patch 3: Added GLES context profile for KMSDRM"
|
||
|
||
# Patch 4: Null safety for glGetString in createContext().
|
||
# If context creation fails, glGetString returns NULL → std::string(NULL) throws SIGABRT
|
||
sed -i 's|std::string glExts = (const char\*)glGetString(GL_EXTENSIONS);|const char* extsPtr = (const char*)glGetString(GL_EXTENSIONS); std::string glExts = extsPtr ? extsPtr : "";|' \
|
||
es-core/src/renderers/Renderer_GLES10.cpp
|
||
echo " Patch 4: Null safety for glGetString"
|
||
|
||
# Patch 5: Re-establish GL context in setSwapInterval().
|
||
# setIcon() via sdl2-compat loses the EGL context → all rendering fails silently
|
||
sed -i '/\t\t\/\/ vsync/i\\t\t// Arch R: Re-establish GL context — setIcon() via sdl2-compat loses it\n\t\tSDL_GL_MakeCurrent(getSDLWindow(), sdlContext);' \
|
||
es-core/src/renderers/Renderer_GLES10.cpp
|
||
echo " Patch 5: GL context restore in setSwapInterval"
|
||
|
||
echo " ✓ Patched Renderer_GLES10.cpp (5 patches)"
|
||
|
||
# --- Performance patches (FPS stability) ---
|
||
|
||
# Patch 8: Remove depth buffer — ES is a 2D UI, never enables GL_DEPTH_TEST.
|
||
# Saves GPU bandwidth: no D24 attachment, no depth load/store per tile on Mali-G31.
|
||
# 24-bit depth at 640x480 = 921,600 bytes wasted per frame clear.
|
||
sed -i 's/SDL_GL_DEPTH_SIZE, 24/SDL_GL_DEPTH_SIZE, 0/' \
|
||
es-core/src/renderers/Renderer_GLES10.cpp
|
||
echo " Patch 8: Removed depth buffer (24 → 0)"
|
||
|
||
# Patch 9: Add explicit stencil buffer — needed for rounded corners (enableRoundCornerStencil).
|
||
# Without explicit request, removing depth may also remove stencil (D24S8 is a packed format).
|
||
sed -i '/SDL_GL_DEPTH_SIZE, 0/a\\t\tSDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);' \
|
||
es-core/src/renderers/Renderer_GLES10.cpp
|
||
echo " Patch 9: Added stencil buffer (for rounded corners)"
|
||
|
||
# Patch 10: Remove depth clear from swapBuffers — no depth buffer, nothing to clear.
|
||
sed -i 's/glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)/glClear(GL_COLOR_BUFFER_BIT)/' \
|
||
es-core/src/renderers/Renderer_GLES10.cpp
|
||
echo " Patch 10: Removed depth clear from swapBuffers"
|
||
|
||
# Patch 11: Remove depth clear from enableRoundCornerStencil — depth not used.
|
||
# Only removes standalone GL_DEPTH_BUFFER_BIT clear (line 403), NOT stencil clear (line 411).
|
||
sed -i '/glClear(GL_DEPTH_BUFFER_BIT);/d' \
|
||
es-core/src/renderers/Renderer_GLES10.cpp
|
||
echo " Patch 11: Removed depth clear from enableRoundCornerStencil"
|
||
|
||
# Patch 12: Explicitly disable depth test in createContext — belt and suspenders.
|
||
# With DEPTH_SIZE=0 the driver shouldn't allocate depth, but this ensures no depth pipeline.
|
||
sed -i '/glClearColor(0.0f, 0.0f, 0.0f, 0.0f);/a\\n\t\t// Arch R: No depth testing — 2D UI only. Saves GPU fill rate.\n\t\tglDisable(GL_DEPTH_TEST);\n\t\tglDepthMask(GL_FALSE);' \
|
||
es-core/src/renderers/Renderer_GLES10.cpp
|
||
echo " Patch 12: Disabled depth test + depth writes in createContext"
|
||
|
||
echo " ✓ Performance patches applied (8-12)"
|
||
|
||
# Patch 13: Eliminate popen() from brightness polling — THE MAIN FPS KILLER.
|
||
# BrightnessInfoComponent polls getBrightnessLevel() every 40ms (CHECKBRIGHTNESSDELAY=40).
|
||
# When brightnessctl exists, it calls popen("brightnessctl -m | awk ...") = fork() 25x/second.
|
||
# Each fork() on ARM takes 2-5ms, easily pushing frames over 16.67ms → dropped frames.
|
||
# Fix: force mExistBrightnessctl=false → uses sysfs fallback (open/read/close, microseconds).
|
||
# The sysfs path already exists in the code (lines 104-138) and works perfectly on R36S.
|
||
sed -i 's|mExistBrightnessctl = Utils::FileSystem::exists(BRIGHTNESSCTL_PATH);|mExistBrightnessctl = false; // Arch R: force sysfs direct reads (no popen overhead)|' \
|
||
es-core/src/DisplayPanelControl.cpp
|
||
echo " Patch 13: Eliminated popen() from brightness polling (sysfs direct)"
|
||
|
||
# Patch 14: Increase brightness/volume polling intervals.
|
||
# Original: 40ms (25 polls/sec) — way too aggressive for info bar display.
|
||
# New: 500ms for brightness (sysfs is fast but no need to poll 25x/sec)
|
||
# 200ms for volume (ALSA mixer is fast, 5x/sec is responsive enough)
|
||
sed -i 's/#define CHECKBRIGHTNESSDELAY\t40/#define CHECKBRIGHTNESSDELAY\t500/' \
|
||
es-core/src/components/BrightnessInfoComponent.cpp
|
||
sed -i 's/#define CHECKVOLUMEDELAY\t40/#define CHECKVOLUMEDELAY\t200/' \
|
||
es-core/src/components/VolumeInfoComponent.cpp
|
||
echo " Patch 14: Reduced polling intervals (brightness 500ms, volume 200ms)"
|
||
|
||
# Patch 6: Null safety for getShOutput() — popen() can return NULL.
|
||
# Without this check, fgets(buffer, size, NULL) → SIGSEGV/SIGABRT.
|
||
# GuiMenu calls getShOutput() for battery, volume, brightness, WiFi info.
|
||
sed -i 's|FILE\* pipe{popen(mStr.c_str(), "r")};|FILE* pipe{popen(mStr.c_str(), "r")};\n if (!pipe) return "";|' \
|
||
es-core/src/platform.cpp
|
||
echo " Patched platform.cpp (getShOutput NULL safety)"
|
||
|
||
# Patch 7: Language change triggers ES restart instead of in-place menu reload.
|
||
# Original: s->setVariable("reloadGuiMenu", true) → delete/new GuiMenu in close()
|
||
# This causes SIGABRT (exit 134) on KMSDRM due to use-after-free or GL context loss.
|
||
# Fix: quitES(QuitMode::RESTART) → clean ES exit → shell loop restarts with new language.
|
||
sed -i '/setString("Language", language->getSelected())/{n;s|s->setVariable("reloadGuiMenu", true);|quitES(QuitMode::RESTART);|}' \
|
||
es-app/src/guis/GuiMenu.cpp
|
||
echo " Patched GuiMenu.cpp (language change → ES restart)"
|
||
|
||
# ==========================================================================
|
||
# Boot-time optimizations (patches 15-17) — Research-based, safe
|
||
# ==========================================================================
|
||
|
||
echo ""
|
||
echo "=== Applying boot-time optimizations ==="
|
||
|
||
THEME_CPP="es-core/src/ThemeData.cpp"
|
||
SYSDATA_CPP="es-app/src/SystemData.cpp"
|
||
TEXTURE_CPP="es-core/src/resources/TextureData.cpp"
|
||
|
||
# Patch 15: Cache getThemeSets() — THE BIGGEST WIN.
|
||
# getThemeSets() enumerates /etc/emulationstation/themes + ~/.emulationstation/themes
|
||
# via getDirContent() on EVERY call. Called per-system during loadTheme() (19+ times).
|
||
# Fix: static cache. First call populates, subsequent calls return cached result.
|
||
# Thread safety: pre-cache on main thread before ThreadPool starts.
|
||
#
|
||
# 15a: Add static cache to getThemeSets()
|
||
sed -i '/ThemeData::getThemeSets()/{n; /^{$/a\\tstatic bool sCached = false; static std::map<std::string, ThemeSet> sCache;\n\tif (sCached) return sCache;
|
||
}' "$THEME_CPP"
|
||
# 15b: Save cache before return in getThemeSets()
|
||
sed -i '/ThemeData::getThemeSets/,/^}/ {
|
||
/\treturn sets;/i\\tsCached = true; sCache = sets;
|
||
}' "$THEME_CPP"
|
||
# 15c: Pre-cache on main thread before ThreadPool (avoids thread race)
|
||
sed -i '/ThreadPool\* pThreadPool = NULL;/i\\t\/\/ Arch R: pre-cache theme sets before parallel loading\n\tThemeData::getThemeSets();' \
|
||
"$SYSDATA_CPP"
|
||
echo " Patch 15: Cache getThemeSets() (19+ dir scans → 1)"
|
||
|
||
# Patch 16: Remove redundant readList() — dead code.
|
||
# SystemData.cpp loadSystem(): line creates vector 'list' that is NEVER used.
|
||
# The actual loop 4 lines later calls readList() again independently.
|
||
sed -i '/std::vector<std::string> list = readList(system.child("extension").text().get());/d' \
|
||
"$SYSDATA_CPP"
|
||
echo " Patch 16: Removed dead readList() call"
|
||
|
||
# Patch 17: NanoSVG — reuse static rasterizer instead of alloc/free per SVG.
|
||
# Current: nsvgCreateRasterizer() + nsvgDeleteRasterizer() for EVERY SVG file.
|
||
# Fix: static rasterizer, allocated once, reused for all SVGs.
|
||
sed -i 's/NSVGrasterizer\* rast = nsvgCreateRasterizer();/static NSVGrasterizer* rast = nsvgCreateRasterizer(); \/\/ Arch R: reuse/' \
|
||
"$TEXTURE_CPP"
|
||
sed -i 's/nsvgDeleteRasterizer(rast);/\/\/ nsvgDeleteRasterizer(rast); \/\/ Arch R: static, reused/' \
|
||
"$TEXTURE_CPP"
|
||
echo " Patch 17: NanoSVG static rasterizer (saves alloc/free per SVG)"
|
||
|
||
# ==========================================================================
|
||
# Audit optimizations (patches 18-21) — Based on full source code audit
|
||
# ==========================================================================
|
||
|
||
echo ""
|
||
echo "=== Applying audit-based optimizations ==="
|
||
|
||
MAIN_CPP="es-app/src/main.cpp"
|
||
MAME_CPP="es-core/src/MameNames.cpp"
|
||
|
||
# Patch 18: Boot profiling — timestamps at key boot stages.
|
||
# Writes to stderr (captured in ~/es-debug.log by emulationstation.sh).
|
||
# Uses clock_gettime(CLOCK_MONOTONIC) for sub-ms precision.
|
||
cat > es-core/src/ArchrProfile.h << 'PROFEOF'
|
||
#pragma once
|
||
#include <time.h>
|
||
#include <stdio.h>
|
||
static struct timespec _archr_t0;
|
||
static inline void archr_profile_init() {
|
||
clock_gettime(CLOCK_MONOTONIC, &_archr_t0);
|
||
}
|
||
static inline void archr_profile(const char* s) {
|
||
struct timespec n;
|
||
clock_gettime(CLOCK_MONOTONIC, &n);
|
||
fprintf(stderr, "[BOOT %8.1fms] %s\n",
|
||
(n.tv_sec - _archr_t0.tv_sec) * 1000.0 +
|
||
(n.tv_nsec - _archr_t0.tv_nsec) / 1000000.0, s);
|
||
}
|
||
PROFEOF
|
||
sed -i '/#include "MameNames.h"/a\#include "ArchrProfile.h"' "$MAIN_CPP"
|
||
sed -i '/LOG(LogInfo) << "EmulationStation - v"/a\\tarchr_profile_init(); archr_profile("start");' "$MAIN_CPP"
|
||
sed -i '/if (!scrape_cmdline)/i\\tarchr_profile("before window.init");' "$MAIN_CPP"
|
||
sed -i '/if(!loadSystemConfigFile/i\\t\tarchr_profile("before loadConfig");' "$MAIN_CPP"
|
||
sed -i '/\/\/run the command line scraper/i\\tarchr_profile("after loadConfig");' "$MAIN_CPP"
|
||
sed -i '/window.endRenderLoadingScreen/i\\tarchr_profile("UI ready");' "$MAIN_CPP"
|
||
echo " Patch 18: Boot profiling (5 timestamps → stderr/es-debug.log)"
|
||
|
||
# Patch 19: ThreadPool wait interval 10→500ms.
|
||
# renderLoadingScreen() calls swapBuffers() which blocks on VSync (~13ms at 78Hz).
|
||
# At 10ms interval: ~130 iterations × 13ms VSync = ~1.7s wasted on progress bar.
|
||
# At 500ms interval: ~6 iterations × 13ms = 78ms. Progress bar still updates, ~1.5s saved.
|
||
sed -i 's/}, 10);/}, 500); \/\/ Arch R: reduce VSync overhead (was 10ms)/' "$SYSDATA_CPP"
|
||
echo " Patch 19: ThreadPool wait 10→500ms (saves ~1.5s VSync overhead)"
|
||
|
||
# Patch 20: Skip non-existent ROM directories early.
|
||
# loadSystem() creates SystemData + FolderData + calls opendir() for each system,
|
||
# even if the directory doesn't exist. Then deletes everything.
|
||
# Fix: quick isDirectory() stat BEFORE allocating objects.
|
||
sed -i '/\/\/create the system runtime environment data/i\\t// Arch R: Skip non-existent directories (avoids alloc + opendir on missing paths)\n\tif (!Utils::FileSystem::isDirectory(path))\n\t\treturn nullptr;\n' "$SYSDATA_CPP"
|
||
echo " Patch 20: Skip non-existent ROM directories (fast stat before alloc)"
|
||
|
||
# Patch 21: MameNames lazy init with std::call_once.
|
||
# MameNames::init() at boot parses 3 XML files (mamenames, bioses, devices).
|
||
# Costs 50-100ms on ARM, only needed for arcade systems.
|
||
# Fix: remove eager init from main(), use thread-safe lazy init on first access.
|
||
sed -i '/#include <string.h>/a\#include <mutex>' "$MAME_CPP"
|
||
sed -i '/MameNames\* MameNames::sInstance = nullptr;/a\static std::once_flag sMameNamesOnce;' "$MAME_CPP"
|
||
sed -i '/MameNames\* MameNames::getInstance/,/} \/\/ getInstance/ {
|
||
/if(!sInstance)/d
|
||
}' "$MAME_CPP"
|
||
sed -i '/MameNames\* MameNames::getInstance/,/} \/\/ getInstance/ {
|
||
s/sInstance = new MameNames();/std::call_once(sMameNamesOnce, []() { sInstance = new MameNames(); });/
|
||
}' "$MAME_CPP"
|
||
sed -i 's/MameNames::init();/\/\/ MameNames::init(); \/\/ Arch R: deferred to lazy call_once/' "$MAIN_CPP"
|
||
echo " Patch 21: MameNames lazy init (std::call_once, thread-safe)"
|
||
|
||
echo "=== ES Build: Installing VLC stub ==="
|
||
|
||
# VLC is NOT installed (pulls in mesa + 865MB of bloat).
|
||
# ES-fcamod deeply integrates VideoVlcComponent (9 files, 8 constructor calls).
|
||
# Solution: provide complete stub headers + library so cmake and gcc are happy.
|
||
# At runtime: libvlc_new()→NULL, ES disables video backgrounds gracefully.
|
||
|
||
mkdir -p /usr/include/vlc
|
||
|
||
# Complete VLC stub header with all types ES uses
|
||
cat > /usr/include/vlc/vlc.h << 'VLCHDR'
|
||
#ifndef VLC_VLC_H
|
||
#define VLC_VLC_H
|
||
#include <vlc/libvlc.h>
|
||
#include <vlc/libvlc_media.h>
|
||
#include <vlc/libvlc_media_player.h>
|
||
#endif
|
||
VLCHDR
|
||
|
||
cat > /usr/include/vlc/libvlc.h << 'VLCCORE'
|
||
#ifndef VLC_LIBVLC_H
|
||
#define VLC_LIBVLC_H
|
||
#include <stddef.h>
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
typedef struct libvlc_instance_t libvlc_instance_t;
|
||
libvlc_instance_t *libvlc_new(int argc, const char *const *argv);
|
||
void libvlc_release(libvlc_instance_t *p);
|
||
const char *libvlc_get_version(void);
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
#endif
|
||
VLCCORE
|
||
|
||
cat > /usr/include/vlc/libvlc_media.h << 'VLCMEDIA'
|
||
#ifndef VLC_LIBVLC_MEDIA_H
|
||
#define VLC_LIBVLC_MEDIA_H
|
||
#include <vlc/libvlc.h>
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
typedef struct libvlc_media_t libvlc_media_t;
|
||
|
||
typedef enum {
|
||
libvlc_media_parse_local = 0x00,
|
||
libvlc_media_parse_network = 0x01,
|
||
libvlc_media_fetch_local = 0x02,
|
||
libvlc_media_fetch_network = 0x04
|
||
} libvlc_media_parse_flag_t;
|
||
|
||
typedef enum {
|
||
libvlc_media_parsed_status_skipped = 1,
|
||
libvlc_media_parsed_status_failed,
|
||
libvlc_media_parsed_status_timeout,
|
||
libvlc_media_parsed_status_done
|
||
} libvlc_media_parsed_status_t;
|
||
|
||
typedef enum {
|
||
libvlc_track_unknown = -1,
|
||
libvlc_track_audio = 0,
|
||
libvlc_track_video = 1,
|
||
libvlc_track_text = 2
|
||
} libvlc_track_type_t;
|
||
|
||
typedef struct { unsigned i_channels; unsigned i_rate; } libvlc_audio_track_t;
|
||
typedef struct { unsigned i_height; unsigned i_width; } libvlc_video_track_t;
|
||
typedef struct { const char *psz_encoding; } libvlc_subtitle_track_t;
|
||
|
||
typedef struct libvlc_media_track_t {
|
||
unsigned i_codec;
|
||
unsigned i_original_fourcc;
|
||
int i_id;
|
||
libvlc_track_type_t i_type;
|
||
int i_profile;
|
||
int i_level;
|
||
union {
|
||
libvlc_audio_track_t *audio;
|
||
libvlc_video_track_t *video;
|
||
libvlc_subtitle_track_t *subtitle;
|
||
};
|
||
unsigned i_bitrate;
|
||
char *psz_language;
|
||
char *psz_description;
|
||
} libvlc_media_track_t;
|
||
|
||
libvlc_media_t *libvlc_media_new_path(libvlc_instance_t *inst, const char *path);
|
||
void libvlc_media_add_option(libvlc_media_t *m, const char *opt);
|
||
int libvlc_media_parse_with_options(libvlc_media_t *m, unsigned flags, int timeout);
|
||
int libvlc_media_get_parsed_status(libvlc_media_t *m);
|
||
unsigned libvlc_media_tracks_get(libvlc_media_t *m, libvlc_media_track_t ***tracks);
|
||
void libvlc_media_tracks_release(libvlc_media_track_t **tracks, unsigned count);
|
||
void libvlc_media_release(libvlc_media_t *m);
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
#endif
|
||
VLCMEDIA
|
||
|
||
cat > /usr/include/vlc/libvlc_media_player.h << 'VLCPLAYER'
|
||
#ifndef VLC_LIBVLC_MEDIA_PLAYER_H
|
||
#define VLC_LIBVLC_MEDIA_PLAYER_H
|
||
#include <vlc/libvlc_media.h>
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
typedef struct libvlc_media_player_t libvlc_media_player_t;
|
||
|
||
typedef enum {
|
||
libvlc_NothingSpecial = 0,
|
||
libvlc_Opening,
|
||
libvlc_Buffering,
|
||
libvlc_Playing,
|
||
libvlc_Paused,
|
||
libvlc_Stopped,
|
||
libvlc_Ended,
|
||
libvlc_Error
|
||
} libvlc_state_t;
|
||
|
||
typedef void *(*libvlc_video_lock_cb)(void *opaque, void **planes);
|
||
typedef void (*libvlc_video_unlock_cb)(void *opaque, void *picture, void *const *planes);
|
||
typedef void (*libvlc_video_display_cb)(void *opaque, void *picture);
|
||
|
||
libvlc_media_player_t *libvlc_media_player_new_from_media(libvlc_media_t *m);
|
||
void libvlc_media_player_set_media(libvlc_media_player_t *p, libvlc_media_t *m);
|
||
int libvlc_media_player_play(libvlc_media_player_t *p);
|
||
void libvlc_media_player_stop(libvlc_media_player_t *p);
|
||
libvlc_state_t libvlc_media_player_get_state(libvlc_media_player_t *p);
|
||
void libvlc_media_player_release(libvlc_media_player_t *p);
|
||
void libvlc_audio_set_mute(libvlc_media_player_t *p, int mute);
|
||
void libvlc_video_set_callbacks(libvlc_media_player_t *p,
|
||
libvlc_video_lock_cb lock, libvlc_video_unlock_cb unlock,
|
||
libvlc_video_display_cb display, void *opaque);
|
||
void libvlc_video_set_format(libvlc_media_player_t *p,
|
||
const char *chroma, unsigned w, unsigned h, unsigned pitch);
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
#endif
|
||
VLCPLAYER
|
||
|
||
# Compile stub library (all functions return NULL/0/failure)
|
||
cat > /tmp/vlc-stub.c << 'VLCSTUB'
|
||
#include <stddef.h>
|
||
typedef void libvlc_instance_t;
|
||
typedef void libvlc_media_t;
|
||
typedef void libvlc_media_player_t;
|
||
typedef void libvlc_media_track_t;
|
||
libvlc_instance_t *libvlc_new(int c, const char *const *v) { return NULL; }
|
||
void libvlc_release(libvlc_instance_t *p) { }
|
||
const char *libvlc_get_version(void) { return "0.0.0-stub"; }
|
||
libvlc_media_t *libvlc_media_new_path(libvlc_instance_t *i, const char *p) { return NULL; }
|
||
void libvlc_media_add_option(libvlc_media_t *m, const char *o) { }
|
||
int libvlc_media_parse_with_options(libvlc_media_t *m, unsigned f, int t) { return -1; }
|
||
int libvlc_media_get_parsed_status(libvlc_media_t *m) { return 0; }
|
||
unsigned libvlc_media_tracks_get(libvlc_media_t *m, libvlc_media_track_t ***t) { return 0; }
|
||
void libvlc_media_tracks_release(libvlc_media_track_t **t, unsigned c) { }
|
||
void libvlc_media_release(libvlc_media_t *m) { }
|
||
libvlc_media_player_t *libvlc_media_player_new_from_media(libvlc_media_t *m) { return NULL; }
|
||
void libvlc_media_player_set_media(libvlc_media_player_t *p, libvlc_media_t *m) { }
|
||
int libvlc_media_player_play(libvlc_media_player_t *p) { return -1; }
|
||
void libvlc_media_player_stop(libvlc_media_player_t *p) { }
|
||
int libvlc_media_player_get_state(libvlc_media_player_t *p) { return 0; }
|
||
void libvlc_media_player_release(libvlc_media_player_t *p) { }
|
||
void libvlc_audio_set_mute(libvlc_media_player_t *p, int m) { }
|
||
void libvlc_video_set_callbacks(libvlc_media_player_t *p, void *l, void *u, void *d, void *o) { }
|
||
void libvlc_video_set_format(libvlc_media_player_t *p, const char *c, unsigned w, unsigned h, unsigned pi) { }
|
||
VLCSTUB
|
||
|
||
gcc -shared -fPIC -o /usr/lib/libvlc.so.5 /tmp/vlc-stub.c
|
||
ln -sf libvlc.so.5 /usr/lib/libvlc.so
|
||
ldconfig
|
||
|
||
# pkg-config so cmake FindVLC finds it
|
||
mkdir -p /usr/lib/pkgconfig
|
||
cat > /usr/lib/pkgconfig/libvlc.pc << 'VLCPC'
|
||
prefix=/usr
|
||
exec_prefix=${prefix}
|
||
libdir=${exec_prefix}/lib
|
||
includedir=${prefix}/include
|
||
|
||
Name: libvlc
|
||
Description: VLC media library (Arch R stub)
|
||
Version: 3.0.0
|
||
Libs: -L${libdir} -lvlc
|
||
Cflags: -I${includedir}
|
||
VLCPC
|
||
|
||
echo " ✓ VLC stub installed (complete headers + libvlc.so.5 + pkg-config)"
|
||
rm -f /tmp/vlc-stub.c
|
||
|
||
echo "=== ES Build: Compiling ==="
|
||
|
||
# Clean previous build
|
||
rm -rf CMakeCache.txt CMakeFiles
|
||
|
||
# Configure — GLES 1.0 native mode (NO gl4es!)
|
||
# -DGLES=ON forces USE_OPENGLES_10 → uses Renderer_GLES10.cpp
|
||
# cmake FindOpenGLES resolves to Mesa's libGLESv1_CM.so
|
||
# Rendering pipeline: ES (GLES 1.0) → Mesa EGL → Panfrost (Mali-G31)
|
||
cmake . \
|
||
-DCMAKE_BUILD_TYPE=Release \
|
||
-DGLES=ON \
|
||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||
-DCMAKE_CXX_FLAGS="-O2 -march=armv8-a+crc -mtune=cortex-a35 -include cstdint -Wno-template-body"
|
||
|
||
# Build (limit jobs — QEMU chroot eats ~500MB/gcc, avoid OOM)
|
||
make -j2
|
||
|
||
echo "=== ES Build: Installing ==="
|
||
|
||
# Install binary
|
||
install -d /usr/bin/emulationstation
|
||
install -m 755 emulationstation /usr/bin/emulationstation/emulationstation
|
||
|
||
# Install resources
|
||
cp -r resources /usr/bin/emulationstation/
|
||
|
||
# Create symlink for easy execution
|
||
ln -sf /usr/bin/emulationstation/emulationstation /usr/local/bin/emulationstation
|
||
|
||
echo "=== ES Build: Complete ==="
|
||
ls -la /usr/bin/emulationstation/emulationstation
|
||
BUILD_EOF
|
||
|
||
chmod +x "$ROOTFS_DIR/tmp/build-es.sh"
|
||
chroot "$ROOTFS_DIR" /tmp/build-es.sh
|
||
|
||
log " ✓ EmulationStation built and installed"
|
||
|
||
#------------------------------------------------------------------------------
|
||
# Step 5: Install Arch R configs
|
||
#------------------------------------------------------------------------------
|
||
log ""
|
||
log "Step 5: Installing Arch R EmulationStation configs..."
|
||
|
||
# es_systems.cfg
|
||
if [ -f "$SCRIPT_DIR/config/es_systems.cfg" ]; then
|
||
mkdir -p "$ROOTFS_DIR/etc/emulationstation"
|
||
cp "$SCRIPT_DIR/config/es_systems.cfg" "$ROOTFS_DIR/etc/emulationstation/"
|
||
log " ✓ es_systems.cfg installed"
|
||
fi
|
||
|
||
# ES launch script
|
||
install -m 755 "$SCRIPT_DIR/scripts/emulationstation.sh" \
|
||
"$ROOTFS_DIR/usr/bin/emulationstation/emulationstation.sh"
|
||
log " ✓ Launch script installed"
|
||
|
||
# EmulationStation systemd service — runs ES as archr user with DRM master capabilities
|
||
# This replaces the old autologin approach (getty@tty1 → .bash_profile)
|
||
# Service provides: TTY association, SDL/Mesa environment, capabilities, auto-restart
|
||
cat > "$ROOTFS_DIR/etc/systemd/system/emulationstation.service" << 'ES_SVC_EOF'
|
||
[Unit]
|
||
Description=EmulationStation
|
||
After=archr-boot-setup.service
|
||
Conflicts=getty@tty1.service
|
||
After=local-fs.target
|
||
|
||
[Service]
|
||
Type=simple
|
||
User=archr
|
||
Group=archr
|
||
WorkingDirectory=/home/archr
|
||
|
||
# VT association for DRM master access
|
||
TTYPath=/dev/tty1
|
||
StandardInput=tty
|
||
StandardOutput=tty
|
||
StandardError=journal
|
||
TTYVTDisallocate=no
|
||
|
||
# Environment
|
||
Environment="HOME=/home/archr"
|
||
Environment="TERM=linux"
|
||
Environment="XDG_RUNTIME_DIR=/run/user/1001"
|
||
Environment="SDL_VIDEODRIVER=KMSDRM"
|
||
Environment="SDL_VIDEO_DRIVER=KMSDRM"
|
||
Environment="SDL_AUDIODRIVER=alsa"
|
||
Environment="SDL_ASSERT=always_ignore"
|
||
Environment="SDL_LOG_PRIORITY=error"
|
||
Environment="SDL_LOGGING=*=error"
|
||
Environment="MESA_NO_ERROR=1"
|
||
Environment="MESA_SHADER_CACHE_DIR=/home/archr/.cache/mesa_shader_cache"
|
||
Environment="SDL_GAMECONTROLLERCONFIG_FILE=/etc/archr/gamecontrollerdb.txt"
|
||
Environment="SDL_AUDIO_DEVICE_SAMPLE_FRAMES=8192"
|
||
|
||
# Capabilities: SYS_ADMIN for DRM, SETUID/SETGID/DAC_OVERRIDE for sudo
|
||
AmbientCapabilities=CAP_SYS_ADMIN CAP_SETUID CAP_SETGID CAP_DAC_OVERRIDE CAP_AUDIT_WRITE
|
||
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_SETUID CAP_SETGID CAP_DAC_OVERRIDE CAP_AUDIT_WRITE
|
||
|
||
ExecStart=/usr/bin/emulationstation/emulationstation.sh
|
||
Restart=on-failure
|
||
RestartSec=1
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
ES_SVC_EOF
|
||
|
||
# Enable the service (create symlink directly — no chroot needed)
|
||
mkdir -p "$ROOTFS_DIR/etc/systemd/system/multi-user.target.wants"
|
||
ln -sf /etc/systemd/system/emulationstation.service \
|
||
"$ROOTFS_DIR/etc/systemd/system/multi-user.target.wants/emulationstation.service"
|
||
|
||
# Disable getty@tty1 — ES service Conflicts with it, but also remove from wants
|
||
# to prevent getty from briefly starting before ES conflicts stop it
|
||
rm -f "$ROOTFS_DIR/etc/systemd/system/getty.target.wants/getty@tty1.service"
|
||
|
||
log " ✓ emulationstation.service created and enabled"
|
||
log " ✓ getty@tty1 disabled (ES service takes over tty1)"
|
||
|
||
#------------------------------------------------------------------------------
|
||
# Step 6: Cleanup
|
||
#------------------------------------------------------------------------------
|
||
log ""
|
||
log "Step 6: Cleaning up..."
|
||
|
||
# Remove build directory (saves ~200MB in rootfs)
|
||
rm -rf "$BUILD_DIR"
|
||
rm -f "$ROOTFS_DIR/tmp/build-es.sh"
|
||
|
||
# Remove build-only deps to save space
|
||
cat > "$ROOTFS_DIR/tmp/cleanup-es.sh" << 'CLEAN_EOF'
|
||
#!/bin/bash
|
||
pacman() { command pacman --disable-sandbox "$@"; }
|
||
# Remove build-only packages (not needed at runtime)
|
||
# KEEP gcc-libs (provides libstdc++.so — needed by everything C++)
|
||
for pkg in cmake eigen gcc make binutils autoconf automake \
|
||
fakeroot patch bison flex m4 libtool texinfo; do
|
||
pacman -Rdd --noconfirm "$pkg" 2>/dev/null || true
|
||
done
|
||
pacman -Scc --noconfirm
|
||
CLEAN_EOF
|
||
chmod +x "$ROOTFS_DIR/tmp/cleanup-es.sh"
|
||
chroot "$ROOTFS_DIR" /tmp/cleanup-es.sh
|
||
rm -f "$ROOTFS_DIR/tmp/cleanup-es.sh"
|
||
|
||
# Remove QEMU
|
||
rm -f "$ROOTFS_DIR/usr/bin/qemu-aarch64-static"
|
||
|
||
# Bind mounts are cleaned up by the EXIT trap (cleanup_mounts)
|
||
|
||
log " ✓ Cleanup complete"
|
||
|
||
#------------------------------------------------------------------------------
|
||
# Summary
|
||
#------------------------------------------------------------------------------
|
||
log ""
|
||
log "=== EmulationStation Build Complete ==="
|
||
log ""
|
||
log "Rendering: ES (GLES 1.0 native) → Mesa EGL → Panfrost (Mali-G31)"
|
||
log " NO gl4es! Direct GLES 1.0 → Panfrost via Mesa TNL"
|
||
log ""
|
||
log "Patches applied:"
|
||
log " 1-5: Context fixes (go2, MINOR, ES profile, null safety, MakeCurrent)"
|
||
log " 6-7: Safety fixes (getShOutput, language restart)"
|
||
log " 8-12: Performance (no depth, stencil, disable depth test)"
|
||
log " 13-14: Eliminate popen, reduce polling intervals"
|
||
log " 15: Cache getThemeSets() (19+ dir scans → 1)"
|
||
log " 16: Remove dead readList() call"
|
||
log " 17: NanoSVG static rasterizer"
|
||
log " 18: Boot profiling (5 timestamps → es-debug.log)"
|
||
log " 19: ThreadPool VSync reduction (10→500ms, ~1.5s saved)"
|
||
log " 20: Skip non-existent ROM directories"
|
||
log " 21: MameNames lazy init (call_once)"
|
||
log ""
|
||
log "Installed:"
|
||
log " /usr/bin/emulationstation/emulationstation (binary, -DGLES=ON)"
|
||
log " /usr/bin/emulationstation/resources/ (themes/fonts)"
|
||
log " /usr/bin/emulationstation/emulationstation.sh (launch script)"
|
||
log " /usr/local/bin/emulationstation (symlink)"
|
||
log " /etc/emulationstation/es_systems.cfg (system config)"
|
||
log ""
|