diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..546dda3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,401 @@ +cmake_minimum_required(VERSION 3.24) + +project(window_renderer_test VERSION 0.1.0 LANGUAGES C CXX) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel) +endif() + +option(WR_FETCH_MISSING_DEPENDENCIES + "Download and build dependencies (SDL3, Vulkan, Slang) that are not installed" ON) +option(WR_COMPILE_SHADERS + "Compile the Slang shader during the build (requires slangc)" ON) + +include(FetchContent) + +# --------------------------------------------------------------------------- +# SDL3 +# --------------------------------------------------------------------------- +find_package(SDL3 3.2 CONFIG QUIET) + +if(NOT SDL3_FOUND) + if(NOT WR_FETCH_MISSING_DEPENDENCIES) + message(FATAL_ERROR + "SDL3 was not found. Install SDL3, or configure with " + "-DWR_FETCH_MISSING_DEPENDENCIES=ON.") + endif() + + message(STATUS "SDL3 not found; fetching and building it from source") + + # Build a static SDL3 so the result does not depend on a system SDL3 + # runtime being present when the application is run. + set(SDL_SHARED OFF CACHE BOOL "Build the SDL3 shared library") + set(SDL_STATIC ON CACHE BOOL "Build the SDL3 static library") + set(SDL_TEST_LIBRARY OFF CACHE BOOL "Build the SDL3 test library") + set(SDL_TESTS OFF CACHE BOOL "Build the SDL3 tests") + set(SDL_EXAMPLES OFF CACHE BOOL "Build the SDL3 examples") + set(SDL_INSTALL OFF CACHE BOOL "Enable SDL3 install rules") + + FetchContent_Declare(SDL3 + GIT_REPOSITORY https://github.com/libsdl-org/SDL.git + GIT_TAG release-3.4.18 + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(SDL3) +endif() + +# --------------------------------------------------------------------------- +# Vulkan headers +# --------------------------------------------------------------------------- +find_package(Vulkan QUIET) + +if(Vulkan_FOUND AND Vulkan_INCLUDE_DIRS) + set(WR_VULKAN_INCLUDE_DIRS ${Vulkan_INCLUDE_DIRS}) +else() + if(NOT WR_FETCH_MISSING_DEPENDENCIES) + message(FATAL_ERROR + "Vulkan headers were not found. Install the Vulkan SDK, or configure " + "with -DWR_FETCH_MISSING_DEPENDENCIES=ON.") + endif() + + message(STATUS "Vulkan headers not found; fetching Vulkan-Headers") + + FetchContent_Declare(vulkan_headers + GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Headers.git + GIT_TAG vulkan-sdk-1.4.350.0 + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(vulkan_headers) + + set(WR_VULKAN_INCLUDE_DIRS ${vulkan_headers_SOURCE_DIR}/include) +endif() + +# --------------------------------------------------------------------------- +# volk (Vulkan meta-loader) +# +# volk is not guaranteed to be shipped with Vulkan headers. The project +# includes , so a fetched volk checkout is placed in a generated +# include directory with the expected layout. +# --------------------------------------------------------------------------- +find_path(WR_VOLK_INCLUDE_DIR + NAMES volk/volk.h + HINTS ${WR_VULKAN_INCLUDE_DIRS} +) +find_file(WR_VOLK_SOURCE_FILE + NAMES volk.c + HINTS ${WR_VULKAN_INCLUDE_DIRS} + PATH_SUFFIXES volk +) + +if(NOT WR_VOLK_INCLUDE_DIR OR NOT WR_VOLK_SOURCE_FILE) + if(NOT WR_FETCH_MISSING_DEPENDENCIES) + message(FATAL_ERROR + "volk was not found in the Vulkan headers. Install volk, or configure " + "with -DWR_FETCH_MISSING_DEPENDENCIES=ON.") + endif() + + message(STATUS "volk not found; fetching it") + + FetchContent_Declare(volk + GIT_REPOSITORY https://github.com/zeux/volk.git + GIT_TAG vulkan-sdk-1.4.350.0 + GIT_SHALLOW TRUE + SOURCE_SUBDIR _disable_add_subdirectory + ) + FetchContent_MakeAvailable(volk) + + set(WR_VOLK_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated/include) + file(MAKE_DIRECTORY ${WR_VOLK_INCLUDE_DIR}/volk) + configure_file( + ${volk_SOURCE_DIR}/volk.h + ${WR_VOLK_INCLUDE_DIR}/volk/volk.h + COPYONLY + ) + set(WR_VOLK_SOURCE_FILE ${volk_SOURCE_DIR}/volk.c) +endif() + +# --------------------------------------------------------------------------- +# Vulkan Memory Allocator (VMA) +# +# VMA is not part of the Vulkan headers either. The project includes +# , so a fetched copy is placed in the same generated +# include directory. +# --------------------------------------------------------------------------- +find_path(WR_VMA_INCLUDE_DIR + NAMES vma/vk_mem_alloc.h + HINTS ${WR_VULKAN_INCLUDE_DIRS} +) + +if(NOT WR_VMA_INCLUDE_DIR) + if(NOT WR_FETCH_MISSING_DEPENDENCIES) + message(FATAL_ERROR + "Vulkan Memory Allocator (VMA) was not found. Install VMA, or configure " + "with -DWR_FETCH_MISSING_DEPENDENCIES=ON.") + endif() + + message(STATUS "Vulkan Memory Allocator not found; fetching it") + + FetchContent_Declare(vma + GIT_REPOSITORY https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git + GIT_TAG v3.5.0 + GIT_SHALLOW TRUE + SOURCE_SUBDIR _disable_add_subdirectory + ) + FetchContent_MakeAvailable(vma) + + set(WR_VMA_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated/include) + file(MAKE_DIRECTORY ${WR_VMA_INCLUDE_DIR}/vma) + configure_file( + ${vma_SOURCE_DIR}/include/vk_mem_alloc.h + ${WR_VMA_INCLUDE_DIR}/vma/vk_mem_alloc.h + COPYONLY + ) +endif() + +# --------------------------------------------------------------------------- +# Application +# --------------------------------------------------------------------------- +add_executable(main + src/main.c + src/wapp/wapp.c + src/config/config_src.c + src/window/window_src.c + src/gpu/gpu_c_src.c + src/gpu/gpu_cc_src.cpp + ${WR_VOLK_SOURCE_FILE} +) + +target_include_directories(main PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${WR_VULKAN_INCLUDE_DIRS} + ${WR_VOLK_INCLUDE_DIR} + ${WR_VMA_INCLUDE_DIR} +) + +target_compile_definitions(main PRIVATE + WP_DEBUG_ASSERT + CGLM_DEFINE_PRINTS + USE_SDL_WINDOW_BACKEND + USE_VULKAN_RENDER_BACKEND + VK_NO_PROTOTYPES +) + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(main PRIVATE + $<$:-Wno-nullability-completeness> + ) +endif() + +target_link_libraries(main PRIVATE + SDL3::SDL3 + ${CMAKE_DL_LIBS} +) + +if(UNIX AND NOT APPLE) + target_link_libraries(main PRIVATE m) +endif() + +# --------------------------------------------------------------------------- +# Shaders +# +# The application loads "shaders/shader.spv" relative to its working directory, +# so the SPIR-V output is placed in /shaders. +# --------------------------------------------------------------------------- +if(WR_COMPILE_SHADERS) + find_program(SLANGC_EXECUTABLE NAMES slangc) + + if(NOT SLANGC_EXECUTABLE AND WR_FETCH_MISSING_DEPENDENCIES) + # Download the prebuilt Slang release instead of requiring the user to + # install the compiler. Asset names and hashes come from the pinned + # GitHub release below. + set(WR_SLANG_VERSION "2026.20") + + string(TOLOWER "${CMAKE_HOST_SYSTEM_PROCESSOR}" WR_SLANG_ARCH) + if(WR_SLANG_ARCH MATCHES "^(x86_64|amd64|x64)$") + set(WR_SLANG_ARCH "x86_64") + elseif(WR_SLANG_ARCH MATCHES "^(aarch64|arm64|arm64e)$") + set(WR_SLANG_ARCH "aarch64") + else() + set(WR_SLANG_ARCH "") + endif() + + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") + set(WR_SLANG_OS "linux") + elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") + set(WR_SLANG_OS "macos") + elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + set(WR_SLANG_OS "windows") + else() + set(WR_SLANG_OS "") + endif() + + if(WR_SLANG_OS AND WR_SLANG_ARCH) + set(WR_SLANG_PLATFORM "${WR_SLANG_OS}-${WR_SLANG_ARCH}") + else() + set(WR_SLANG_PLATFORM "") + endif() + + if(WR_SLANG_PLATFORM STREQUAL "linux-x86_64") + set(WR_SLANG_ASSET + "slang-${WR_SLANG_VERSION}-linux-x86_64-glibc-2.28.tar.gz") + set(WR_SLANG_SHA256 + "84ebd777968caceea26b21bfd014341078be6047be6c5a963a69f984f896cebd") + elseif(WR_SLANG_PLATFORM STREQUAL "linux-aarch64") + set(WR_SLANG_ASSET + "slang-${WR_SLANG_VERSION}-linux-aarch64-glibc-2.28.tar.gz") + set(WR_SLANG_SHA256 + "9959d741713162efdbd863ff6d32400432b6e103eae3a9ee0afbdba6a00c6a9e") + elseif(WR_SLANG_PLATFORM STREQUAL "macos-x86_64") + set(WR_SLANG_ASSET + "slang-${WR_SLANG_VERSION}-macos-x86_64.tar.gz") + set(WR_SLANG_SHA256 + "b4f01f615dc625f60c593d7d0a4bbd38e05dfa2a34bfd3973017c309d866feaa") + elseif(WR_SLANG_PLATFORM STREQUAL "macos-aarch64") + set(WR_SLANG_ASSET + "slang-${WR_SLANG_VERSION}-macos-aarch64.tar.gz") + set(WR_SLANG_SHA256 + "07fa8833fdbb343c3674c84fdd885910f495f3daaaa0c732b32980151e8b58c4") + elseif(WR_SLANG_PLATFORM STREQUAL "windows-x86_64") + set(WR_SLANG_ASSET + "slang-${WR_SLANG_VERSION}-windows-x86_64.zip") + set(WR_SLANG_SHA256 + "e6dd32bbddf1720ebb4e679d55e9635ce792865a5bd84f3d2cd9af28647c0174") + elseif(WR_SLANG_PLATFORM STREQUAL "windows-aarch64") + set(WR_SLANG_ASSET + "slang-${WR_SLANG_VERSION}-windows-aarch64.zip") + set(WR_SLANG_SHA256 + "4f18a7e76a59969bc9bf6ea596bd9e496a3c689af173990ae028b18f03e1ac1a") + else() + set(WR_SLANG_ASSET "") + endif() + + if(NOT WR_SLANG_ASSET) + message(FATAL_ERROR + "slangc was not found, and no prebuilt Slang archive is available " + "for ${CMAKE_HOST_SYSTEM_NAME}/${CMAKE_HOST_SYSTEM_PROCESSOR}. " + "Install the Slang compiler, or configure with " + "-DWR_COMPILE_SHADERS=OFF.") + endif() + + set(WR_SLANG_DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/slang") + set(WR_SLANG_ARCHIVE + "${WR_SLANG_DOWNLOAD_DIR}/${WR_SLANG_ASSET}") + set(WR_SLANG_URL + "https://github.com/shader-slang/slang/releases/download/v${WR_SLANG_VERSION}/${WR_SLANG_ASSET}") + + if(EXISTS "${WR_SLANG_ARCHIVE}") + file(SHA256 "${WR_SLANG_ARCHIVE}" WR_SLANG_ACTUAL_SHA256) + if(NOT WR_SLANG_ACTUAL_SHA256 STREQUAL WR_SLANG_SHA256) + message(STATUS "Removing corrupt Slang archive") + file(REMOVE "${WR_SLANG_ARCHIVE}") + endif() + endif() + + if(NOT EXISTS "${WR_SLANG_ARCHIVE}") + message(STATUS "Downloading Slang ${WR_SLANG_VERSION} for ${WR_SLANG_PLATFORM}") + file(DOWNLOAD + "${WR_SLANG_URL}" + "${WR_SLANG_ARCHIVE}" + EXPECTED_HASH SHA256=${WR_SLANG_SHA256} + STATUS WR_SLANG_DOWNLOAD_STATUS + TLS_VERIFY ON + ) + list(GET WR_SLANG_DOWNLOAD_STATUS 0 WR_SLANG_DOWNLOAD_CODE) + list(GET WR_SLANG_DOWNLOAD_STATUS 1 WR_SLANG_DOWNLOAD_MESSAGE) + if(NOT WR_SLANG_DOWNLOAD_CODE EQUAL 0) + file(REMOVE "${WR_SLANG_ARCHIVE}") + message(FATAL_ERROR + "Failed to download Slang from ${WR_SLANG_URL}: " + "${WR_SLANG_DOWNLOAD_MESSAGE}") + endif() + endif() + + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + set(WR_SLANG_EXECUTABLE + "${WR_SLANG_DOWNLOAD_DIR}/bin/slangc.exe") + else() + set(WR_SLANG_EXECUTABLE + "${WR_SLANG_DOWNLOAD_DIR}/bin/slangc") + endif() + + if(NOT EXISTS "${WR_SLANG_EXECUTABLE}") + message(STATUS "Extracting Slang ${WR_SLANG_VERSION}") + file(ARCHIVE_EXTRACT + INPUT "${WR_SLANG_ARCHIVE}" + DESTINATION "${WR_SLANG_DOWNLOAD_DIR}" + ) + endif() + + if(NOT EXISTS "${WR_SLANG_EXECUTABLE}") + message(FATAL_ERROR + "The Slang archive did not contain bin/slangc") + endif() + + set(SLANGC_EXECUTABLE "${WR_SLANG_EXECUTABLE}") + message(STATUS "Using downloaded Slang compiler: ${SLANGC_EXECUTABLE}") + endif() + + if(NOT SLANGC_EXECUTABLE) + message(FATAL_ERROR + "slangc was not found. Install the Slang compiler, configure with " + "-DWR_FETCH_MISSING_DEPENDENCIES=ON to download it, or configure with " + "-DWR_COMPILE_SHADERS=OFF.") + endif() + + set(WR_SHADER_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/src/shaders/shader.slang) + set(WR_SHADER_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/shaders) + set(WR_SHADER_OUTPUT ${WR_SHADER_OUTPUT_DIR}/shader.spv) + + add_custom_command( + OUTPUT ${WR_SHADER_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E make_directory ${WR_SHADER_OUTPUT_DIR} + COMMAND ${SLANGC_EXECUTABLE} + ${WR_SHADER_SOURCE} + -entry vertMain + -entry fragMain + -o ${WR_SHADER_OUTPUT} + DEPENDS ${WR_SHADER_SOURCE} + COMMENT "Compiling shader.slang" + VERBATIM + ) + + add_custom_target(shaders ALL DEPENDS ${WR_SHADER_OUTPUT}) + add_dependencies(main shaders) +endif() + +# --------------------------------------------------------------------------- +# Assets +# +# The application loads "assets/collections.png" relative to its working +# directory, so the assets directory is copied into /assets. +# --------------------------------------------------------------------------- +set(WR_ASSETS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/assets) +set(WR_ASSETS_DEST_DIR ${CMAKE_CURRENT_BINARY_DIR}/assets) + +if(EXISTS ${WR_ASSETS_SOURCE_DIR}) + add_custom_target(copy_assets ALL + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${WR_ASSETS_SOURCE_DIR} + ${WR_ASSETS_DEST_DIR} + COMMENT "Copying assets" + VERBATIM + ) + add_dependencies(main copy_assets) +endif() + +add_custom_target(run + COMMAND $ + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS main + USES_TERMINAL +) + +if(TARGET shaders) + add_dependencies(run shaders) +endif()