# # Singe 3 # Copyright (C) 2006-2026 Scott Duensing # # 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 . 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/.xcf as .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) # The picture shown where a game's art should be. Drawn here rather than kept as a file: it is # ours, it needs no licence, and it cannot drift from what the code expects. Loud on purpose -- # a blank space reads as a layout mistake, this reads as "your path is wrong". add_custom_command( OUTPUT ${GENERATED_DIR}/missing.png COMMAND ${IMAGEMAGICK} -size 256x256 pattern:checkerboard -auto-level -fill "#c0208090" -colorize 60 -fill "#ffd84a" -pointsize 34 -gravity center -annotate +0-14 "MISSING" -fill "#ffffffcc" -pointsize 18 -annotate +0+22 "art" -bordercolor "#ffd84a" -border 4 ${GENERATED_DIR}/missing.png COMMENT "Drawing missing.png" VERBATIM ) singeEmbed(${GENERATED_DIR}/missing.png ${GENERATED_DIR}/missing_png.h "") 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/Tools.singe ${GENERATED_DIR}/Tools_singe.h "") # Forge, the authoring tool, is built from assets/Forge but is NOT embedded: it is distributed on # its own, not with the engine. Nothing of it -- not the editor, not the compiler, not the runtime # a game it builds loads -- belongs inside Singe. singeEmbed(${CMAKE_SOURCE_DIR}/assets/Net.singe ${GENERATED_DIR}/Net_singe.h "") singeEmbed(${CMAKE_SOURCE_DIR}/assets/cacerts.pem ${GENERATED_DIR}/cacerts_pem.h "") singeEmbed(${CMAKE_SOURCE_DIR}/assets/Master.singe ${GENERATED_DIR}/Master_singe.h "") singeEmbed(${CMAKE_SOURCE_DIR}/assets/Backdrop.singe ${GENERATED_DIR}/Backdrop_singe.h "") singeEmbed(${CMAKE_SOURCE_DIR}/assets/MenuDocument.singe ${GENERATED_DIR}/MenuDocument_singe.h "") singeEmbed(${CMAKE_SOURCE_DIR}/assets/MenuOverlay.singe ${GENERATED_DIR}/MenuOverlay_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 "") # The two models the menu's intro is made of. Singe's own branding, so they belong with the engine # the way the font and the logos do -- the menu cannot reach into a game directory for them. singeEmbed(${CMAKE_SOURCE_DIR}/assets/DragonModel.glb ${GENERATED_DIR}/DragonModel_glb.h "") singeEmbed(${CMAKE_SOURCE_DIR}/assets/SingeText.glb ${GENERATED_DIR}/SingeText_glb.h "") # Menu background video: a recording of the backdrop the menu draws, for the machines that cannot # draw it. It is not built here -- rendering it needs a GPU device and the engine itself, which is # the wrong way round for a build -- so it is an asset like the models are, made by # util/renderMenuVideo.py and re-made whenever assets/Backdrop.singe changes. singeEmbed(${CMAKE_SOURCE_DIR}/assets/menuBackground.mkv ${GENERATED_DIR}/menuBackground_mkv.h "") # The same sound the video carries, for the renderer that draws the backdrop instead of playing it. # util/renderMenuVideo.py writes the two together. singeEmbed(${CMAKE_SOURCE_DIR}/assets/menuIntro.flac ${GENERATED_DIR}/menuIntro_flac.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 "") # The licence Singe is under and the notices its third-party libraries and assets require. Both # have to reach whoever gets the binary, so they travel inside it like the manual does. singeEmbed(${CMAKE_SOURCE_DIR}/COPYING ${GENERATED_DIR}/COPYING.h "") singeEmbed(${CMAKE_SOURCE_DIR}/LICENSES ${GENERATED_DIR}/LICENSES.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 "") singeEmbedLua(thirdparty/middleclass/middleclass.lua "") singeEmbedLua(thirdparty/lume/lume.lua "") singeEmbedLua(thirdparty/inspect.lua/inspect.lua "") singeEmbedLua(thirdparty/bump.lua/bump.lua "") # Forge's manual, rendered the same way. It is not embedded: the superbuild packs it into # Forge.game (cmake/Superbuild.cmake), which is the one thing that ships Forge. add_custom_command( OUTPUT ${MANUAL_DIR}/Forge.pdf COMMAND ${CMAKE_COMMAND} -E make_directory ${MANUAL_DIR} COMMAND ${ASCIIDOCTOR_PDF} -a revnumber=${PROJECT_VERSION} ${CMAKE_SOURCE_DIR}/docs/Forge.adoc -o ${MANUAL_DIR}/Forge.pdf DEPENDS docs/Forge.adoc COMMENT "Rendering Forge.pdf" VERBATIM ) add_custom_target(forgeManual ALL DEPENDS ${MANUAL_DIR}/Forge.pdf) # 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/collide.c src/collide.h src/common.h src/decode.c src/decode.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/persist.c src/persist.h src/scheduler.c src/scheduler.h src/stats.c src/stats.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/midi.c src/midi.h src/midiIo.c src/midiIo.h thirdparty/tinysoundfont/tsf.h thirdparty/tinysoundfont/tml.h src/render.c src/render.h src/renderGles.c src/renderGlesApi.h src/renderGlesLoad.c src/renderGpu.c 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) # The stateless decoders are a separate interface again, and FFmpeg builds them only when its # own dependencies were met, so the engine follows what FFmpeg actually did. if(EXISTS ${BUILD_DIR}/build/ffmpeg/config.h) file(READ ${BUILD_DIR}/build/ffmpeg/config.h ffmpegConfig) if(ffmpegConfig MATCHES "#define CONFIG_V4L2_REQUEST 1") target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE SINGE_V4L2_REQUEST_DECODE) message(STATUS "FFmpeg carries the V4L2 stateless decoders; enabling them in the player") endif() endif() 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,CoreMIDI -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 -lrpcrt4 -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 ) # Not every archive exists in every tree: SDL3_image builds its own jpeg and png decoders where the # platform has none (macOS uses ImageIO for jpeg), brotli arrives with FreeType's WOFF2 support, and # libgme with the chiptune decoder. Link whichever ones are there. foreach(optionalLib libjpeg.a libpng16.a libbrotlidec.a libbrotlicommon.a libgme.a libdav1d.a libjxl_dec.a libhwy.a) if(EXISTS ${BUILD_DIR}/lib/${optionalLib}) list(APPEND STATIC_LIBS ${BUILD_DIR}/lib/${optionalLib}) 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() # SPIRV-Cross turns the same SPIR-V into GLSL ES for the GLES backend (PLAN.md section 56). Unlike # shadercross this one is not fatal when missing: the header then carries null ES strings, the GLES # backend declines to start, and every other platform builds exactly as before. if(NOT SINGE_SPIRV_CROSS) find_program(SINGE_SPIRV_CROSS spirv-cross) endif() if(NOT SINGE_SPIRV_CROSS) message(WARNING "spirv-cross was not found; this build will have no OpenGL ES shaders and so no GLES backend.") endif() set(shaderHeader ${CMAKE_BINARY_DIR}/generated/shaders/sceneShaders.h) add_custom_command( OUTPUT ${shaderHeader} COMMAND ${CMAKE_COMMAND} -DSHADERCROSS=${SINGE_SHADERCROSS} -DSPIRVCROSS=${SINGE_SPIRV_CROSS} -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} -DSPIRVCROSS=${SINGE_SPIRV_CROSS} -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. 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::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::Core ${SYSTEM_LIBS} -pthread -lm) endif() install(TARGETS ${CMAKE_PROJECT_NAME})