cmake_minimum_required(VERSION 3.21) project(townengine LANGUAGES C) # SDL dependencies # for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default if(NOT EMSCRIPTEN) find_package(SDL2 REQUIRED GLOBAL) endif() # CMake actually has no default configuration and it's toolchain file dependent, set debug if not specified if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() if(NOT TWN_SANITIZE AND CMAKE_BUILD_TYPE MATCHES Debug) set(TWN_SANITIZE ON) endif() set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(TWN_TARGET townengine CACHE INTERNAL "") set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "") # feature configuration, set them with -DFEATURE=ON/OFF in cli option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON) # todo: figure out how to compile for dynamic linking instead if(EMSCRIPTEN) if(TWN_FEATURE_DYNLIB_GAME) message(WARNING "TWN_FEATURE_DYNLIB_GAME is set, but not supported - it is turned off") set(TWN_FEATURE_DYNLIB_GAME OFF CACHE INTERNAL "") endif() endif() if(HAIKU) if(TWN_SANITIZE) message(WARNING "TWN_SANITIZE is set, but not supported - it is turned off") set(TWN_SANITIZE OFF CACHE INTERNAL "") endif() endif() # add -fPIC globally so that it's linked well add_compile_options($<$:-fPIC>) set(PHYSFS_BUILD_SHARED FALSE) set(PHYSFS_DISABLE_INSTALL TRUE) set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall") set(PHYSFS_ARCHIVE_GRP OFF) set(PHYSFS_ARCHIVE_WAD OFF) set(PHYSFS_ARCHIVE_HOG OFF) set(PHYSFS_ARCHIVE_MVL OFF) set(PHYSFS_ARCHIVE_QPAK OFF) set(PHYSFS_ARCHIVE_SLB OFF) set(PHYSFS_ARCHIVE_ISO9660 OFF) set(PHYSFS_ARCHIVE_VDF OFF) add_subdirectory(third-party/physfs ${CMAKE_CURRENT_BINARY_DIR}/third-party/physfs SYSTEM) add_subdirectory(third-party/libxm ${CMAKE_CURRENT_BINARY_DIR}/third-party/libxm SYSTEM) if(LINUX) set(SYSTEM_SOURCE_FILES src/system/linux/twn_elf.c $<$:src/game_object/twn_linux_game_object.c>) elseif(WIN32) set(SYSTEM_SOURCE_FILES $<$:src/game_object/twn_win32_game_object.c>) else() set(SYSTEM_SOURCE_FILES) endif() if(EMSCRIPTEN) set(TWN_RENDERING_API WEBGL1) else() set(TWN_RENDERING_API OPENGL_15) endif() if(TWN_RENDERING_API MATCHES OPENGL_15) set(SYSTEM_SOURCE_FILES ${SYSTEM_SOURCE_FILES} src/rendering/twn_gl_any_rendering.c src/rendering/twn_gl_15_rendering.c src/rendering/twn_gl_15_gpu_texture.c) endif() set(TWN_THIRD_PARTY_SOURCE_FILES third-party/physfs/extras/physfsrwops.c third-party/stb/stb_vorbis.c third-party/tomlc99/toml.c $<$>:third-party/glad/src/glad.c>) set(TWN_SOURCE_FILES src/twn_loop.c src/twn_main.c src/twn_context.c include/twn_context.h src/twn_audio.c include/twn_audio.h src/twn_util.c include/twn_util.h src/twn_input.c include/twn_input.h src/twn_camera.c include/twn_camera.h src/twn_textures.c src/twn_textures_c.h src/rendering/twn_draw.c src/rendering/twn_draw_c.h src/rendering/twn_sprites.c src/rendering/twn_text.c src/rendering/twn_triangles.c src/rendering/twn_circles.c src/rendering/twn_skybox.c src/rendering/twn_fog.c # for dynamic load based solution main is compiled in a separate target $<$>:src/twn_main.c src/game_object/twn_static_game_object.c> ${SYSTEM_SOURCE_FILES}) list(TRANSFORM TWN_SOURCE_FILES PREPEND ${TWN_ROOT_DIR}/) add_library(twn_third_parties STATIC ${TWN_THIRD_PARTY_SOURCE_FILES}) # base engine code, reused for games and tools if(TWN_FEATURE_DYNLIB_GAME) add_library(${TWN_TARGET} SHARED ${TWN_SOURCE_FILES} ${twn_third_parties}) else() add_library(${TWN_TARGET} STATIC ${TWN_SOURCE_FILES} ${twn_third_parties}) endif() source_group(TREE ${TWN_ROOT_DIR} FILES ${TWN_SOURCE_FILES}) set_target_properties(${TWN_TARGET} PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED ON C_EXTENSIONS ON) # extensions are required by stb_ds.h # precompile commonly used not-so-small headers target_precompile_headers(${TWN_TARGET} PRIVATE $<$>:third-party/glad/include/glad/glad.h> third-party/stb/stb_ds.h) function(give_options_without_warnings target) set(BUILD_FLAGS # these SHOULDN'T break anything... -fno-math-errno -ffp-contract=fast -fno-signed-zeros -fno-trapping-math -freciprocal-math $<$:-sUSE_SDL=2>) set(BUILD_FLAGS_RELEASE -O3 -flto=auto -mavx -mavx2 -Wl,--gc-sections -fdata-sections -ffunction-sections -funroll-loops -fomit-frame-pointer -s) set(BUILD_FLAGS_DEBUG -O0 -g3 -gdwarf -fno-omit-frame-pointer $<$:-fstack-protector-all -fsanitize=undefined -fsanitize=address> $<$:-gsource-map>) target_compile_options(${target} PUBLIC ${BUILD_FLAGS} $<$:${BUILD_FLAGS_RELEASE}> $<$:${BUILD_FLAGS_DEBUG}> $<$:-Wl,-rpath,$ORIGIN/>) target_link_options(${target} PUBLIC ${BUILD_FLAGS} # -Wl,--no-undefined # TODO: use later for implementing no-libc $<$:${BUILD_FLAGS_RELEASE}> $<$:${BUILD_FLAGS_DEBUG}> $<$:-Wl,-rpath,$ORIGIN/> -Bsymbolic-functions -Wl,--hash-style=gnu) target_compile_definitions(${target} PUBLIC $<$:TWN_FEATURE_DYNLIB_GAME>) endfunction() function(give_options target) give_options_without_warnings(${target}) set(WARNING_FLAGS_CLANG -Weverything -Wno-padded -Wno-declaration-after-statement -Wno-unsafe-buffer-usage -Wno-unused-command-line-argument) set(WARNING_FLAGS -Wall -Wextra -Wpedantic -Wshadow -Wdouble-promotion -Wconversion -Wno-sign-conversion -Werror=vla -Wno-missing-field-initializers -Wunused-result $<$:-Wcast-align=strict>) target_compile_options(${target} PRIVATE ${WARNING_FLAGS} $<$:${WARNING_FLAGS_CLANG}>) endfunction() function(include_deps target) # header-only libraries should be marked as "system includes" # to suppress compiler warnings in their code (it's not my problem after all) set(THIRD_PARTY_INCLUDES third-party/physfs/src third-party/physfs/extras third-party/libxm/include third-party/stb third-party/x-watcher third-party/tomlc99 $<$>:third-party/glad/include>) list(TRANSFORM THIRD_PARTY_INCLUDES PREPEND ${TWN_ROOT_DIR}/) target_include_directories(${target} SYSTEM PRIVATE ${THIRD_PARTY_INCLUDES}) # allow access to headers from any point in source tree target_include_directories(${target} PRIVATE ${TWN_ROOT_DIR} ${TWN_ROOT_DIR}/include) endfunction() function(link_deps target) target_link_libraries(${target} PUBLIC $<$>:SDL2::SDL2> physfs-static xms) target_include_directories(${target} PUBLIC ${SDL2_INCLUDE_DIRS}) endfunction() function(use_townengine target sources output_directory) if(TWN_FEATURE_DYNLIB_GAME) # game shared library, for reloading add_library(${target}_game SHARED ${sources}) give_options(${target}_game) include_deps(${target}_game) target_link_libraries(${target}_game PUBLIC $<$>:SDL2::SDL2> ${TWN_TARGET}) set_target_properties(${target}_game PROPERTIES OUTPUT_NAME game LIBRARY_OUTPUT_DIRECTORY ${output_directory} RUNTIME_OUTPUT_DIRECTORY ${output_directory}) # launcher binary, loads game and engine shared library add_executable(${target}_app ${TWN_ROOT_DIR}/src/twn_main.c) # todo: copy instead? # put libtownengine.so alongside the binary set_target_properties(${TWN_TARGET} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${output_directory}) else() # build and link all statically add_executable(${target}_app ${sources}) endif() set_target_properties(${target}_app PROPERTIES OUTPUT_NAME ${target} LIBRARY_OUTPUT_DIRECTORY ${output_directory} RUNTIME_OUTPUT_DIRECTORY ${output_directory}) if(EMSCRIPTEN) set_target_properties(${target}_app PROPERTIES SUFFIX .html) endif() target_compile_options(${target}_app PRIVATE $<$:--shell-file ${TWN_ROOT_DIR}/shell_minimal.html>) # system libraries find_library(MATH_LIBRARY m) if (MATH_LIBRARY) target_link_libraries(${target}_app PUBLIC ${MATH_LIBRARY}) endif() give_options(${target}_app) include_deps(${target}_app) link_deps(${target}_app) target_link_libraries(${target}_app PUBLIC ${TWN_TARGET}) if(WIN32) # copy dlls for baby windows add_custom_command(TARGET ${target}_app POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ COMMAND_EXPAND_LISTS) endif() endfunction() give_options_without_warnings(twn_third_parties) include_deps(twn_third_parties) link_deps(twn_third_parties) give_options(${TWN_TARGET}) include_deps(${TWN_TARGET}) link_deps(${TWN_TARGET}) target_link_libraries(${TWN_TARGET} PUBLIC twn_third_parties) target_include_directories(${TWN_TARGET} PRIVATE ${TWN_ROOT_DIR}/src) # move compie_commands.json into root directory so that it plays nicer with code editors without any configuration add_custom_target(copy-compile-commands ALL ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/compile_commands.json ${TWN_ROOT_DIR})