singe/CMakeLists.txt
2026-09-10 20:17:28 -05:00

790 lines
30 KiB
CMake

#
# Singe 3
# Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public Licens
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
cmake_minimum_required(VERSION 3.22)
project(singe2 VERSION 3.00 LANGUAGES C CXX)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
# The copyright end year tracks the build date so nothing has to be hand edited.
string(TIMESTAMP SINGE_COPYRIGHT_END_YEAR "%Y")
if(NOT DEFINED KANGAROO_OS)
set(KANGAROO_OS linux)
endif()
if(NOT DEFINED KANGAROO_ARCH)
set(KANGAROO_ARCH x86_64)
endif()
# Where the superbuild installs the third party libraries for this platform. SINGE_TREE lets a
# second toolchain for the same platform keep its own tree (the zig Linux build beside the gcc one).
if(NOT DEFINED SINGE_TREE)
set(SINGE_TREE ${KANGAROO_OS})
endif()
set(BUILD_DIR ${CMAKE_SOURCE_DIR}/.builddir/${SINGE_TREE}/${KANGAROO_ARCH})
set(GENERATED_DIR ${CMAKE_BINARY_DIR}/generated)
file(MAKE_DIRECTORY ${GENERATED_DIR})
# With SINGE_SUPERBUILD on (the presets' default) this configure builds the vendored libraries
# first and then Singe as a nested project; see cmake/Superbuild.cmake.
option(SINGE_SUPERBUILD "Build the vendored libraries, then Singe" OFF)
if(SINGE_SUPERBUILD)
include(cmake/Superbuild.cmake)
return()
endif()
# ===== Host tools needed to generate embedded resources =====
find_program(IMAGEMAGICK NAMES magick convert)
if(NOT IMAGEMAGICK)
message(FATAL_ERROR "ImageMagick (magick or convert) is required to render the embedded images.")
endif()
find_program(FFMPEG_TOOL NAMES ffmpeg)
if(NOT FFMPEG_TOOL)
message(FATAL_ERROR "ffmpeg is required to build the menu background video.")
endif()
find_program(LUA_TOOL NAMES lua5.4 lua)
if(NOT LUA_TOOL)
message(FATAL_ERROR "A Lua interpreter is required to generate the LuaSec option table.")
endif()
find_program(ASCIIDOCTOR_PDF NAMES asciidoctor-pdf)
if(NOT ASCIIDOCTOR_PDF)
message(FATAL_ERROR "asciidoctor-pdf is required to build the embedded manual (gem install asciidoctor-pdf rouge).")
endif()
find_program(ASCIIDOCTOR NAMES asciidoctor)
# ===== Generated sources =====
# Version header and Windows resources come from the project() line above.
configure_file(src/version.h.in ${GENERATED_DIR}/version.h @ONLY)
configure_file(src/singe.rc.in ${GENERATED_DIR}/singe.rc @ONLY)
# LuaSec needs its headers reachable as <luasocket/...>.
file(GLOB LUASOCKET_HEADERS thirdparty/luasocket/src/*.h)
file(COPY ${LUASOCKET_HEADERS} DESTINATION ${GENERATED_DIR}/include/luasocket)
# LuaSec option table generated from the OpenSSL headers that were built.
add_custom_command(
OUTPUT ${GENERATED_DIR}/luasec_options.c
COMMAND ${CMAKE_COMMAND} -DOUTPUT=${GENERATED_DIR}/luasec_options.c -DCOMMAND=${LUA_TOOL} "-DARGS=${CMAKE_SOURCE_DIR}/thirdparty/luasec/src/options.lua;-g;${BUILD_DIR}/include/openssl/ssl.h" -P ${CMAKE_SOURCE_DIR}/cmake/runToFile.cmake
DEPENDS thirdparty/luasec/src/options.lua ${BUILD_DIR}/include/openssl/ssl.h cmake/runToFile.cmake
COMMENT "Generating LuaSec options"
VERBATIM
)
# Embeds a file as a C array. The symbol name is derived from the file name, as xxd does.
set(EMBEDDED_HEADERS)
function(singeEmbed input output prefix)
get_filename_component(name ${input} NAME)
string(MAKE_C_IDENTIFIER "${prefix}${name}" symbol)
get_filename_component(guardName ${output} NAME)
string(MAKE_C_IDENTIFIER "${guardName}" guard)
string(TOUPPER "${guard}" guard)
add_custom_command(
OUTPUT ${output}
COMMAND ${CMAKE_COMMAND} -DINPUT=${input} -DOUTPUT=${output} -DGUARD=${guard} -DSYMBOL=${symbol} -DCOPYRIGHT_END_YEAR=${SINGE_COPYRIGHT_END_YEAR} -P ${CMAKE_SOURCE_DIR}/cmake/embed.cmake
DEPENDS ${input} cmake/embed.cmake
COMMENT "Embedding ${name}"
VERBATIM
)
set(EMBEDDED_HEADERS ${EMBEDDED_HEADERS} ${output} PARENT_SCOPE)
endfunction()
# The canvas size of a GIMP image, from the XCF header (ImageMagick only sees the layers, and its
# flatten crops to the first one).
function(singeXcfCanvas xcf widthVar heightVar)
file(READ ${xcf} header LIMIT 22 HEX)
string(SUBSTRING ${header} 28 8 widthHex)
string(SUBSTRING ${header} 36 8 heightHex)
math(EXPR width "0x${widthHex}")
math(EXPR height "0x${heightHex}")
set(${widthVar} ${width} PARENT_SCOPE)
set(${heightVar} ${height} PARENT_SCOPE)
endfunction()
# Renders assets/<source>.xcf as <name>.png the way GIMP would: every layer at its offset onto the
# image's own canvas, anything outside it clipped.
function(singeRenderImage name source)
singeXcfCanvas(${CMAKE_SOURCE_DIR}/assets/${source}.xcf width height)
add_custom_command(
OUTPUT ${GENERATED_DIR}/${name}.png
COMMAND ${IMAGEMAGICK} -background "rgba(0,0,0,0)" -size ${width}x${height} xc:none ${CMAKE_SOURCE_DIR}/assets/${source}.xcf -flatten ${GENERATED_DIR}/${name}.png
DEPENDS assets/${source}.xcf
COMMENT "Rendering ${name}.png (${width}x${height})"
VERBATIM
)
endfunction()
# Renders a layered image and embeds the PNG.
function(singeEmbedImage name)
singeRenderImage(${name} ${name})
singeEmbed(${GENERATED_DIR}/${name}.png ${GENERATED_DIR}/${name}.h "")
set(EMBEDDED_HEADERS ${EMBEDDED_HEADERS} PARENT_SCOPE)
endfunction()
# Embeds a Lua module from the third party tree.
function(singeEmbedLua path prefix)
get_filename_component(name ${path} NAME_WE)
singeEmbed(${CMAKE_SOURCE_DIR}/${path} ${GENERATED_DIR}/${prefix}${name}_lua.h "${prefix}")
set(EMBEDDED_HEADERS ${EMBEDDED_HEADERS} PARENT_SCOPE)
endfunction()
# Images
singeEmbedImage(font)
# The icon is drawn large; the window gets a 128 pixel copy and the Windows .ico the usual sizes,
# both cut from a square, transparent-padded render so a non-square source keeps its proportions.
set(ICON_SIZE 1024)
singeRenderImage(iconFull icon)
add_custom_command(
OUTPUT ${GENERATED_DIR}/iconSquare.png
COMMAND ${IMAGEMAGICK} ${GENERATED_DIR}/iconFull.png -background "rgba(0,0,0,0)" -resize "${ICON_SIZE}x${ICON_SIZE}>" -gravity center -extent ${ICON_SIZE}x${ICON_SIZE} ${GENERATED_DIR}/iconSquare.png
DEPENDS ${GENERATED_DIR}/iconFull.png
COMMENT "Rendering iconSquare.png"
VERBATIM
)
add_custom_command(
OUTPUT ${GENERATED_DIR}/icon.png
COMMAND ${IMAGEMAGICK} ${GENERATED_DIR}/iconSquare.png -resize 128x128 ${GENERATED_DIR}/icon.png
DEPENDS ${GENERATED_DIR}/iconSquare.png
COMMENT "Rendering icon.png"
VERBATIM
)
singeEmbed(${GENERATED_DIR}/icon.png ${GENERATED_DIR}/icon.h "")
singeEmbedImage(kangarooPunchLogo)
singeEmbedImage(singeLogo)
# Windows icon for the resource file.
add_custom_command(
OUTPUT ${GENERATED_DIR}/icon.ico
COMMAND ${IMAGEMAGICK} ${GENERATED_DIR}/iconSquare.png -define icon:auto-resize=256,64,48,32,16 ${GENERATED_DIR}/icon.ico
DEPENDS ${GENERATED_DIR}/iconSquare.png
COMMENT "Rendering icon.ico"
VERBATIM
)
# Support files extracted at first run.
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Framework.singe ${GENERATED_DIR}/Framework_singe.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/controls.cfg ${GENERATED_DIR}/controls_cfg.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/settings.cfg ${GENERATED_DIR}/settings_cfg.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Menu.singe ${GENERATED_DIR}/Menu_singe.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/MenuClassic.singe ${GENERATED_DIR}/MenuClassic_singe.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/Menu.rml ${GENERATED_DIR}/Menu_rml.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/menu.rcss ${GENERATED_DIR}/menu_rcss.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/gui.rcss ${GENERATED_DIR}/gui_rcss.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/scoreBezel.rml ${GENERATED_DIR}/scoreBezel_rml.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/scoreBezel.rcss ${GENERATED_DIR}/scoreBezel_rcss.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/subtitle.rml ${GENERATED_DIR}/subtitle_rml.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/subtitle.rcss ${GENERATED_DIR}/subtitle_rcss.h "")
singeEmbed(${CMAKE_SOURCE_DIR}/assets/FreeSansBold.ttf ${GENERATED_DIR}/FreeSansBold_ttf.h "")
# Menu background video: two clips cropped to 4:3, scaled to 720x480, keyframed every second so the
# menu's seeks are instant and the engine's keyframe warning stays quiet, and joined.
file(WRITE ${GENERATED_DIR}/menuBackground.txt "file ${GENERATED_DIR}/menuBackground1.mkv\nfile ${GENERATED_DIR}/menuBackground2.mkv\n")
add_custom_command(
OUTPUT ${GENERATED_DIR}/menuBackground.mkv
COMMAND ${FFMPEG_TOOL} -y -loglevel error -i "${CMAKE_SOURCE_DIR}/assets/Singe Engine Intro.mpg" -filter:v "crop=ih/3*4:ih,scale=720:480" -c:v libx264 -force_key_frames "expr:gte(t,n_forced)" -c:a aac -f matroska ${GENERATED_DIR}/menuBackground1.mkv
COMMAND ${FFMPEG_TOOL} -y -loglevel error -i ${CMAKE_SOURCE_DIR}/assets/180503_01_PurpleGrid.mp4 -filter:v "crop=ih/3*4:ih,scale=720:480" -c:v libx264 -force_key_frames "expr:gte(t,n_forced)" -c:a aac -f matroska ${GENERATED_DIR}/menuBackground2.mkv
COMMAND ${FFMPEG_TOOL} -y -loglevel error -f concat -safe 0 -i ${GENERATED_DIR}/menuBackground.txt -c copy ${GENERATED_DIR}/menuBackground.mkv
DEPENDS "assets/Singe Engine Intro.mpg" assets/180503_01_PurpleGrid.mp4
COMMENT "Building menuBackground.mkv"
VERBATIM
)
singeEmbed(${GENERATED_DIR}/menuBackground.mkv ${GENERATED_DIR}/menuBackground_mkv.h "")
# Calibration click for the menu's audio delay screen: 20 ms of white noise with a fast fade.
add_custom_command(
OUTPUT ${GENERATED_DIR}/click.wav
COMMAND ${FFMPEG_TOOL} -y -loglevel error -f lavfi -i "anoisesrc=d=0.02:c=white:r=44100:a=0.6" -af "afade=t=out:st=0:d=0.02:curve=exp" -ac 1 -c:a pcm_s16le ${GENERATED_DIR}/click.wav
COMMENT "Building click.wav"
VERBATIM
)
singeEmbed(${GENERATED_DIR}/click.wav ${GENERATED_DIR}/click_wav.h "")
# Manual, rendered from the AsciiDoc source into the top-level build directory beside the binaries
# (build-docs.sh writes the same files) and shipped inside the binary.
set(MANUAL_DIR ${CMAKE_SOURCE_DIR}/.builddir)
add_custom_command(
OUTPUT ${MANUAL_DIR}/Manual.pdf
COMMAND ${CMAKE_COMMAND} -E make_directory ${MANUAL_DIR}
COMMAND ${ASCIIDOCTOR_PDF} -a revnumber=${PROJECT_VERSION} ${CMAKE_SOURCE_DIR}/docs/Manual.adoc -o ${MANUAL_DIR}/Manual.pdf
DEPENDS docs/Manual.adoc
COMMENT "Rendering Manual.pdf"
VERBATIM
)
singeEmbed(${MANUAL_DIR}/Manual.pdf ${GENERATED_DIR}/Manual_pdf.h "")
# Lua libraries
singeEmbedLua(thirdparty/luasocket/src/ftp.lua "")
singeEmbedLua(thirdparty/luasocket/src/headers.lua "")
singeEmbedLua(thirdparty/luasocket/src/http.lua "")
singeEmbedLua(thirdparty/luasocket/src/ltn12.lua "")
singeEmbedLua(thirdparty/luasocket/src/mbox.lua "")
singeEmbedLua(thirdparty/luasocket/src/mime.lua "")
singeEmbedLua(thirdparty/luasocket/src/smtp.lua "")
singeEmbedLua(thirdparty/luasocket/src/socket.lua "")
singeEmbedLua(thirdparty/luasocket/src/tp.lua "")
singeEmbedLua(thirdparty/luasocket/src/url.lua "")
singeEmbedLua(thirdparty/luasec/src/https.lua "")
singeEmbedLua(thirdparty/luasec/src/ssl.lua "")
singeEmbedLua(thirdparty/librs232/bindings/lua/rs232.lua "")
singeEmbedLua(thirdparty/copas/src/copas.lua "")
singeEmbedLua(thirdparty/copas/src/copas/ftp.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/future.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/http.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/smtp.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/lock.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/queue.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/semaphore.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/timer.lua "copas_")
singeEmbedLua(thirdparty/binaryheap.lua/src/binaryheap.lua "")
singeEmbedLua(thirdparty/timerwheel.lua/src/timerwheel/timerwheel.lua "")
singeEmbedLua(thirdparty/json.lua/json.lua "")
# Optional HTML manual for browsing: cmake --build . --target docs
if(ASCIIDOCTOR)
add_custom_target(docs
COMMAND ${ASCIIDOCTOR} -a revnumber=${PROJECT_VERSION} ${CMAKE_SOURCE_DIR}/docs/Manual.adoc -o ${MANUAL_DIR}/Manual.html
DEPENDS ${MANUAL_DIR}/Manual.pdf
COMMENT "Rendering Manual.html"
VERBATIM
)
endif()
# ===== Sources =====
set(SINGE_SOURCE
src/common.h
src/embedded.h
src/frameFile.c
src/rotoZoom.c
src/rotoZoom.h
src/frameFile.h
src/main.c
src/main.h
src/pack.c
src/pack.h
src/particles.c
src/particles.h
src/physics.h
src/navRecast.cpp
src/physicsJolt.cpp
src/gui.cpp
src/gui.h
src/guiRender.cpp
src/guiRender.h
src/hdr.c
src/ktx2.h
src/ktx2Basis.cpp
thirdparty/basis_universal/transcoder/basisu_transcoder.cpp
src/model.c
src/model.h
src/math3d.c
src/math3d.h
src/scene.c
src/scene.h
src/singe.c
src/singe.h
src/stddclmr.h
src/util.c
src/util.h
src/videoPlayer.c
src/videoPlayer.h
src/vfs.c
src/vfs.h
)
# The binding reports this from sqlite3.lversion(); it is the version of thirdparty/lsqlite3/lsqlite3.c.
set(LSQLITE_VERSION "0.9.7")
set(SQLITE_SOURCE
thirdparty/sqlite/sqlite3.c
thirdparty/sqlite/sqlite3.h
thirdparty/lsqlite3/lsqlite3.c
)
set_source_files_properties(thirdparty/lsqlite3/lsqlite3.c PROPERTIES COMPILE_DEFINITIONS "LSQLITE_VERSION=\"${LSQLITE_VERSION}\"")
set(ARG_PARSER_SOURCE
thirdparty/arg_parser/carg_parser.c
thirdparty/arg_parser/carg_parser.h
)
file(GLOB LUA_SOURCE thirdparty/lua/src/*.c thirdparty/lua/src/*.h)
list(FILTER LUA_SOURCE EXCLUDE REGEX "/(lua|luac|onelua)\\.c$")
set(LUA_FILESYSTEM_SOURCE
thirdparty/luafilesystem/src/lfs.c
thirdparty/luafilesystem/src/lfs.h
)
# The PUC-Rio md5 library, as Hypseus bundles it (its sumhexa is named hexsum), so the games that
# require("md5") get the same five functions with the same behaviour.
set(LUA_MD5_SOURCE
thirdparty/md5/md5.c
thirdparty/md5/md5.h
thirdparty/md5/md5lib.c
)
# The rest of the Lua modules Hypseus exposes, so a game written against it finds them here too:
# LuaBitOp, lrandom, Lua CJSON and LPeg.
set(LUA_BITOP_SOURCE
thirdparty/luabitop/lbit.c
)
set(LUA_RANDOM_SOURCE
thirdparty/lrandom/lrandom.c
)
set(LUA_CJSON_SOURCE
thirdparty/lua-cjson/fpconv.c
thirdparty/lua-cjson/fpconv.h
thirdparty/lua-cjson/lua_cjson.c
thirdparty/lua-cjson/strbuf.c
thirdparty/lua-cjson/strbuf.h
)
set(LPEG_SOURCE
thirdparty/lpeg/lpcap.c
thirdparty/lpeg/lpcap.h
thirdparty/lpeg/lpcode.c
thirdparty/lpeg/lpcode.h
thirdparty/lpeg/lpcset.c
thirdparty/lpeg/lpcset.h
thirdparty/lpeg/lpprint.c
thirdparty/lpeg/lpprint.h
thirdparty/lpeg/lptree.c
thirdparty/lpeg/lptree.h
thirdparty/lpeg/lptypes.h
thirdparty/lpeg/lpvm.c
thirdparty/lpeg/lpvm.h
)
set(LUA_SOCKET_SOURCE
thirdparty/luasocket/src/auxiliar.c
thirdparty/luasocket/src/auxiliar.h
thirdparty/luasocket/src/buffer.c
thirdparty/luasocket/src/buffer.h
thirdparty/luasocket/src/compat.c
thirdparty/luasocket/src/compat.h
thirdparty/luasocket/src/except.c
thirdparty/luasocket/src/except.h
thirdparty/luasocket/src/inet.c
thirdparty/luasocket/src/inet.h
thirdparty/luasocket/src/io.c
thirdparty/luasocket/src/io.h
thirdparty/luasocket/src/luasocket.c
thirdparty/luasocket/src/luasocket.h
thirdparty/luasocket/src/mime.c
thirdparty/luasocket/src/mime.h
thirdparty/luasocket/src/options.c
thirdparty/luasocket/src/options.h
thirdparty/luasocket/src/select.c
thirdparty/luasocket/src/select.h
thirdparty/luasocket/src/socket.h
thirdparty/luasocket/src/tcp.c
thirdparty/luasocket/src/tcp.h
thirdparty/luasocket/src/timeout.c
thirdparty/luasocket/src/timeout.h
thirdparty/luasocket/src/udp.c
thirdparty/luasocket/src/udp.h
)
if(WIN32)
list(APPEND LUA_SOCKET_SOURCE
thirdparty/luasocket/src/wsocket.c
thirdparty/luasocket/src/wsocket.h
)
else()
list(APPEND LUA_SOCKET_SOURCE
thirdparty/luasocket/src/serial.c
thirdparty/luasocket/src/unix.c
thirdparty/luasocket/src/unix.h
thirdparty/luasocket/src/unixdgram.c
thirdparty/luasocket/src/unixdgram.h
thirdparty/luasocket/src/unixstream.c
thirdparty/luasocket/src/unixstream.h
thirdparty/luasocket/src/usocket.c
thirdparty/luasocket/src/usocket.h
)
endif()
set(LUASEC_SOURCE
${GENERATED_DIR}/luasec_options.c
thirdparty/luasec/src/compat.h
thirdparty/luasec/src/config.c
thirdparty/luasec/src/context.c
thirdparty/luasec/src/context.h
thirdparty/luasec/src/ec.c
thirdparty/luasec/src/ec.h
thirdparty/luasec/src/options.h
thirdparty/luasec/src/ssl.c
thirdparty/luasec/src/ssl.h
thirdparty/luasec/src/x509.c
thirdparty/luasec/src/x509.h
)
set(LUA_RS232_SOURCE
thirdparty/librs232/bindings/lua/luars232.c
thirdparty/librs232/src/rs232.c
)
if(WIN32)
list(APPEND LUA_RS232_SOURCE thirdparty/librs232/src/rs232_windows.c)
else()
list(APPEND LUA_RS232_SOURCE thirdparty/librs232/src/rs232_posix.c)
endif()
set(UTHASH_SOURCE
thirdparty/uthash/src/uthash.h
thirdparty/uthash/src/utlist.h
)
set(MANYMOUSE_SOURCE
thirdparty/manymouse/linux_evdev.c
thirdparty/manymouse/macosx_hidmanager.c
thirdparty/manymouse/macosx_hidutilities.c
thirdparty/manymouse/manymouse.c
thirdparty/manymouse/manymouse.h
thirdparty/manymouse/windows_wminput.c
thirdparty/manymouse/x11_xinput2.c
)
# ===== Target =====
add_executable(${CMAKE_PROJECT_NAME}
${SINGE_SOURCE}
${EMBEDDED_HEADERS}
${GENERATED_DIR}/version.h
${ARG_PARSER_SOURCE}
${LUA_SOURCE}
${LUA_FILESYSTEM_SOURCE}
${LUA_MD5_SOURCE}
${LUA_BITOP_SOURCE}
${LUA_RANDOM_SOURCE}
${LUA_CJSON_SOURCE}
${LPEG_SOURCE}
${SQLITE_SOURCE}
${LUA_SOCKET_SOURCE}
${LUASEC_SOURCE}
${LUA_RS232_SOURCE}
${UTHASH_SOURCE}
${MANYMOUSE_SOURCE}
)
if(WIN32)
if(SINGE_ZIG_TARGET)
# CMake archives every object into objects.a for MinGW links, and zig's linker cannot take a
# compiled resource from inside an archive; compiled on its own it links as a plain input.
add_custom_command(
OUTPUT ${GENERATED_DIR}/singe.res
COMMAND ${CMAKE_RC_COMPILER} /fo ${GENERATED_DIR}/singe.res ${GENERATED_DIR}/singe.rc
DEPENDS ${GENERATED_DIR}/singe.rc ${GENERATED_DIR}/icon.ico
COMMENT "Compiling resources"
VERBATIM
)
add_custom_target(singeResources DEPENDS ${GENERATED_DIR}/singe.res)
add_dependencies(${CMAKE_PROJECT_NAME} singeResources)
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE ${GENERATED_DIR}/singe.res)
else()
enable_language(RC)
target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${GENERATED_DIR}/singe.rc ${GENERATED_DIR}/icon.ico)
endif()
endif()
# Output name matches the release artifact: Singe-v3.00-Linux-x86_64
string(SUBSTRING ${KANGAROO_OS} 0 1 osInitial)
string(SUBSTRING ${KANGAROO_OS} 1 -1 osRest)
string(TOUPPER ${osInitial} osInitial)
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME "Singe-v${PROJECT_VERSION}-${osInitial}${osRest}-${KANGAROO_ARCH}")
# Warnings apply to our code only; the vendored libraries are not ours to fix.
set_source_files_properties(${SINGE_SOURCE} PROPERTIES COMPILE_OPTIONS "-Wall;-Wextra")
# librs232 stamps its build date into the module; clang under zig makes that an error by default.
set_source_files_properties(thirdparty/librs232/bindings/lua/luars232.c PROPERTIES COMPILE_OPTIONS "-Wno-date-time")
# The Basis transcoder is warning-heavy third-party C++; it is compiled quietly.
set_source_files_properties(thirdparty/basis_universal/transcoder/basisu_transcoder.cpp PROPERTIES COMPILE_OPTIONS "-w")
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
RS232_STATIC
BASISD_SUPPORT_KTX2=1
BASISD_SUPPORT_KTX2_ZSTD=1
BASISD_SUPPORT_PVRTC1=0
BASISD_SUPPORT_PVRTC2=0
BASISD_SUPPORT_ATC=0
BASISD_SUPPORT_FXT1=0
BASISD_SUPPORT_ETC2_EAC_A8=0
BASISD_SUPPORT_ETC2_EAC_RG11=0
SQLITE_DQS=0
SQLITE_THREADSAFE=1
SQLITE_DEFAULT_MEMSTATUS=0
SQLITE_OMIT_SHARED_CACHE
)
if(WIN32)
# _WIN32_WINNT=0x0600 sets the minimum compatible version of Windows to Vista.
# The function inet_pton() does not exist before then.
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
_WIN32_WINNT=0x0600
)
endif()
target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
-Wno-deprecated-declarations
)
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
${CMAKE_BINARY_DIR}
${GENERATED_DIR}/include
${BUILD_DIR}
${BUILD_DIR}/include
thirdparty/lua/src
thirdparty/sqlite
thirdparty/luasec/src
thirdparty/librs232/include
thirdparty/basis_universal/transcoder
)
target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE
${BUILD_DIR}/lib
)
# ===== Libraries =====
# Platform specific system libraries.
if(KANGAROO_OS STREQUAL "linux")
if(KANGAROO_ARCH MATCHES "^(aarch64|armhf)$")
set(SYSTEM_LIBS -ldl)
# An ARM board decodes video through its V4L2 memory-to-memory device (videoPlayer.c).
# Nothing here is particular to a Raspberry Pi: any board whose kernel offers one is served.
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE SINGE_V4L2_DECODE)
else()
# ffmpeg's vdpau hardware context is always compiled in on X11 hosts.
set(SYSTEM_LIBS -lX11 -lvdpau -ldl -rdynamic) # -rdynamic gives the crash backtrace function names
endif()
# Release binaries are stripped; the crash backtrace reads the dynamic symbols -rdynamic exports,
# which stripping keeps.
if(CMAKE_BUILD_TYPE STREQUAL "Release")
list(APPEND SYSTEM_LIBS -s)
endif()
elseif(KANGAROO_OS STREQUAL "macos")
set(SYSTEM_LIBS
-Wl,-framework,CoreVideo
-Wl,-framework,Cocoa
-Wl,-framework,IOKit
-Wl,-framework,ForceFeedback
-Wl,-framework,Carbon
-Wl,-framework,CoreAudio
-Wl,-framework,AudioToolbox
-Wl,-framework,AVFoundation
-Wl,-framework,Foundation
-Wl,-weak_framework,GameController
-Wl,-weak_framework,Metal
-Wl,-weak_framework,QuartzCore
-Wl,-weak_framework,CoreHaptics
-Wl,-weak_framework,UniformTypeIdentifiers
-liconv
-lc++
)
if(NOT SINGE_ZIG_TARGET)
# osxcross builds need clang's runtime library named; zig carries its own.
if(NOT DEFINED OSXCROSS_TARGET_DIR)
message(FATAL_ERROR "OSXCROSS_TARGET_DIR must be set for macOS builds without zig.")
endif()
list(APPEND SYSTEM_LIBS ${OSXCROSS_TARGET_DIR}/darwin/libclang_rt.osx.a)
endif()
elseif(KANGAROO_OS STREQUAL "windows")
# A GUI-subsystem executable, so no console window opens; zig's linker wants the flag spelled out.
if(SINGE_ZIG_TARGET)
set(windowsSubsystem -Wl,--subsystem,windows)
else()
set(windowsSubsystem -mwindows)
endif()
set(SYSTEM_LIBS
${windowsSubsystem}
-static
-lmingw32
-latomic
-lole32
-luser32
-lkernel32
-lgdi32
-lwinmm
-limm32
-loleaut32
-lversion
-luuid
-ladvapi32
-lsetupapi
-lshell32
-ldinput8
-lws2_32
-lbcrypt
-lcrypt32
)
if(NOT SINGE_ZIG_TARGET)
# MinGW's stack protector runtime; zig's compiler-rt carries it itself.
list(APPEND SYSTEM_LIBS -lssp)
endif()
else()
message(FATAL_ERROR "Unknown KANGAROO_OS: ${KANGAROO_OS}")
endif()
# FFmpeg's hardware decoding backends (VA-API, VDPAU, DRM, V4L2, VideoToolbox) are whatever the
# superbuild configured; its pkg-config files name the system libraries and frameworks they need, so
# read them back.
if(NOT KANGAROO_OS STREQUAL "windows")
foreach(pc libavutil libavcodec)
set(pcFile ${BUILD_DIR}/lib/pkgconfig/${pc}.pc)
if(EXISTS ${pcFile})
file(STRINGS ${pcFile} pcLibs REGEX "^Libs:")
string(REGEX REPLACE "^Libs: *" "" pcLibs "${pcLibs}")
separate_arguments(pcLibs)
set(framework FALSE)
foreach(token ${pcLibs})
if(framework)
list(APPEND SYSTEM_LIBS -Wl,-framework,${token})
set(framework FALSE)
elseif(token STREQUAL "-framework")
set(framework TRUE)
elseif(token MATCHES "^-l" AND NOT token MATCHES "^-l(avutil|avcodec|z|m|atomic|pthread|iconv)$")
list(APPEND SYSTEM_LIBS ${token})
endif()
endforeach()
endif()
endforeach()
list(REMOVE_DUPLICATES SYSTEM_LIBS)
endif()
# Static third party libraries built by build-all.sh.
set(STATIC_LIBS
${BUILD_DIR}/lib/libavcodec.a
${BUILD_DIR}/lib/libavdevice.a
${BUILD_DIR}/lib/libavfilter.a
${BUILD_DIR}/lib/libavformat.a
${BUILD_DIR}/lib/libavutil.a
${BUILD_DIR}/lib/libfreetype.a
${BUILD_DIR}/lib/libopus.a
${BUILD_DIR}/lib/libopusfile.a
${BUILD_DIR}/lib/libogg.a
${BUILD_DIR}/lib/libSDL3.a
${BUILD_DIR}/lib/libSDL3_image.a
${BUILD_DIR}/lib/libSDL3_mixer.a
${BUILD_DIR}/lib/libSDL3_ttf.a
${BUILD_DIR}/lib/libswresample.a
${BUILD_DIR}/lib/libswscale.a
${BUILD_DIR}/lib/libwavpack.a
${BUILD_DIR}/lib/libwebp.a
${BUILD_DIR}/lib/libwebpdemux.a
${BUILD_DIR}/lib/libwebpmux.a
${BUILD_DIR}/lib/libsharpyuv.a
${BUILD_DIR}/lib/libxmp.a
${BUILD_DIR}/lib/libz.a
${BUILD_DIR}/lib/libzstd.a
${BUILD_DIR}/lib/libcrypto.a
${BUILD_DIR}/lib/libssl.a
)
# SDL3_image builds its own jpeg and png decoders where the platform has none (macOS uses ImageIO
# for jpeg); link whichever archives it produced.
foreach(imageLib libjpeg.a libpng16.a)
if(EXISTS ${BUILD_DIR}/lib/${imageLib})
list(APPEND STATIC_LIBS ${BUILD_DIR}/lib/${imageLib})
endif()
endforeach()
# The scene shaders: src/shaders/scene.hlsl compiled at build time into a header of SPIR-V, DXIL
# and MSL blobs by SDL_shadercross, which the superbuild builds for the host (cmake/hostTools.cmake)
# and names in SINGE_SHADERCROSS; configuring this project alone finds one on the PATH.
if(NOT SINGE_SHADERCROSS)
find_program(SINGE_SHADERCROSS shadercross)
if(NOT SINGE_SHADERCROSS)
message(FATAL_ERROR "SDL_shadercross is needed to compile the scene shaders; build through the superbuild (cmake --preset ...) or set SINGE_SHADERCROSS.")
endif()
endif()
set(shaderHeader ${CMAKE_BINARY_DIR}/generated/shaders/sceneShaders.h)
add_custom_command(
OUTPUT ${shaderHeader}
COMMAND ${CMAKE_COMMAND} -DSHADERCROSS=${SINGE_SHADERCROSS} -DSOURCE=${CMAKE_SOURCE_DIR}/src/shaders/scene.hlsl -DOUTPUT=${shaderHeader} -P ${CMAKE_SOURCE_DIR}/cmake/shaderHeader.cmake
DEPENDS ${CMAKE_SOURCE_DIR}/src/shaders/scene.hlsl ${CMAKE_SOURCE_DIR}/src/sceneShared.h ${CMAKE_SOURCE_DIR}/cmake/shaderHeader.cmake
COMMENT "Compiling the scene shaders"
)
target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${shaderHeader})
# The GUI shaders the same way: src/shaders/gui.hlsl into guiShaders.h for guiRender.cpp. The
# entry list is a CMake list, so it is quoted as one argument and VERBATIM keeps the semicolons.
set(guiShaderHeader ${CMAKE_BINARY_DIR}/generated/shaders/guiShaders.h)
set(guiShaderEntries "guiVertex:vertex;guiFragmentColor:fragment;guiFragmentTexture:fragment;guiFragmentFilter:fragment;guiFragmentBlur:fragment;guiFragmentColorMatrix:fragment;guiFragmentGradient:fragment")
add_custom_command(
OUTPUT ${guiShaderHeader}
COMMAND ${CMAKE_COMMAND} -DSHADERCROSS=${SINGE_SHADERCROSS} -DSOURCE=${CMAKE_SOURCE_DIR}/src/shaders/gui.hlsl -DOUTPUT=${guiShaderHeader} "-DENTRIES=${guiShaderEntries}" -DPREFIX=guiShader -DTYPE=GuiShaderT -P ${CMAKE_SOURCE_DIR}/cmake/shaderHeader.cmake
DEPENDS ${CMAKE_SOURCE_DIR}/src/shaders/gui.hlsl ${CMAKE_SOURCE_DIR}/cmake/shaderHeader.cmake
COMMENT "Compiling the GUI shaders"
VERBATIM
)
target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${guiShaderHeader})
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR}/generated)
# Rockchip's hardware decoders are reached by name (videoPlayer.c), and only exist when FFmpeg was
# built against the Rockchip MPP library. Asking FFmpeg itself is what decides it.
execute_process(COMMAND ${CMAKE_COMMAND} -E echo "" OUTPUT_QUIET)
if(EXISTS ${BUILD_DIR}/lib/pkgconfig/libavcodec.pc)
file(READ ${BUILD_DIR}/lib/pkgconfig/libavcodec.pc avcodecPc)
if(avcodecPc MATCHES "rockchip_mpp")
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE SINGE_RKMPP_DECODE)
message(STATUS "FFmpeg carries the rkmpp decoders; enabling them in the player")
endif()
endif()
# Jolt Physics comes with the compile definitions it was built with (SIMD level, layer bits) through
# its exported target; the one C++ file in the tree (physicsJolt.cpp) needs them. Its C++ runtime is
# zig's own libc++, linked statically, or the host's libstdc++ under gcc; macOS already lists -lc++.
find_package(Jolt CONFIG REQUIRED PATHS ${BUILD_DIR}/lib/cmake/Jolt NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
# Recast Navigation the same way (navRecast.cpp): Recast bakes, Detour queries, DetourCrowd steers.
find_package(recastnavigation CONFIG REQUIRED PATHS ${BUILD_DIR}/lib/cmake/recastnavigation NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
# RmlUi (gui.cpp, and guiRender.cpp, the SDL_GPU render interface forked from RmlUi's Backends).
# Its package config looks for Freetype::Freetype and Lua::Lua only when they are not already
# targets, so both are declared here from the superbuild's FreeType and the engine's own Lua.
add_library(Freetype::Freetype STATIC IMPORTED)
set_target_properties(Freetype::Freetype PROPERTIES IMPORTED_LOCATION ${BUILD_DIR}/lib/libfreetype.a INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/thirdparty/SDL3_ttf/external/freetype/include)
add_library(Lua::Lua INTERFACE IMPORTED)
set_target_properties(Lua::Lua PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/thirdparty/lua/src)
find_package(RmlUi CONFIG REQUIRED PATHS ${BUILD_DIR}/lib/cmake/RmlUi NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)
# Jolt is built without RTTI, so the file that subclasses its debug renderer must be too, or the
# subclass's vtable wants type information the library never made.
set_source_files_properties(src/physicsJolt.cpp PROPERTIES COMPILE_OPTIONS "-fno-rtti")
if(NOT KANGAROO_OS STREQUAL "macos")
if(SINGE_ZIG_TARGET)
list(APPEND SYSTEM_LIBS -lc++)
else()
list(APPEND SYSTEM_LIBS -lstdc++)
endif()
endif()
# System libraries follow the static ones so their symbols resolve for everything above.
# Apple's linker has no --start-group, so the list is repeated there instead.
if(KANGAROO_OS STREQUAL "macos")
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${STATIC_LIBS} ${STATIC_LIBS} ${STATIC_LIBS} Jolt::Jolt RecastNavigation::DetourCrowd RecastNavigation::Detour RecastNavigation::Recast RmlUi::Lua RmlUi::Debugger RmlUi::Core ${SYSTEM_LIBS} -pthread -lm)
else()
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE -Wl,--start-group ${STATIC_LIBS} -Wl,--end-group Jolt::Jolt RecastNavigation::DetourCrowd RecastNavigation::Detour RecastNavigation::Recast RmlUi::Lua RmlUi::Debugger RmlUi::Core ${SYSTEM_LIBS} -pthread -lm)
endif()
install(TARGETS ${CMAKE_PROJECT_NAME})