cmake_minimum_required(VERSION 3.25) # Debian 12 ships 3.25 project(VulkanEngine) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake") include(BuildInfo) include(FetchContent) # LTO include(CheckIPOSupported) check_ipo_supported(RESULT LTO_SUPPORTED OUTPUT LTO_ERROR) # --------------------------------------------------------------------------- # Build modes # (default) Nix dev build, deps from the Nix shell # FETCH_DEPS + PORTABLE Single directory, everything static except # glibc (and the Vulkan loader, loaded via volk) # FETCH_DEPS + BUNDLE bin/ + lib/, project libs shared in lib/, # system libstdc++ / libc / loader are used # --------------------------------------------------------------------------- option(VULKANENGINE_PORTABLE "Build a Linux binary without Nix-specific runtime paths (static libs)" OFF ) option(VULKANENGINE_BUNDLE "Build a Linux bundle using libraries from the bundle directory" OFF ) option(VULKANENGINE_FETCH_DEPS "Fetch dependencies with FetchContent instead of using system/Nix packages" OFF ) set(VULKANENGINE_PREBUILT_SHADERS "" CACHE PATH "Directory containing precompiled .spv files (skips glslang/slangc)" ) if(VULKANENGINE_PORTABLE AND VULKANENGINE_BUNDLE) message(FATAL_ERROR "VULKANENGINE_PORTABLE and VULKANENGINE_BUNDLE are mutually exclusive") endif() if((VULKANENGINE_PORTABLE OR VULKANENGINE_BUNDLE) AND NOT VULKANENGINE_FETCH_DEPS) message(FATAL_ERROR "PORTABLE/BUNDLE builds need -DVULKANENGINE_FETCH_DEPS=ON " "(Nix libraries must not leak into the result)") endif() set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # RPATH / runtime paths if(VULKANENGINE_PORTABLE) message(STATUS "VulkanEngine: PORTABLE build") set(CMAKE_SKIP_RPATH TRUE) elseif(VULKANENGINE_BUNDLE) message(STATUS "VulkanEngine: BUNDLE build") set(CMAKE_SKIP_BUILD_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) # Executables are installed in bin/ # Libraries are installed in lib/ set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) # Every shared lib we build ends up here, installed as-is into lib/ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") else() message(STATUS "VulkanEngine: normal build") endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") find_program(GCC_AR NAMES gcc-ar REQUIRED) find_program(GCC_RANLIB NAMES gcc-ranlib REQUIRED) set(CMAKE_AR "${GCC_AR}") set(CMAKE_RANLIB "${GCC_RANLIB}") endif() # --------------------------------------------------------------------------- # Dependencies # --------------------------------------------------------------------------- set(ENGINE_EXTRA_DEFINITIONS "") if(VULKANENGINE_FETCH_DEPS) # Portable = static libs, bundle = shared libs (in lib/) if(VULKANENGINE_BUNDLE) set(BUILD_SHARED_LIBS ON) else() set(BUILD_SHARED_LIBS OFF) endif() set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(VULKAN_HEADERS_ENABLE_MODULE OFF CACHE BOOL "" FORCE) set(VULKAN_HEADERS_ENABLE_TESTS OFF CACHE BOOL "" FORCE) set(VULKAN_HEADERS_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) set(VOLK_PULL_IN_VULKAN OFF CACHE BOOL "" FORCE) set(VOLK_INSTALL OFF CACHE BOOL "" FORCE) set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) set(GLFW_INSTALL OFF CACHE BOOL "" FORCE) set(FMT_INSTALL OFF CACHE BOOL "" FORCE) set(FMT_TEST OFF CACHE BOOL "" FORCE) set(SIMDJSON_DEVELOPER_MODE OFF CACHE BOOL "" FORCE) set(CLI11_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(GLM_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(GLM_BUILD_LIBRARY OFF CACHE BOOL "" FORCE) # Keep these versions in sync with your nixpkgs (check the tags exist) FetchContent_Declare(VulkanHeaders GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Headers.git GIT_TAG v1.4.309) FetchContent_Declare(volk GIT_REPOSITORY https://github.com/zeux/volk.git GIT_TAG 1.4.304) FetchContent_Declare(VMA GIT_REPOSITORY https://github.com/GPUOpen-LibrariesAndPatches/VulkanMemoryAllocator.git GIT_TAG v3.2.1) FetchContent_Declare(glfw GIT_REPOSITORY https://github.com/glfw/glfw.git GIT_TAG 3.4) FetchContent_Declare(vk-bootstrap GIT_REPOSITORY https://github.com/charles-lunarg/vk-bootstrap.git GIT_TAG v1.4.309) FetchContent_Declare(fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 11.0.2) FetchContent_Declare(CLI11 GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git GIT_TAG v2.4.2) FetchContent_Declare(glm GIT_REPOSITORY https://github.com/g-truc/glm.git GIT_TAG 1.0.1) FetchContent_Declare(simdjson GIT_REPOSITORY https://github.com/simdjson/simdjson.git GIT_TAG v3.10.1) # Vulkan headers first: vk-bootstrap and volk pick up Vulkan::Headers FetchContent_MakeAvailable(VulkanHeaders) FetchContent_MakeAvailable(volk VMA glfw vk-bootstrap fmt CLI11 glm simdjson) target_link_libraries(volk PUBLIC Vulkan::Headers) # No link-time dependency on libvulkan: functions come from volk list(APPEND ENGINE_EXTRA_DEFINITIONS VK_NO_PROTOTYPES VMA_STATIC_VULKAN_FUNCTIONS=0 VMA_DYNAMIC_VULKAN_FUNCTIONS=1 ) else() # Nix path (unchanged) find_package( glfw3 REQUIRED ) find_package( glm REQUIRED ) find_package( VulkanMemoryAllocator CONFIG REQUIRED ) find_package( CLI11 REQUIRED ) find_package( simdjson REQUIRED ) endif() # fastgltf because it's not in nixpkgs (declared after the switch above so it # inherits BUILD_SHARED_LIBS) FetchContent_Declare( fastgltf GIT_REPOSITORY https://github.com/spnda/fastgltf.git GIT_TAG v0.8.0 ) FetchContent_MakeAvailable(fastgltf) # --------------------------------------------------------------------------- # Shader compilers (not needed when precompiled shaders are provided) # --------------------------------------------------------------------------- if(NOT VULKANENGINE_PREBUILT_SHADERS) add_executable (glslang::validator IMPORTED) find_program ( GLSLANG_VALIDATOR "glslangValidator" HINTS $ENV{VULKAN_SDK}/bin REQUIRED ) set_property ( TARGET glslang::validator PROPERTY IMPORTED_LOCATION "${GLSLANG_VALIDATOR}" ) find_program(SLANGC_EXECUTABLE slangc HINTS $ENV{VULKAN_SDK}/bin REQUIRED) endif() # Helper functions to build shaders function(addGlslShader SOURCE OUTPUT_DIR) get_filename_component(SHADER_NAME ${SOURCE} NAME_WE) set(SPV_FILE "${OUTPUT_DIR}/${SHADER_NAME}.spv") add_custom_command( OUTPUT ${SPV_FILE} COMMAND ${CMAKE_COMMAND} -E make_directory ${OUTPUT_DIR} COMMAND glslang::validator -V --target-env vulkan1.4 # Change if needed -o ${SPV_FILE} ${SOURCE} DEPENDS ${SOURCE} COMMENT "Compiling GLSL shader: ${SOURCE} -> ${SPV_FILE}" VERBATIM ) set(SPV_FILE ${SPV_FILE} PARENT_SCOPE) endfunction() function(addSlangShader SOURCES ENTRY_POINTS OUTPUT_DIR) set(SPV_FILE "${OUTPUT_DIR}/slang.spv") add_custom_command( OUTPUT ${SPV_FILE} COMMAND ${CMAKE_COMMAND} -E make_directory ${OUTPUT_DIR} COMMAND ${SLANGC_EXECUTABLE} ${SOURCES} -target spirv -profile spirv_1_6+spvRayQueryKHR # Change if needed -emit-spirv-directly -fvk-use-entrypoint-name ${ENTRY_POINTS} -o ${SPV_FILE} DEPENDS ${SOURCES} COMMENT "Compiling Slang shaders -> ${SPV_FILE}" VERBATIM ) set(SPV_FILE ${SPV_FILE} PARENT_SCOPE) endfunction() # A function to automate executables aditions function ( addBinary BINARY_NAME ) cmake_parse_arguments ( BINARY "IMGUI;LICENSE" "SOURCE" "SHADERS;SLANG_SHADERS;SLANG_ENTRIES;LIBS;TEXTURES;MODELS" ${ARGN} ) if (DEFINED BINARY_SOURCE) add_executable ( ${BINARY_NAME} src/${BINARY_SOURCE}.cpp ) endif() target_include_directories( ${BINARY_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src ) # Sets the output dir to have clear separation between binaries set_target_properties ( ${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${BINARY_NAME} CXX_STANDARD 20 # C++ 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF CXX_SCAN_FOR_MODULES OFF ) # Sets per binary LTO if(LTO_SUPPORTED AND CMAKE_BUILD_TYPE STREQUAL "Release") set_target_properties(${BINARY_NAME} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) message("Enabled LTO") endif() # Portable: libstdc++/libgcc statically linked so the binary does not depend # on the host's version. NOT done for bundle: the shared libs in lib/ use the # system libstdc++, and mixing two copies in one process is asking for trouble. if(VULKANENGINE_PORTABLE) target_link_options(${BINARY_NAME} PRIVATE -static-libstdc++ -static-libgcc) endif() # Define VULKAN_HPP_HANDLE_ERROR_OUT_OF_DATE_AS_SUCCESS to treat VK_ERROR_OUT_OF_DATE_KHR as a success code target_compile_definitions ( ${BINARY_NAME} PRIVATE "VULKAN_HPP_HANDLE_ERROR_OUT_OF_DATE_AS_SUCCESS" $<$:DEBUG> # So CMAKE expose a #define DEBUG for debug builds # Exposes versions BUILD_ID="${ENGINE_VERSION}" ${ENGINE_EXTRA_DEFINITIONS} ) if(BINARY_IMGUI) target_include_directories(${BINARY_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include/imgui ${CMAKE_SOURCE_DIR}/include/imgui/backends ) target_sources(${BINARY_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include/imgui/imgui.cpp ${CMAKE_SOURCE_DIR}/include/imgui/imgui_draw.cpp ${CMAKE_SOURCE_DIR}/include/imgui/imgui_tables.cpp ${CMAKE_SOURCE_DIR}/include/imgui/imgui_widgets.cpp ${CMAKE_SOURCE_DIR}/include/imgui/imgui_demo.cpp #${CMAKE_SOURCE_DIR}/include/imgui/imgui_memory_editor.h ${CMAKE_SOURCE_DIR}/include/imgui/backends/imgui_impl_glfw.cpp ${CMAKE_SOURCE_DIR}/include/imgui/backends/imgui_impl_vulkan.cpp ) # imgui's Vulkan backend must use volk when there is no linked loader if(VULKANENGINE_FETCH_DEPS) target_compile_definitions(${BINARY_NAME} PRIVATE IMGUI_IMPL_VULKAN_USE_VOLK) endif() endif() if(BINARY_LICENSE) file(COPY ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION ${CMAKE_BINARY_DIR}/${BINARY_NAME}) endif() if(DEFINED BINARY_SHADERS) set(SHADER_OUTPUT_DIR ${CMAKE_BINARY_DIR}/${BINARY_NAME}/shaders) if(VULKANENGINE_PREBUILT_SHADERS) # Copy the .spv produced by the Nix job instead of compiling them file(GLOB PREBUILT_SPV CONFIGURE_DEPENDS "${VULKANENGINE_PREBUILT_SHADERS}/*.spv") if(NOT PREBUILT_SPV) message(FATAL_ERROR "No .spv found in ${VULKANENGINE_PREBUILT_SHADERS}") endif() file(COPY ${PREBUILT_SPV} DESTINATION ${SHADER_OUTPUT_DIR}) else() set(ALL_SPV_FILES "") set(EXPANDED_SHADERS "") foreach(SHADER_PATTERN ${BINARY_SHADERS}) if(SHADER_PATTERN MATCHES "[*?]") file(GLOB SHADER_FILES CONFIGURE_DEPENDS ${SHADER_PATTERN}) list(APPEND EXPANDED_SHADERS ${SHADER_FILES}) else() list(APPEND EXPANDED_SHADERS ${SHADER_PATTERN}) endif() endforeach() foreach(SHADER_SRC ${EXPANDED_SHADERS}) if(NOT IS_ABSOLUTE ${SHADER_SRC}) set(SHADER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/${SHADER_SRC}) endif() get_filename_component(EXT ${SHADER_SRC} EXT) if(EXT STREQUAL ".slang") addSlangShader(${SHADER_SRC} "${BINARY_SLANG_ENTRIES}" ${SHADER_OUTPUT_DIR}) list(APPEND ALL_SPV_FILES ${SPV_FILE}) else() addGlslShader(${SHADER_SRC} ${SHADER_OUTPUT_DIR}) list(APPEND ALL_SPV_FILES ${SPV_FILE}) endif() endforeach() add_custom_target(shaders_${BINARY_NAME} DEPENDS ${ALL_SPV_FILES}) add_dependencies(${BINARY_NAME} shaders_${BINARY_NAME}) endif() endif() if (DEFINED BINARY_LIBS) target_link_libraries ( ${BINARY_NAME} PRIVATE ${BINARY_LIBS} ) endif() if (DEFINED BINARY_MODELS) # Adds the 3D models in the binary folder set(RESOLVED_MODELS "") foreach(MODEL_PATTERN ${BINARY_MODELS}) file(GLOB MATCHED_MODELS CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/assets/models/${MODEL_PATTERN}" ) list(APPEND RESOLVED_MODELS ${MATCHED_MODELS}) endforeach() if (RESOLVED_MODELS) file(COPY ${RESOLVED_MODELS} DESTINATION ${CMAKE_BINARY_DIR}/${BINARY_NAME}/models) endif() endif() if (DEFINED BINARY_TEXTURES) # Adds the textures in the binary folder set(RESOLVED_TEXTURES "") foreach(TEX_PATTERN ${BINARY_TEXTURES}) file(GLOB MATCHED_TEXTURES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/assets/textures/${TEX_PATTERN}" ) list(APPEND RESOLVED_TEXTURES ${MATCHED_TEXTURES}) endforeach() if (RESOLVED_TEXTURES) file(COPY ${RESOLVED_TEXTURES} DESTINATION ${CMAKE_BINARY_DIR}/${BINARY_NAME}/textures) endif() endif () endfunction() # --------------------------------------------------------------------------- # Binaries # --------------------------------------------------------------------------- list( APPEND lib_list # Libs used glfw fmt vk-bootstrap CLI11::CLI11 fastgltf ) if(VULKANENGINE_FETCH_DEPS) # No libvulkan at link time: volk loads libvulkan.so.1 at runtime. # Remove what your code does not use (simdjson, glm...). list(APPEND lib_list volk Vulkan::Headers GPUOpen::VulkanMemoryAllocator glm::glm-header-only simdjson::simdjson ) else() list(APPEND lib_list vulkan) endif() addBinary ( engine SOURCE Engine/main # Flags IMGUI LICENSE SHADERS shaders/* MODELS * LIBS ${lib_list} ) addBinary ( server SOURCE Server/main ) # --------------------------------------------------------------------------- # Install (used for portable / bundle packaging) # dist/bin/{engine,server,shaders,models,textures} # dist/lib/*.so* (bundle only) # dist/LICENSE # Never bundled: libc, libstdc++, libvulkan, GL, X11, Wayland. # --------------------------------------------------------------------------- install(TARGETS engine server RUNTIME DESTINATION bin) install(DIRECTORY ${CMAKE_BINARY_DIR}/engine/shaders DESTINATION bin OPTIONAL) install(DIRECTORY ${CMAKE_BINARY_DIR}/engine/models DESTINATION bin OPTIONAL) install(DIRECTORY ${CMAKE_BINARY_DIR}/engine/textures DESTINATION bin OPTIONAL) install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION .) if(VULKANENGINE_BUNDLE) # Every shared lib built from fetched deps (glfw, fmt, fastgltf and its own # simdjson, ...) with its symlinks. libstdc++ etc. are not built here. install(DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ DESTINATION lib USE_SOURCE_PERMISSIONS FILES_MATCHING PATTERN "*.so*" ) endif()