diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64a0af0..e3ceaf8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,8 +50,9 @@ jobs: build-essential cmake ninja-build pkg-config \ libopencv-dev \ libgl1-mesa-dev libglu1-mesa-dev \ + libx11-dev libxext-dev \ libxinerama-dev libxcursor-dev libxi-dev libxrandr-dev \ - libwayland-dev libxkbcommon-dev \ + libwayland-dev libxkbcommon-dev wayland-protocols \ libusb-1.0-0-dev libudev-dev \ libgtk-3-dev @@ -67,7 +68,21 @@ jobs: curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFB0B24895113F120&options=mr" \ | sudo gpg --dearmor -o /etc/apt/keyrings/librealsense.gpg sudo chmod 0644 /etc/apt/keyrings/librealsense.gpg - echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo $(lsb_release -cs) main" \ + # Intel only publishes the apt repo for select Ubuntu LTS codenames + # (bionic/focal/jammy/noble). On a newer/interim release the repo 404s + # and `apt-get update` fails, so fall back to the newest supported LTS. + RS_CODENAME="$(lsb_release -cs)" + # Fall back to the newest supported LTS ONLY when the repo definitively + # lacks this codename (Intel returns 403/404 for unsupported releases). + # Retry first so a transient network error doesn't silently retarget the + # distro — a real outage then fails loudly at apt-get update instead. + RS_HTTP="$(curl -sSL -o /dev/null -w '%{http_code}' --retry 3 --retry-connrefused \ + "https://librealsense.intel.com/Debian/apt-repo/dists/${RS_CODENAME}/Release" || echo 000)" + if [ "$RS_HTTP" = "403" ] || [ "$RS_HTTP" = "404" ]; then + echo "librealsense apt repo has no '${RS_CODENAME}' (HTTP $RS_HTTP); falling back to noble" + RS_CODENAME=noble + fi + echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo ${RS_CODENAME} main" \ | sudo tee /etc/apt/sources.list.d/librealsense.list sudo apt-get update sudo apt-get install -y --no-install-recommends librealsense2-dev librealsense2-utils diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ce89a9..fcc7ab6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,11 +41,12 @@ jobs: sudo apt-get update sudo apt-get install -y --no-install-recommends \ ca-certificates curl gnupg lsb-release \ - build-essential cmake ninja-build pkg-config patchelf \ + build-essential cmake ninja-build pkg-config patchelf icoutils \ libopencv-dev \ libgl1-mesa-dev libglu1-mesa-dev \ + libx11-dev libxext-dev \ libxinerama-dev libxcursor-dev libxi-dev libxrandr-dev \ - libwayland-dev libxkbcommon-dev \ + libwayland-dev libxkbcommon-dev wayland-protocols \ libusb-1.0-0-dev libudev-dev \ libgtk-3-dev @@ -54,10 +55,26 @@ jobs: curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFB0B24895113F120&options=mr" \ | sudo gpg --dearmor -o /etc/apt/keyrings/librealsense.gpg sudo chmod 0644 /etc/apt/keyrings/librealsense.gpg - echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo $(lsb_release -cs) main" \ + # Intel only publishes the apt repo for select Ubuntu LTS codenames + # (bionic/focal/jammy/noble). On a newer/interim release the repo 404s + # and `apt-get update` fails, so fall back to the newest supported LTS + # (its binaries are forward-compatible). + RS_CODENAME="$(lsb_release -cs)" + # Fall back to the newest supported LTS ONLY when the repo definitively + # lacks this codename (Intel returns 403/404 for unsupported releases). + # Retry first so a transient network error doesn't silently retarget the + # distro — a real outage then fails loudly at apt-get update instead. + RS_HTTP="$(curl -sSL -o /dev/null -w '%{http_code}' --retry 3 --retry-connrefused \ + "https://librealsense.intel.com/Debian/apt-repo/dists/${RS_CODENAME}/Release" || echo 000)" + if [ "$RS_HTTP" = "403" ] || [ "$RS_HTTP" = "404" ]; then + echo "librealsense apt repo has no '${RS_CODENAME}' (HTTP $RS_HTTP); falling back to noble" + RS_CODENAME=noble + fi + echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo ${RS_CODENAME} main" \ | sudo tee /etc/apt/sources.list.d/librealsense.list sudo apt-get update - sudo apt-get install -y --no-install-recommends librealsense2-dev librealsense2-utils + sudo apt-get install -y --no-install-recommends \ + librealsense2-dev librealsense2-utils librealsense2-udev-rules - name: Install macOS dependencies if: runner.os == 'macOS' @@ -160,11 +177,53 @@ jobs: # Binary + bundled libs under /usr/lib// with $ORIGIN rpath. cp build/ir-tracking-app "$STAGE/usr/lib/$PKG/" - ldd build/ir-tracking-app \ - | awk '/(librealsense|libopencv|libtbb|libusb-1\.0)/ { print $3 }' \ - | grep -v '^$' | sort -u \ - | while read -r lib; do cp -L "$lib" "$STAGE/usr/lib/$PKG/"; done + + # Bundle the application's private compute libraries — RealSense, + # OpenCV, and whatever THOSE pull in (TBB, protobuf, image codecs, ...) + # — so the package never relies on the host shipping the exact sonames + # we built against. Those drift across Ubuntu 22.04/24.04/26.04 + # (libtbb.so.2 -> .so.12, libprotobuf.so.23 absent on 26.04, etc.). + # + # We compute this set as the *dependency closure of the RealSense + + # OpenCV libraries only*, not of the whole binary. That deliberately + # excludes the GL / X11 / Wayland / GTK / C++-runtime stack (the binary + # links those directly, but RealSense/OpenCV do not depend on them), so + # we can never accidentally bundle e.g. libgtk-3 and break the user's + # desktop integration. Those host libraries come from Depends instead. + # (The previous hand-written allow-list missed libprotobuf.so.23 and + # crashed on any host without it.) + mapfile -t ROOTS < <(ldd build/ir-tracking-app \ + | awk '/=> \// && /lib(realsense2|opencv)/ { print $3 }' | sort -u) + declare -A SEEN + queue=("${ROOTS[@]}") + while ((${#queue[@]})); do + cur="${queue[0]}"; queue=("${queue[@]:1}") + if [[ -n "${SEEN[$cur]:-}" ]]; then continue; fi + SEEN["$cur"]=1 + while read -r dep; do + if [[ -n "$dep" && -z "${SEEN[$dep]:-}" ]]; then queue+=("$dep"); fi + done < <(ldd "$cur" 2>/dev/null | awk '/=> \//{print $3}') + done + # Copy the closure, except the host C/C++/system runtime (ABI-stable + # and present on every target — bundling an older copy would be wrong). + RUNTIME='^(ld-linux|libc|libm|libdl|libpthread|librt|libresolv|libstdc\+\+|libgcc_s|libatomic|libz|liblzma|libzstd|libbz2|libudev|libusb-1\.0|libcrypt)\.' + for lib in "${!SEEN[@]}"; do + base="$(basename "$lib")" + if printf '%s' "$base" | grep -qE "$RUNTIME"; then continue; fi + cp -L "$lib" "$STAGE/usr/lib/$PKG/" + done + + # $ORIGIN rpath on the binary AND every bundled lib. RUNPATH does not + # propagate to a library's *own* dependencies, so without rpath on the + # bundled .so files their private deps (libopencv_core -> libtbb.so.2, + # libopencv_dnn -> libprotobuf.so.23) would be searched only on the + # host and fail wherever those sonames are gone. patchelf --set-rpath '$ORIGIN' "$STAGE/usr/lib/$PKG/ir-tracking-app" + shopt -s nullglob # don't iterate a literal '*.so*' if the closure were ever empty + for so in "$STAGE/usr/lib/$PKG/"*.so*; do + patchelf --set-rpath '$ORIGIN' "$so" + done + shopt -u nullglob # Launcher shim on PATH. cat > "$STAGE/usr/bin/$PKG" < "$STAGE/DEBIAN/postinst" <<'SH' + #!/bin/sh + set -e + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules || true + udevadm trigger || true + fi + # Refresh the icon cache / desktop database so the launcher entry and + # icon appear without a re-login. + command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache -q -f /usr/share/icons/hicolor 2>/dev/null || true + command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database -q /usr/share/applications 2>/dev/null || true + exit 0 + SH + cat > "$STAGE/DEBIAN/postrm" <<'SH' + #!/bin/sh + set -e + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules || true + fi + exit 0 + SH + chmod 0755 "$STAGE/DEBIAN/postinst" "$STAGE/DEBIAN/postrm" + + # dpkg control metadata. Depends covers the host-provided runtime libs + # we deliberately do NOT bundle (the GL/GTK/X11/C++-runtime/USB stack). + # Names are the Ubuntu 22.04 ones; on 24.04/26.04 the t64 packages + # (e.g. libgtk-3-0t64) satisfy them via Provides, so one .deb installs + # on all three. libopengl0 provides libOpenGL.so.0, which the binary + # links directly — its omission was the original launch crash. INSTALLED_KB=$(du -sk "$STAGE" --exclude=DEBIAN | cut -f1) cat > "$STAGE/DEBIAN/control" < Homepage: https://github.com/stytim/RealSense-ToolTracker - Depends: libgtk-3-0, libgl1, libusb-1.0-0 + Depends: libc6, libstdc++6, libgcc-s1, libgtk-3-0, libgl1, libopengl0, libx11-6, libusb-1.0-0, libudev1, zlib1g Installed-Size: $INSTALLED_KB Description: Intel RealSense IR retro-reflective marker tool tracker Tracks passive IR sphere markers from an Intel RealSense camera and diff --git a/CMakeLists.txt b/CMakeLists.txt index e924b2e..e2f4ad4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,8 +29,20 @@ else() # Static library: -fPIC set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") - set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -march=native -fstack-protector") - set(CMAKE_CXX_FLAGS_RELEASE "-O3 -march=native") + # -march=native tunes the binary to the CPU that builds it. That is fine for + # a local build, but wrong for a redistributable artifact: the CI release + # binary would pick up instructions (e.g. AVX-512) that fault with SIGILL on + # a user's older CPU. Default OFF so one Linux build runs on any x86-64 box; + # opt in with -DIRTRACK_NATIVE_ARCH=ON for a tuned local performance build. + option(IRTRACK_NATIVE_ARCH "Tune the build for the local CPU (-march=native); not portable" OFF) + if(IRTRACK_NATIVE_ARCH) + set(IRTRACK_ARCH_FLAG "-march=native") + else() + set(IRTRACK_ARCH_FLAG "") + endif() + + set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 ${IRTRACK_ARCH_FLAG} -fstack-protector") + set(CMAKE_CXX_FLAGS_RELEASE "-O3 ${IRTRACK_ARCH_FLAG}") endif() IF(MSVC) diff --git a/README.md b/README.md index 103ed9b..76ac1e2 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,60 @@ This project leverages the versatility of Intel RealSense cameras, enabling high ## Building Ensure all the prerequisites are installed before proceeding with the build process. -### For Linux and MacOS: +### Linux (Ubuntu 22.04 / 24.04 / 26.04) + +Install the build dependencies. Intel's RealSense apt repo only publishes for +select LTS codenames (`bionic`/`focal`/`jammy`/`noble`); on a newer release +(e.g. 26.04 "resolute") it 404s, so the snippet below falls back to the newest +supported LTS — those packages are forward-compatible. + +```bash +sudo apt-get update +sudo apt-get install -y \ + ca-certificates curl gnupg lsb-release \ + build-essential cmake ninja-build pkg-config patchelf \ + libopencv-dev \ + libgl1-mesa-dev libglu1-mesa-dev \ + libx11-dev libxext-dev \ + libxinerama-dev libxcursor-dev libxi-dev libxrandr-dev \ + libwayland-dev libxkbcommon-dev wayland-protocols \ + libusb-1.0-0-dev libudev-dev \ + libgtk-3-dev + +# Intel RealSense SDK from Intel's apt repo (with LTS-codename fallback) +sudo install -m 0755 -d /etc/apt/keyrings +curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFB0B24895113F120&options=mr" \ + | sudo gpg --dearmor -o /etc/apt/keyrings/librealsense.gpg +sudo chmod 0644 /etc/apt/keyrings/librealsense.gpg +RS_CODENAME="$(lsb_release -cs)" +curl -fsSL -o /dev/null "https://librealsense.intel.com/Debian/apt-repo/dists/${RS_CODENAME}/Release" || RS_CODENAME=noble +echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo ${RS_CODENAME} main" \ + | sudo tee /etc/apt/sources.list.d/librealsense.list +sudo apt-get update +# librealsense2-udev-rules lets a non-root user access the camera (otherwise the +# USB device nodes are root-only and the app reports "No RealSense devices found"). +sudo apt-get install -y librealsense2-dev librealsense2-utils librealsense2-udev-rules +``` + +> The prebuilt `.deb` already installs these udev rules for you. + +Then build: + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build --parallel +``` + +> The build targets a portable x86-64 baseline so the binary runs on any +> machine. For a CPU-tuned local build, add `-DIRTRACK_NATIVE_ARCH=ON` (uses +> `-march=native`; the result is not redistributable). + +> Prefer the prebuilt `.deb` from the +> [Releases page](https://github.com/stytim/RealSense-ToolTracker/releases) if +> you just want to run the app — it bundles RealSense/OpenCV and installs on +> Ubuntu 22.04/24.04/26.04. + +### For MacOS: ```bash mkdir build && cd build diff --git a/extern/imgui/src/backends/imgui_impl_opengl3_loader.h b/extern/imgui/src/backends/imgui_impl_opengl3_loader.h index 9fb8ffb..8dbc86d 100644 --- a/extern/imgui/src/backends/imgui_impl_opengl3_loader.h +++ b/extern/imgui/src/backends/imgui_impl_opengl3_loader.h @@ -666,8 +666,17 @@ static GL3WglProc (*glx_get_proc_address)(const GLubyte *); static int open_libgl(void) { - // While most systems use libGL.so.1, NetBSD seems to use that libGL.so.3. See https://github.com/ocornut/imgui/issues/6983 - libgl = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL); + // Open the versioned runtime soname FIRST. The unversioned "libGL.so" only + // ships with a -dev package (libgl-dev/mesa), so an end-user machine that + // has just the runtime libgl1 would otherwise fail here with + // "Failed to initialize OpenGL loader!" and abort. Most Linux systems use + // libGL.so.1; NetBSD uses libGL.so.3 (see ocornut/imgui#6983); fall back to + // the unversioned name last for dev environments. + libgl = dlopen("libGL.so.1", RTLD_LAZY | RTLD_LOCAL); + if (!libgl) + libgl = dlopen("libGL.so.3", RTLD_LAZY | RTLD_LOCAL); + if (!libgl) + libgl = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL); if (!libgl) return GL3W_ERROR_LIBRARY_OPEN; *(void **)(&glx_get_proc_address) = dlsym(libgl, "glXGetProcAddressARB");