Files
JustOneCacophony/CMakeLists.txt
T
TheM14 42d6da6302
ci / ubuntu-latest (push) Failing after 13s
ci / macos-latest (push) Has been cancelled
ci / windows-latest (push) Has been cancelled
Initial pure C++ implementation
2026-09-25 04:17:30 +08:00

331 lines
14 KiB
CMake

cmake_minimum_required(VERSION 3.20)
project(joc_core VERSION 0.1.0 LANGUAGES C CXX)
# ---------------------------------------------------------------------------
# Products
# joc_core shared library: the execution core (E-AC-3/EMDF/JOC/OAMD
# bitstream, DSP, speaker and binaural rendering, ADM-BWF/WAV
# writing, file task and the streaming push/pull surface)
# joc_cli command line frontend for file tasks
#
# Public headers: include/joc_core.h engine, telemetry and file task
# include/joc_stream.h embedder-facing streaming surface
#
# Options
# JOC_BUILD_TESTS unit tests (CTest), on by default
# JOC_BUILD_DEVTOOLS in-tree verification tools, off: those sources live in
# devtools/, which is not part of the repository
# JOC_ENABLE_AVX2 build the runtime-dispatched AVX2 kernels, on by default
# JOC_ENABLE_AVX512 build the runtime-dispatched AVX-512 kernels, on by default
#
# Only the MSVC toolchain is validated locally; other platforms are built by CI.
# The floating-point flags of the original native library are preserved on
# purpose: /fp:precise on MSVC, -fno-fast-math elsewhere, and a static CRT so
# that no redistributable is required.
# ---------------------------------------------------------------------------
option(JOC_BUILD_TESTS "Build the unit tests" ON)
option(JOC_BUILD_DEVTOOLS "Build the in-tree verification tools (needs devtools/)" OFF)
# The DSP kernels are dispatched at run time (see src/simd/simd.h): the
# baseline units stay on the ISA every x86-64 CPU has, and these two options
# decide whether the wider units are linked in at all. Both are on by default.
# The instruction set actually executed is chosen from CPUID/XGETBV (or the
# AArch64 baseline) when the library is first used, so a binary carrying the
# AVX-512 unit still runs on a CPU without it, and JOC_SIMD=scalar|sse2|avx2|
# avx512|neon pins one tier for verification.
option(JOC_ENABLE_AVX2 "Build the runtime-dispatched AVX2 kernels" ON)
option(JOC_ENABLE_AVX512 "Build the runtime-dispatched AVX-512 kernels" ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
find_package(Threads REQUIRED)
# Ninja learns MSVC's header dependencies by parsing the compiler's /showIncludes
# notes, and it only recognises the prefix it is told about. A localized MSVC
# prints a translated prefix, and when CMake cannot detect it the notes are
# silently dropped: the build then reuses stale objects after a header changes and
# produces a binary that does not match its sources -- wrong, not just slow. This
# only warns, because supplying the value is not always possible either: it has to
# survive the cache code page to be usable, and a build driver can compensate more
# reliably by dropping objects when a header is newer than they are.
if(MSVC AND CMAKE_GENERATOR MATCHES "Ninja" AND NOT CMAKE_CL_SHOWINCLUDES_PREFIX)
message(WARNING
"No /showIncludes prefix was detected, so Ninja will not track header "
"dependencies and a header change will not rebuild what includes it. "
"Configure with -DCMAKE_CL_SHOWINCLUDES_PREFIX=<the text MSVC prints in "
"front of each included file>, or make sure the compiler emits its "
"messages in the language CMake probes for.")
endif()
# Verbatim copies of the upstream native library; see THIRD_PARTY_NOTICES.md.
# These files are never edited: they are the validated DSP kernels.
set(JOC_REUSED_SOURCES
src/joc_core/eac3joc_core.cpp
src/speaker/speaker_renderer.cpp
src/binaural/binaural_renderer.cpp
)
set(JOC_INTERNAL_SOURCES
src/adm/adm_metadata.cpp
src/adm/adm_tracks.cpp
src/binaural/binaural_runtime.cpp
src/binaural/sofa_binaural_renderer.cpp
src/eac3_transport/eac3_reader.cpp
src/emdf/emdf_parser.cpp
src/foundation/bit_reader.cpp
src/foundation/fft.cpp
src/foundation/fs_utf8.cpp
src/foundation/mini_json.cpp
src/foundation/sha256.cpp
src/hrtf/jochrtf.cpp
src/hrtf/public_filterbank.cpp
src/hrtf/rosella_model.cpp
src/hrtf/rosella_renderer.cpp
src/hrtf/kernel_tables.cpp
src/hrtf/sofa.cpp
src/hrtf/sofa_cache.cpp
src/hrtf/sofa_field.cpp
src/io/adm_writer.cpp
src/io/hdf5.cpp
src/io/inflate.cpp
src/io/npy.cpp
src/io/npy_writer.cpp
src/io/process.cpp
src/io/wav_writer.cpp
src/io/zip_reader.cpp
src/joc_bitstream/joc_parser.cpp
src/joc_core/objects16.cpp
src/oamd/oamd_parser.cpp
src/speaker/speaker_layout_lookup.cpp
src/speaker/speaker_step.cpp
src/stream/stream.cpp
src/task/task.cpp
src/telemetry/event_bus.cpp
src/timeline/position_timeline.cpp
)
# ---------------------------------------------------------------------------
# Runtime-dispatched SIMD kernels (src/simd/simd.h explains the contract).
#
# One flat directory, the ISA in the file name (`kernels_intrin_<isa>.cpp`),
# never in a subdirectory. MSVC has no function-level ISA attribute, so every
# ISA lives in its own translation unit compiled with its own flag, and
# dispatch.cpp -- built for the baseline ISA -- picks one when the library is
# first used. Only a `kernels_intrin_*.cpp` unit ever gets a wider flag, so a
# baseline unit cannot inherit one by accident.
#
# The x86-64 baseline is SSE2 and there is no SSE2 unit on purpose: a 128-bit
# SSE2 register is the register a scalar double already occupies, so SSE2 cannot
# widen double-precision arithmetic and hand-written SSE2 would only add moves.
# AArch64 needs no probe either; ASIMD is architectural, and the NEON unit is
# how a vector path gets selected there.
# ---------------------------------------------------------------------------
set(JOC_SIMD_SOURCES
src/simd/cpu_probe.cpp
src/simd/dispatch.cpp
src/simd/kernels_scalar.cpp
)
set(JOC_SIMD_DEFINES "")
set(JOC_ARCH "")
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64|EM64T)$")
set(JOC_ARCH x86_64)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64|AARCH64)$")
set(JOC_ARCH aarch64)
endif()
endif()
if(NOT JOC_ARCH AND DEFINED CMAKE_CXX_COMPILER_ARCHITECTURE_ID)
if(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "x64")
set(JOC_ARCH x86_64)
elseif(CMAKE_CXX_COMPILER_ARCHITECTURE_ID STREQUAL "ARM64")
set(JOC_ARCH aarch64)
endif()
endif()
if(JOC_ARCH STREQUAL "x86_64")
list(APPEND JOC_SIMD_DEFINES JOC_SIMD_HAVE_SSE2=1)
if(JOC_ENABLE_AVX2)
list(APPEND JOC_SIMD_SOURCES src/simd/kernels_intrin_avx2.cpp)
list(APPEND JOC_SIMD_DEFINES JOC_SIMD_HAVE_AVX2=1)
if(MSVC)
set_source_files_properties(src/simd/kernels_intrin_avx2.cpp
PROPERTIES COMPILE_OPTIONS "/arch:AVX2")
else()
set_source_files_properties(src/simd/kernels_intrin_avx2.cpp
PROPERTIES COMPILE_OPTIONS "-mavx2")
endif()
endif()
if(JOC_ENABLE_AVX512)
list(APPEND JOC_SIMD_SOURCES src/simd/kernels_intrin_avx512.cpp)
list(APPEND JOC_SIMD_DEFINES JOC_SIMD_HAVE_AVX512=1)
if(MSVC)
set_source_files_properties(src/simd/kernels_intrin_avx512.cpp
PROPERTIES COMPILE_OPTIONS "/arch:AVX512")
else()
set_source_files_properties(src/simd/kernels_intrin_avx512.cpp
PROPERTIES COMPILE_OPTIONS "-mavx512f")
endif()
endif()
elseif(JOC_ARCH STREQUAL "aarch64")
list(APPEND JOC_SIMD_DEFINES JOC_SIMD_HAVE_NEON=1)
list(APPEND JOC_SIMD_SOURCES src/simd/kernels_intrin_neon.cpp)
endif()
add_library(joc_simd OBJECT ${JOC_SIMD_SOURCES})
target_include_directories(joc_simd PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_compile_definitions(joc_simd PRIVATE ${JOC_SIMD_DEFINES})
# The objects are linked into the shared library as well, so they must be
# position independent even though an object library does not inherit that.
set_target_properties(joc_simd PROPERTIES POSITION_INDEPENDENT_CODE ON)
# Static form of the engine, used by the in-tree tools and tests so they can use
# internal modules without exporting them from the shared library.
if(JOC_BUILD_TESTS OR JOC_BUILD_DEVTOOLS)
add_library(joc_core_impl STATIC ${JOC_INTERNAL_SOURCES} ${JOC_REUSED_SOURCES}
$<TARGET_OBJECTS:joc_simd>)
target_include_directories(joc_core_impl
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include"
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src"
)
target_link_libraries(joc_core_impl PUBLIC Threads::Threads)
endif()
add_library(joc_core SHARED
src/api/joc_api.cpp
src/api/joc_stream_api.cpp
src/api/joc_task_api.cpp
${JOC_INTERNAL_SOURCES}
${JOC_REUSED_SOURCES}
$<TARGET_OBJECTS:joc_simd>
)
target_include_directories(joc_core
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include"
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src"
)
target_compile_definitions(joc_core PRIVATE JOC_BUILD_DLL)
target_link_libraries(joc_core PRIVATE Threads::Threads)
set_target_properties(joc_core PROPERTIES
OUTPUT_NAME "joc_core"
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
POSITION_INDEPENDENT_CODE YES
)
# The frontend compiles the UTF-8 path shim itself: it is small, and the shared
# library keeps its internals unexported.
add_executable(joc_cli src/cli/joc_cli.cpp src/foundation/fs_utf8.cpp)
target_link_libraries(joc_cli PRIVATE joc_core)
target_include_directories(joc_cli PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
# The installed CLI is in bin/ and the shared library in lib/, and ELF/Mach-O
# strip the build rpath on install, so the relative lookup has to be recorded
# here or `bin/joc_cli` cannot find `../lib/libjoc_core.*`.
if(UNIX)
if(APPLE)
set_target_properties(joc_cli PROPERTIES INSTALL_RPATH "@loader_path/../lib")
else()
set_target_properties(joc_cli PROPERTIES INSTALL_RPATH "$ORIGIN/../lib")
endif()
endif()
if(JOC_BUILD_TESTS)
enable_testing()
add_executable(joc_tests tests/test_core.cpp)
target_link_libraries(joc_tests PRIVATE joc_core_impl)
target_include_directories(joc_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
add_test(NAME core COMMAND joc_tests)
# The public headers must stay valid C and C++: these targets exist to prove it.
add_library(joc_headers_c OBJECT tests/test_headers.c)
target_include_directories(joc_headers_c PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
add_library(joc_headers_cpp OBJECT tests/test_headers.cpp)
target_link_libraries(joc_headers_cpp PRIVATE joc_core)
endif()
if(JOC_BUILD_DEVTOOLS)
add_executable(joc_dump devtools/joc_dump/main.cpp)
target_link_libraries(joc_dump PRIVATE joc_core joc_core_impl)
target_include_directories(joc_dump PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
# SOFA reader probe: the C++ side of devtools/checks/check_sofa.py.
add_executable(joc_sofa_field_probe devtools/sofa_field_probe/main.cpp)
add_executable(joc_dictionary_probe devtools/sofa_field_probe/dictionary_main.cpp)
target_link_libraries(joc_dictionary_probe PRIVATE joc_core_impl)
target_include_directories(joc_dictionary_probe PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(joc_sofa_field_probe PRIVATE joc_core_impl)
target_include_directories(joc_sofa_field_probe PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
# Rosella model probe: the C++ side of devtools/checks/check_rosella.py.
add_executable(joc_rosella_probe devtools/rosella_probe/main.cpp)
target_link_libraries(joc_rosella_probe PRIVATE joc_core_impl)
target_include_directories(joc_rosella_probe PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
add_executable(joc_sofa_probe devtools/sofa_probe/main.cpp)
target_link_libraries(joc_sofa_probe PRIVATE joc_core_impl)
target_include_directories(joc_sofa_probe PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
# Probe for the streaming surface: the acceptance harness and the usage
# example for joc_stream.h. Not shipped: a player integrates the library.
add_executable(joc_stream_probe devtools/joc_stream/main.cpp)
target_link_libraries(joc_stream_probe PRIVATE joc_core)
endif()
set(JOC_MSVC_TARGETS joc_core joc_cli joc_simd)
if(JOC_BUILD_TESTS OR JOC_BUILD_DEVTOOLS)
list(APPEND JOC_MSVC_TARGETS joc_core_impl)
endif()
if(JOC_BUILD_TESTS)
list(APPEND JOC_MSVC_TARGETS joc_tests joc_headers_c joc_headers_cpp)
endif()
if(JOC_BUILD_DEVTOOLS)
list(APPEND JOC_MSVC_TARGETS joc_rosella_probe joc_dump joc_stream_probe joc_sofa_probe joc_sofa_field_probe joc_dictionary_probe)
endif()
if(MSVC)
set(JOC_MSVC_FLAGS /W4 /permissive- /Zc:__cplusplus /utf-8 /EHsc /GR- /fp:precise)
# The writers use std::fopen for seekable, byte-exact output.
set(JOC_MSVC_DEFINES _CRT_SECURE_NO_WARNINGS)
# C4324 comes from the reused kernel's std::barrier member at /W4; it is
# pre-existing behaviour of a verbatim file, so the warning is silenced
# rather than the file edited.
set_source_files_properties(src/joc_core/eac3joc_core.cpp
PROPERTIES COMPILE_OPTIONS "/wd4324")
foreach(target IN LISTS JOC_MSVC_TARGETS)
set_property(TARGET ${target} PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
# No /arch here on purpose: the baseline units keep the architecture's
# guaranteed ISA and only the src/simd `kernels_intrin_*` units carry a
# wider one (their flags are set per source file above).
target_compile_options(${target} PRIVATE ${JOC_MSVC_FLAGS}
$<$<CONFIG:Release>:/O2>
$<$<CONFIG:Release>:/Oi>)
target_compile_definitions(${target} PRIVATE ${JOC_MSVC_DEFINES})
endforeach()
target_link_options(joc_core PRIVATE /INCREMENTAL:NO /OPT:REF /OPT:ICF)
target_link_options(joc_cli PRIVATE /INCREMENTAL:NO)
else()
foreach(target IN LISTS JOC_MSVC_TARGETS)
target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic -fno-fast-math
$<$<CONFIG:Release>:-O3>)
endforeach()
endif()
# Headers listed for IDE visibility.
target_sources(joc_core PRIVATE
include/joc_core.h
include/joc_stream.h
include/eac3joc_core.h
src/joc_bitstream/joc_huffman_tables.h
src/joc_core/qmf_tables.h
src/speaker/speaker_layouts.h
)
install(TARGETS joc_core joc_cli
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)