cmake_minimum_required(VERSION 3.20) project(noita_gpu LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) if(MSVC) # Release keeps its optimisations and writes a PDB (the crash handler of noita_gpu prints a symbolised stack) add_compile_options($<$:/Zi>) add_link_options($<$:/DEBUG:FULL> $<$:/OPT:REF> $<$:/OPT:ICF>) endif() add_library(ngcore STATIC src/core/wak.cpp src/core/xml.cpp src/core/materials.cpp) target_include_directories(ngcore PUBLIC src) if(MSVC) target_compile_options(ngcore PRIVATE /W4 /permissive-) endif() # ---- GPU app (needs the Vulkan SDK: set VULKAN_SDK or -DVULKAN_SDK=...) ---- if(NOT VULKAN_SDK AND DEFINED ENV{VULKAN_SDK}) set(VULKAN_SDK $ENV{VULKAN_SDK}) endif() set(ENV{VULKAN_SDK} "${VULKAN_SDK}") find_package(Vulkan REQUIRED) find_program(GLSLC glslc HINTS ${VULKAN_SDK}/Bin REQUIRED) # Runtime Lua -> GLSL -> SPIR-V. Embed engine declarations, not generated Lua. file(READ ${CMAKE_SOURCE_DIR}/shaders/common.glsl NG_LUA_COMMON) string(REGEX MATCH "layout\\(push_constant\\)[^}]*} pc;" NG_LUA_PC "${NG_LUA_COMMON}") file(READ ${CMAKE_SOURCE_DIR}/shaders/fx_common.glsl NG_LUA_FX) string(REGEX MATCH "struct Ent [^{]*\\{[^}]*};" NG_LUA_ENT "${NG_LUA_FX}") file(READ ${CMAKE_SOURCE_DIR}/shaders/fx_state.glsl NG_LUA_STATE) string(REGEX MATCH "struct FProj [^{]*\\{[^}]*};" NG_LUA_PROJ "${NG_LUA_STATE}") file(READ ${CMAKE_SOURCE_DIR}/shaders/lua_native_runtime.glsl NG_LUA_RUNTIME) file(READ ${CMAKE_SOURCE_DIR}/shaders/lua_native_terrain.glsl NG_LUA_TERRAIN) file(READ ${CMAKE_SOURCE_DIR}/shaders/proj_lua_dispatch.glsl NG_LUA_DISPATCH) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS shaders/common.glsl shaders/fx_common.glsl shaders/fx_state.glsl shaders/lua_native_runtime.glsl shaders/lua_native_terrain.glsl shaders/proj_lua_dispatch.glsl) if(NOT NG_LUA_PC OR NOT NG_LUA_ENT OR NOT NG_LUA_PROJ) message(FATAL_ERROR "Cannot extract the engine ABI for runtime Lua shaders") endif() configure_file(src/lua/gpu_lua_sources.h.in generated/gpu_lua_sources.h @ONLY) find_path(SHADERC_INCLUDE_DIR shaderc/shaderc.h HINTS ${VULKAN_SDK}/Include REQUIRED) find_file(SHADERC_RUNTIME shaderc_shared.dll HINTS ${VULKAN_SDK}/Bin REQUIRED) add_library(nggpulua STATIC src/lua/gpu_lua.cpp src/lua/gpu_lua_proto.c) target_include_directories(nggpulua PUBLIC src PRIVATE ${CMAKE_BINARY_DIR}/generated ${SHADERC_INCLUDE_DIR}) target_link_libraries(nggpulua PUBLIC luajit_static) # ---- Blosc2 (world save format: byte-shuffled, LZ4/Zstd-compressed tiles); lz4/zstd sources are local checkouts ---- set(BUILD_SHARED OFF CACHE BOOL "" FORCE) set(BUILD_STATIC ON CACHE BOOL "" FORCE) set(BUILD_TESTS OFF CACHE BOOL "" FORCE) set(BUILD_FUZZERS OFF CACHE BOOL "" FORCE) set(BUILD_BENCHMARKS OFF CACHE BOOL "" FORCE) set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(BUILD_PLUGINS OFF CACHE BOOL "" FORCE) set(DEACTIVATE_ZLIB ON CACHE BOOL "" FORCE) set(BLOSC_ENABLE_ZFP OFF CACHE STRING "" FORCE) set(BLOSC_LZ4_SOURCE_DIR ${CMAKE_SOURCE_DIR}/third_party/lz4 CACHE PATH "" FORCE) set(BLOSC_ZSTD_SOURCE_DIR ${CMAKE_SOURCE_DIR}/third_party/zstd CACHE PATH "" FORCE) set(BLOSC_MERGE_BUNDLED_STATIC OFF CACHE BOOL "" FORCE) # (the in-place merge of lz4 / zstd into libblosc2 ran on every build: blosc/CMakeLists.txt) add_subdirectory(third_party/c-blosc2 EXCLUDE_FROM_ALL) add_library(ngblosc INTERFACE) target_link_libraries(ngblosc INTERFACE blosc2_static) target_include_directories(ngblosc INTERFACE ${CMAKE_SOURCE_DIR}/third_party/c-blosc2/include) add_executable(noita_gpu src/app/main.cpp src/audio/fmod_audio.cpp) target_link_libraries(noita_gpu PRIVATE ngspawn ngblosc nggpulua Vulkan::Vulkan) # the short git hash of the sources at configure time (the GIF metadata's "build", docs/GIF_METADATA.md) execute_process(COMMAND git rev-parse --short HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE NG_GIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) if(NOT NG_GIT_HASH) set(NG_GIT_HASH "dev") endif() target_compile_definitions(noita_gpu PRIVATE NG_BUILD="${NG_GIT_HASH}") if(MSVC) target_compile_options(noita_gpu PRIVATE /W4 /permissive- /bigobj) endif() set(SHADERS intent apply paint render terrain sprites entities projectiles edits react burn rigid_solve rigid_commit rigid_frame rigid_events proj_spawn proj_step fx_particles fx_anims ents_prev fx_hand displace rigid_raster explode_rays rigid_burn vines sky_cols glow fog particle_req light_cells clip_half) # shaders are rebuilt when their source or any included .glsl changes, then copied next to the executable file(GLOB SHADER_INCLUDES ${CMAKE_SOURCE_DIR}/shaders/*.glsl ${CMAKE_SOURCE_DIR}/shaders/generated/*.glsl) set(SPV_FILES "") # Offline reference artifacts only; the game uses native C++ startup translation. find_package(Python3 COMPONENTS Interpreter QUIET) if(Python3_Interpreter_FOUND) add_custom_target(lua_gpu_generate COMMAND ${Python3_EXECUTABLE} -m tools.lua_gpu.engine --data-root ${CMAKE_SOURCE_DIR}/.. --out-dir ${CMAKE_SOURCE_DIR}/shaders/generated --header ${CMAKE_SOURCE_DIR}/src/lua/gpu_program_generated.h WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Generating offline Lua GPU reference artifacts" VERBATIM) endif() foreach(s ${SHADERS}) add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/spv/${s}.spv COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/spv COMMAND ${GLSLC} --target-env=vulkan1.1 -O -I ${CMAKE_SOURCE_DIR}/shaders ${CMAKE_SOURCE_DIR}/shaders/${s}.comp -o ${CMAKE_BINARY_DIR}/spv/${s}.spv DEPENDS ${CMAKE_SOURCE_DIR}/shaders/${s}.comp ${SHADER_INCLUDES} COMMENT "glslc ${s}.comp") list(APPEND SPV_FILES ${CMAKE_BINARY_DIR}/spv/${s}.spv) add_custom_command(TARGET noita_gpu POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/spv/${s}.spv $/${s}.spv) endforeach() add_custom_target(shaders ALL DEPENDS ${SPV_FILES}) # building only the shaders (e.g. the `shaders` project in Visual Studio) must also refresh the demo folder and the exe folder foreach(s ${SHADERS}) add_custom_command(TARGET shaders POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/spv/${s}.spv ${CMAKE_SOURCE_DIR}/demo/${s}.spv COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/spv/${s}.spv ${CMAKE_BINARY_DIR}/$/${s}.spv) endforeach() # `cmake --install` / the INSTALL project: the demo folder gets the exe and the shaders install(TARGETS noita_gpu RUNTIME DESTINATION ${CMAKE_SOURCE_DIR}/demo) install(FILES ${SPV_FILES} DESTINATION ${CMAKE_SOURCE_DIR}/demo) # keep the demo folder (run_*.bat) in step with the build add_custom_command(TARGET noita_gpu POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${CMAKE_SOURCE_DIR}/demo/noita_gpu.exe) foreach(s ${SHADERS}) add_custom_command(TARGET noita_gpu POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/spv/${s}.spv ${CMAKE_SOURCE_DIR}/demo/${s}.spv) endforeach() add_dependencies(noita_gpu shaders) # noita_spelltest.exe: the same sources and engine, built to run only the spell test room (NG_SPELLTEST_ONLY in main.cpp); it uses the same shaders add_executable(noita_spelltest src/app/main.cpp src/audio/fmod_audio.cpp) target_link_libraries(noita_spelltest PRIVATE ngspawn ngblosc nggpulua Vulkan::Vulkan) target_compile_definitions(noita_spelltest PRIVATE NG_BUILD="${NG_GIT_HASH}" NG_SPELLTEST_ONLY=1) if(MSVC) target_compile_options(noita_spelltest PRIVATE /W4 /permissive- /bigobj) endif() add_dependencies(noita_spelltest shaders) foreach(app noita_gpu noita_spelltest) add_custom_command(TARGET ${app} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SHADERC_RUNTIME} $/shaderc_shared.dll COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SHADERC_RUNTIME} ${CMAKE_SOURCE_DIR}/demo/shaderc_shared.dll) endforeach() install(FILES ${SHADERC_RUNTIME} DESTINATION ${CMAKE_SOURCE_DIR}/demo) add_custom_command(TARGET noita_spelltest POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${CMAKE_SOURCE_DIR}/demo/noita_spelltest.exe) # pack_spelltest (not part of ALL): build/pack/noita_spelltest/ and build/pack/noita_spelltest_.zip, to run with the Steam Noita (packaging/spelltest/README.txt): # the exe, the shaders, audio_host.exe (the 32-bit sound helper, built here in build_audio32) and the C++ runtime DLLs; never data.wak or FMOD # cmake --build build --config Release --target pack_spelltest set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) set(CMAKE_INSTALL_UCRT_LIBRARIES FALSE) include(InstallRequiredSystemLibraries) # (only to find the redistributable DLLs: CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS) string(REPLACE ";" "|" NG_PACK_SHADERS "${SHADERS}") string(REPLACE ";" "|" NG_PACK_CRT "${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}") set(NG_AUDIO32_DIR ${CMAKE_SOURCE_DIR}/build_audio32) add_custom_target(pack_spelltest COMMAND ${CMAKE_COMMAND} -S ${CMAKE_SOURCE_DIR}/tools/audio_host -B ${NG_AUDIO32_DIR} -G "${CMAKE_GENERATOR}" -A Win32 COMMAND ${CMAKE_COMMAND} --build ${NG_AUDIO32_DIR} --config Release COMMAND ${CMAKE_COMMAND} -DEXE=$ -DSPV_DIR=${CMAKE_BINARY_DIR}/spv "-DSHADERS=${NG_PACK_SHADERS}" -DAUDIO=${NG_AUDIO32_DIR}/Release/audio_host.exe -DREADME=${CMAKE_SOURCE_DIR}/packaging/spelltest/README.txt "-DCRT=${NG_PACK_CRT}" -DSHADERC=${SHADERC_RUNTIME} -DOUT=${CMAKE_BINARY_DIR}/pack -DVERSION=${NG_GIT_HASH} -P ${CMAKE_SOURCE_DIR}/packaging/spelltest/pack.cmake COMMENT "packing the spell room" VERBATIM) add_dependencies(pack_spelltest noita_spelltest shaders) enable_testing() # ---- world generation (vendored noita_random, MIT) ---- add_library(ngworld STATIC src/world/wang_gen.cpp src/world/biomes.cpp src/world/petri.cpp src/world/noise.cpp src/world/biome_materials.cpp src/world/cave_bitmap.cpp src/world/proc_biome.cpp src/world/terrain.cpp src/world/reactions.cpp) target_link_libraries(ngworld PUBLIC ngcore) target_include_directories(ngworld PUBLIC src PRIVATE third_party/noita_random) if(MSVC) target_compile_options(ngworld PRIVATE /W0 /bigobj /permissive) endif() add_library(ngspawn STATIC src/ai/pathfind.cpp src/world/chunk_spawn.cpp src/world/area_setup.cpp src/world/area_store.cpp src/world/biome_modifiers.cpp src/entity/entity.cpp src/entity/runtime.cpp src/entity/runtime_natives.cpp src/entity/electricity.cpp src/entity/status.cpp src/gun/gun.cpp src/gun/projectiles.cpp src/gun/effects.cpp src/gun/gpu_fx.cpp src/physics/rigid.cpp src/physics/gpu_bodies.cpp) target_link_libraries(ngspawn PUBLIC ngworld nglua) # ---- Lua host (LuaJIT built from third_party/luajit with MSVC) ---- set(LUAJIT_DIR ${CMAKE_SOURCE_DIR}/third_party/luajit) if(NOT EXISTS ${LUAJIT_DIR}/src/lua51.lib) message(STATUS "Building LuaJIT (static, x64)...") execute_process(COMMAND cmd /c ${LUAJIT_DIR}/build_luajit.bat OUTPUT_QUIET ERROR_QUIET RESULT_VARIABLE LJ_RC) if(NOT LJ_RC EQUAL 0) message(FATAL_ERROR "LuaJIT build failed; run third_party/luajit/build_luajit.bat to see why") endif() endif() add_library(luajit_static STATIC IMPORTED) set_target_properties(luajit_static PROPERTIES IMPORTED_LOCATION ${LUAJIT_DIR}/src/lua51.lib INTERFACE_INCLUDE_DIRECTORIES ${LUAJIT_DIR}/src) add_library(nglua STATIC src/lua/lua_host.cpp src/lua/entity_natives.cpp src/lua/host_natives.cpp src/lua/mod_loader.cpp src/entity/herds.cpp) target_link_libraries(nglua PUBLIC ngcore luajit_static) if(MSVC) target_compile_options(nglua PRIVATE /W4 /permissive-) endif() # CPU rigid-body engine: the recovered Box2D of the original, used by the game through src/phys. set(NG_CPU_PHYS_SOURCES src/phys/entity_physics.cpp src/phys/rigid_dynamics.cpp src/phys/rigid_collision.cpp src/phys/convex_distance.cpp src/phys/rigid_world_toi.cpp src/phys/dynamic_tree.cpp src/phys/contact_solver.cpp src/phys/rigid_contact_world.cpp src/phys/rigid_world_step.cpp src/phys/joint_solver.cpp src/phys/shape_queries.cpp src/phys/recovered_units.cpp src/phys/world.cpp src/phys/rigid_fracture.cpp src/phys/terrain_outline.cpp src/phys/terrain_world.cpp src/phys/poly_decompose.cpp src/phys/phys_system.cpp) file(GLOB NG_TESS_SOURCES third_party/libtess2/Source/*.c) # libtess2 (SGI free software licence B), the tessellator of the original add_library(ngphys STATIC ${NG_CPU_PHYS_SOURCES} ${NG_TESS_SOURCES}) target_link_libraries(ngspawn PUBLIC ngphys) target_include_directories(ngphys PUBLIC cpu src third_party/libtess2/Include) target_include_directories(ngphys PRIVATE third_party/libtess2/Source)