# SPDX-License-Identifier: BSD-2-Clause # # daedalus-decoder — frame-level GPU H.264 decoder for V3D7 (Pi 5). # Phase 1 scaffold; see DESIGN.md for architecture. # # Build dependencies: # - daedalus-fourier ≥ 0.1.0 (kernel pack, V3D primitives + recipe layer) # resolved via pkg-config; install via the daedalus-fourier upstream # `cmake --install` rule (PR #5 made the .pc relocatable, so any # install prefix works as long as $PKG_CONFIG_PATH is set). # - Vulkan headers + libvulkan (pulled in transitively via # daedalus-fourier, listed here explicitly for the link order). # # Build: # cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release # cmake --build build # ctest --test-dir build cmake_minimum_required(VERSION 3.20) project(daedalus-decoder VERSION 0.0.1 DESCRIPTION "Frame-level GPU H.264 decoder for Raspberry Pi 5 / V3D7" LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() # Pi 5 is the only supported target. Other aarch64 SoCs (Pi 4 V3D4, # RK3588 Mali, …) might work but would need explicit substrate + # shader-pack validation per the daedalus-fourier architecture # backlog. Don't pretend to support what we haven't validated. if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") message(WARNING "daedalus-decoder is designed for aarch64 (Pi 5 BCM2712 / V3D7). " "Build will proceed but is unlikely to function.") endif() add_compile_options(-Wall -Wextra -Wno-unused-parameter) # ---- Dependencies -------------------------------------------------- find_package(PkgConfig REQUIRED) # daedalus-fourier — find_package via pkg-config per the Phase 1 # decision §9.6. Minimum version 0.1.0 (the cycle 6-9 shaders + pool # + recipe-flip baseline). PKG_CONFIG_PATH should point at the # directory holding daedalus-fourier.pc (e.g. /usr/local/lib/pkgconfig # or a custom install prefix). pkg_check_modules(DAEDALUS_FOURIER REQUIRED daedalus-fourier>=0.1.0) # Vulkan — daedalus-fourier already depends on this; we add it # explicitly so the link order stays correct (daedalus-fourier static # archive contains undefined vk* symbols that the loader resolves). find_package(Vulkan REQUIRED) # ---- Version string baked into the library ------------------------ # git rev tagged onto the version string for traceability; degrades # gracefully to bare semver if git isn't available. execute_process( COMMAND git -C ${CMAKE_CURRENT_SOURCE_DIR} rev-parse --short=7 HEAD OUTPUT_VARIABLE DAEDALUS_DECODER_GITREV OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) if(DAEDALUS_DECODER_GITREV) set(DAEDALUS_DECODER_VERSION "${PROJECT_VERSION}+g${DAEDALUS_DECODER_GITREV}") else() set(DAEDALUS_DECODER_VERSION "${PROJECT_VERSION}") endif() message(STATUS "daedalus-decoder version: ${DAEDALUS_DECODER_VERSION}") # ---- Library ------------------------------------------------------ add_library(daedalus_decoder STATIC src/daedalus_decoder.c ) target_include_directories(daedalus_decoder PUBLIC $ $ PRIVATE src ${DAEDALUS_FOURIER_INCLUDE_DIRS} ) target_link_directories(daedalus_decoder PUBLIC ${DAEDALUS_FOURIER_LIBRARY_DIRS} ) target_link_libraries(daedalus_decoder PUBLIC # Order matters: daedalus-fourier static archive references # vulkan symbols; the loader needs daedalus-fourier first then # vulkan to resolve them. ${DAEDALUS_FOURIER_LIBRARIES} Vulkan::Vulkan ) target_compile_definitions(daedalus_decoder PRIVATE DAEDALUS_DECODER_VERSION="${DAEDALUS_DECODER_VERSION}" ) target_compile_options(daedalus_decoder PRIVATE -O2) # ---- Smoke test --------------------------------------------------- enable_testing() add_executable(test_smoke tests/test_smoke.c) target_link_libraries(test_smoke PRIVATE daedalus_decoder) target_compile_options(test_smoke PRIVATE -O2) add_test(NAME smoke COMMAND test_smoke) add_executable(test_idct_bitexact tests/test_idct_bitexact.c) target_link_libraries(test_idct_bitexact PRIVATE daedalus_decoder) target_compile_options(test_idct_bitexact PRIVATE -O2) # 320x240 QVGA — fast inner-loop test (300 MBs, sub-second). add_test(NAME idct_bitexact COMMAND test_idct_bitexact) # Same QVGA test re-run on the CPU NEON path (forces fallback even on # V3D7 hosts). Catches silent drift between the V3D shader and the # NEON reference path — both must produce identical output for the # same coefficient input. Also keeps the bit-exact gate alive on # hosts without V3D7 (CI runners, x86 dev boxes). add_test(NAME idct_bitexact_cpu COMMAND test_idct_bitexact 320 240 0xfeedface5a5a5a5a cpu) # 1920x1088 1080p — deployment-scale test (8160 MBs, ~0.25 s on hertz). # Validates the per-MB block index + pixel offset math at full coded # height (1088, not 1080 — see daedalus_decoder.h on H.264 coded vs # displayed dims). Cheap enough to run unconditionally; if it ever # gets slow we'll split into a CTest LABEL for opt-in. add_test(NAME idct_bitexact_1080p COMMAND test_idct_bitexact 1920 1088) # ---- Stage 2 PR-b deblock smoke ------------------------------------ # # Validates flush_frame's per-frame deblock dispatch (luma + chroma, # V + H, bS<4 + bS=4 intra — up to 8 dispatches added after IDCT). # Strategy: same input through substrate=CPU and substrate=QPU, assert # byte-exact match (transitive bit-exact gate — daedalus-fourier's own # test_api_h264 already validates each substrate against a C reference, # so CPU-QPU equivalence here means both match the spec). Plus an # anti-no-op check: run a third pass with edges removed and assert # different output, proving deblock actually ran. add_executable(test_deblock_smoke tests/test_deblock_smoke.c) target_link_libraries(test_deblock_smoke PRIVATE daedalus_decoder) target_compile_options(test_deblock_smoke PRIVATE -O2) add_test(NAME deblock_smoke COMMAND test_deblock_smoke) # ---- Benchmarks (not gated by ctest) ------------------------------ # # Build-time only; user runs them by hand when checking perf. Adding # them as ctest would make every CI run slow and the numbers would # get drowned in pass/fail noise. See the header of each .c for what # they measure. add_executable(bench_flush_frame tests/bench_flush_frame.c) target_link_libraries(bench_flush_frame PRIVATE daedalus_decoder) target_compile_options(bench_flush_frame PRIVATE -O2) # ---- Tools (not gated by ctest; opt-in via DAEDALUS_BUILD_TOOLS) ---- # # daedalus_decode_h264 — option A standalone test harness that # wraps libavcodec + daedalus-decoder and bit-exact-compares their # outputs on real H.264 streams. Identity-passthrough mode in this # first iteration (predicted = AVFrame pixels, coeffs = 0, no # deblock edges); follow-up PRs use the per-MB inspection callback # (marfrit-packages patch 0016) to feed REAL per-MB state. # # Requires libavcodec + libavformat headers + libs. Off by default # so the standard ctest build doesn't pull in FFmpeg as a hard dep. option(DAEDALUS_BUILD_TOOLS "Build daedalus-decoder CLI tools (requires libavcodec)" OFF) if(DAEDALUS_BUILD_TOOLS) # Optional path to a private FFmpeg install carrying the per-MB # inspection callback (marfrit-packages patch 0016). When set, # the CLI links against it instead of the system FFmpeg and the # inspection-callback code path is compiled in. set(DAEDALUS_FFMPEG_PREFIX "" CACHE PATH "Path to a patched FFmpeg install (with 0016 mb-inspect-callback) for daedalus_decode_h264. Empty = use system pkg-config FFmpeg.") if(DAEDALUS_FFMPEG_PREFIX) message(STATUS "daedalus_decode_h264: patched FFmpeg at ${DAEDALUS_FFMPEG_PREFIX}") set(FFMPEG_INCLUDE_DIRS ${DAEDALUS_FFMPEG_PREFIX}/include) set(FFMPEG_LIBRARY_DIRS ${DAEDALUS_FFMPEG_PREFIX}/lib) # Patched libavcodec is built static (no shared libs in the private prefix). # System pull-ins are still needed for libav* dependencies. set(FFMPEG_LIBRARIES ${DAEDALUS_FFMPEG_PREFIX}/lib/libavformat.a ${DAEDALUS_FFMPEG_PREFIX}/lib/libavcodec.a ${DAEDALUS_FFMPEG_PREFIX}/lib/libavutil.a ${DAEDALUS_FFMPEG_PREFIX}/lib/libswresample.a m z pthread) set(FFMPEG_CFLAGS_OTHER "-DDAEDALUS_HAVE_H264_MB_INSPECT_CB=1") # PR-A3+ optional: also point at the patched FFmpeg SOURCE TREE # so the CLI can include libavcodec/h264dec.h directly and # dereference H264Context fields (the side-buffer mb_inspect_coeffs # added in marfrit-packages patch 0017, the cur_pic.f for # pre-deblock pixel access, etc.). When set, the internal-header # include codepath is compiled in. set(DAEDALUS_FFMPEG_SRC "" CACHE PATH "Path to patched FFmpeg source tree (= path to FFmpeg/ checkout where build was run; contains config.h + libavcodec/h264dec.h). Empty = h264dec.h includes are disabled.") if(DAEDALUS_FFMPEG_SRC) message(STATUS "daedalus_decode_h264: FFmpeg source at ${DAEDALUS_FFMPEG_SRC}") # IMPORTANT: source tree FIRST in -I order — its # libavutil/common.h does #include "intmath.h" with HAVE_AV_CONFIG_H, # which resolves to libavutil/intmath.h (in the source tree # only — that header isn't installed since it's arch-dispatched). # The installed-prefix include path's libavutil/common.h is the # same file textually but resolves "intmath.h" against the # install dir where it doesn't exist. set(FFMPEG_INCLUDE_DIRS ${DAEDALUS_FFMPEG_SRC}) set(FFMPEG_CFLAGS_OTHER "${FFMPEG_CFLAGS_OTHER} -DDAEDALUS_HAVE_H264_MB_INSPECT_COEFFS=1 -DHAVE_AV_CONFIG_H") # Convert space-separated string to list (CMake idiom for compile flags). separate_arguments(FFMPEG_CFLAGS_OTHER UNIX_COMMAND "${FFMPEG_CFLAGS_OTHER}") endif() else() pkg_check_modules(FFMPEG REQUIRED libavcodec libavformat libavutil) message(STATUS "daedalus_decode_h264: system FFmpeg (no inspection callback)") endif() add_executable(daedalus_decode_h264 tools/daedalus_decode_h264.c) target_link_libraries(daedalus_decode_h264 PRIVATE daedalus_decoder ${FFMPEG_LIBRARIES}) target_include_directories(daedalus_decode_h264 PRIVATE ${FFMPEG_INCLUDE_DIRS}) target_link_directories(daedalus_decode_h264 PRIVATE ${FFMPEG_LIBRARY_DIRS}) target_compile_options(daedalus_decode_h264 PRIVATE -O2 ${FFMPEG_CFLAGS_OTHER}) endif() # ---- Install ------------------------------------------------------ # # Installs: # - libdaedalus_decoder.a → ${CMAKE_INSTALL_LIBDIR} # - include/daedalus_decoder.h → ${CMAKE_INSTALL_INCLUDEDIR} # - daedalus-decoder.pc → ${CMAKE_INSTALL_LIBDIR}/pkgconfig # # The .pc lets sibling consumers (daedalus-v4l2 daemon, the # daedalus_decode_h264 CLI when built externally) discover the static # archive + headers via pkg-config. daedalus-fourier is declared as a # public `Requires:` because the consumer (which static-links # libdaedalus_decoder.a) also needs daedalus-fourier in its own link # line to resolve the daedalus_ctx_* / daedalus_recipe_* symbols this # archive references. # # Relocatable-prefix scheme mirrors daedalus-fourier's .pc generation: # `prefix` is derived from ${pcfiledir} so `cmake --install --prefix /foo` # produces a .pc that resolves prefix=/foo at lookup time, regardless of # what CMAKE_INSTALL_PREFIX was at configure time. include(GNUInstallDirs) install(TARGETS daedalus_decoder ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(FILES include/daedalus_decoder.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) file(RELATIVE_PATH PKGCONFIG_PCDIR_TO_PREFIX "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/pkgconfig" "${CMAKE_INSTALL_PREFIX}") set(PKGCONFIG_OUT ${CMAKE_CURRENT_BINARY_DIR}/daedalus-decoder.pc) file(WRITE ${PKGCONFIG_OUT} "prefix=\${pcfiledir}/${PKGCONFIG_PCDIR_TO_PREFIX} exec_prefix=\${prefix} libdir=\${prefix}/${CMAKE_INSTALL_LIBDIR} includedir=\${prefix}/${CMAKE_INSTALL_INCLUDEDIR} Name: daedalus-decoder Description: Frame-major H.264 decoder on V3D7 via daedalus-fourier primitives Version: ${PROJECT_VERSION} Libs: -L\${libdir} -ldaedalus_decoder Requires: daedalus-fourier Cflags: -I\${includedir} ") install(FILES ${PKGCONFIG_OUT} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig )