diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..7652aae --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +FLAKEPATH="/home/alex/Developer/nix/NixOSDots/dev-flakes" + +use flake ${FLAKEPATH}/vulkan diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/CMake/FindKTX.cmake b/CMake/FindKTX.cmake new file mode 100644 index 0000000..ac6971a --- /dev/null +++ b/CMake/FindKTX.cmake @@ -0,0 +1,106 @@ +# FindKTX.cmake +# +# Finds the KTX library +# +# This will define the following variables +# +# KTX_FOUND +# KTX_INCLUDE_DIRS +# KTX_LIBRARIES +# +# and the following imported targets +# +# KTX::ktx +# + +# Check if we're on Linux - if so, we'll skip the search and directly use FetchContent +if(UNIX AND NOT APPLE) + # On Linux, we assume KTX is not installed and proceed directly to fetching it + set(KTX_FOUND FALSE) +else() + # On non-Linux platforms, try to find KTX using pkg-config first + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_KTX QUIET ktx libktx ktx2 libktx2) + endif() + + # Try to find KTX using standard find_package + find_path(KTX_INCLUDE_DIR + NAMES ktx.h + PATH_SUFFIXES include ktx KTX ktx2 KTX2 + HINTS + ${PC_KTX_INCLUDEDIR} + /usr/include + /usr/local/include + $ENV{KTX_DIR}/include + $ENV{VULKAN_SDK}/include + ${CMAKE_SOURCE_DIR}/external/ktx/include + ) + + find_library(KTX_LIBRARY + NAMES ktx ktx2 libktx libktx2 + PATH_SUFFIXES lib lib64 + HINTS + ${PC_KTX_LIBDIR} + /usr/lib + /usr/lib64 + /usr/local/lib + /usr/local/lib64 + $ENV{KTX_DIR}/lib + $ENV{VULKAN_SDK}/lib + ${CMAKE_SOURCE_DIR}/external/ktx/lib + ) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(KTX + REQUIRED_VARS KTX_INCLUDE_DIR KTX_LIBRARY + FAIL_MESSAGE "" # Suppress the error message to allow our fallback + ) + + # Debug output if KTX is not found (only on non-Linux platforms) + if(NOT KTX_FOUND) + message(STATUS "KTX include directory search paths: ${PC_KTX_INCLUDEDIR}, /usr/include, /usr/local/include, $ENV{KTX_DIR}/include, $ENV{VULKAN_SDK}/include, ${CMAKE_SOURCE_DIR}/external/ktx/include") + message(STATUS "KTX library search paths: ${PC_KTX_LIBDIR}, /usr/lib, /usr/lib64, /usr/local/lib, /usr/local/lib64, $ENV{KTX_DIR}/lib, $ENV{VULKAN_SDK}/lib, ${CMAKE_SOURCE_DIR}/external/ktx/lib") + endif() +endif() + +if(KTX_FOUND) + set(KTX_INCLUDE_DIRS ${KTX_INCLUDE_DIR}) + set(KTX_LIBRARIES ${KTX_LIBRARY}) + + if(NOT TARGET KTX::ktx) + add_library(KTX::ktx UNKNOWN IMPORTED) + set_target_properties(KTX::ktx PROPERTIES + IMPORTED_LOCATION "${KTX_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${KTX_INCLUDE_DIRS}" + ) + endif() +else() + # If not found, use FetchContent to download and build + include(FetchContent) + + # Only show the message on non-Linux platforms + if(NOT (UNIX AND NOT APPLE)) + message(STATUS "KTX not found, fetching from GitHub...") + endif() + + FetchContent_Declare( + ktx + GIT_REPOSITORY https://github.com/KhronosGroup/KTX-Software.git + GIT_TAG v4.3.1 # Use a specific tag for stability + ) + + # Set options to minimize build time and dependencies + set(KTX_FEATURE_TOOLS OFF CACHE BOOL "Build KTX tools" FORCE) + set(KTX_FEATURE_DOC OFF CACHE BOOL "Build KTX documentation" FORCE) + set(KTX_FEATURE_TESTS OFF CACHE BOOL "Build KTX tests" FORCE) + + FetchContent_MakeAvailable(ktx) + + # Create an alias to match the expected target name + if(NOT TARGET KTX::ktx) + add_library(KTX::ktx ALIAS ktx) + endif() + + set(KTX_FOUND TRUE) +endif() diff --git a/CMake/FindVulkan.cmake b/CMake/FindVulkan.cmake new file mode 100644 index 0000000..55ac153 --- /dev/null +++ b/CMake/FindVulkan.cmake @@ -0,0 +1,937 @@ +# Updates for iOS Copyright (c) 2024, Holochip Inc. +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FindVulkan +---------- + +.. versionadded:: 3.7 + +Find Vulkan, which is a low-overhead, cross-platform 3D graphics +and computing API. + +Optional COMPONENTS +^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.24 + +This module respects several optional COMPONENTS. +There are corresponding imported targets for each of these. + +``glslc`` + The SPIR-V compiler. + +``glslangValidator`` + The ``glslangValidator`` tool. + +``glslang`` + The SPIR-V generator library. + +``shaderc_combined`` + The static library for Vulkan shader compilation. + +``SPIRV-Tools`` + Tools to process SPIR-V modules. + +``MoltenVK`` + On macOS, an additional component ``MoltenVK`` is available. + +``dxc`` + .. versionadded:: 3.25 + + The DirectX Shader Compiler. + +The ``glslc`` and ``glslangValidator`` components are provided even +if not explicitly requested (for backward compatibility). + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +This module defines :prop_tgt:`IMPORTED` targets if Vulkan has been found: + +``Vulkan::Vulkan`` + The main Vulkan library. + +``Vulkan::glslc`` + .. versionadded:: 3.19 + + The GLSLC SPIR-V compiler, if it has been found. + +``Vulkan::Headers`` + .. versionadded:: 3.21 + + Provides just Vulkan headers include paths, if found. No library is + included in this target. This can be useful for applications that + load Vulkan library dynamically. + +``Vulkan::glslangValidator`` + .. versionadded:: 3.21 + + The glslangValidator tool, if found. It is used to compile GLSL and + HLSL shaders into SPIR-V. + +``Vulkan::glslang`` + .. versionadded:: 3.24 + + Defined if SDK has the Khronos-reference front-end shader parser and SPIR-V + generator library (glslang). + +``Vulkan::shaderc_combined`` + .. versionadded:: 3.24 + + Defined if SDK has the Google static library for Vulkan shader compilation + (shaderc_combined). + +``Vulkan::SPIRV-Tools`` + .. versionadded:: 3.24 + + Defined if SDK has the Khronos library to process SPIR-V modules + (SPIRV-Tools). + +``Vulkan::MoltenVK`` + .. versionadded:: 3.24 + + Defined if SDK has the Khronos library which implement a subset of Vulkan API + over Apple Metal graphics framework. (MoltenVK). + +``Vulkan::volk`` + .. versionadded:: 3.25 + + Defined if SDK has the Vulkan meta-loader (volk). + +``Vulkan::dxc_lib`` + .. versionadded:: 3.25 + + Defined if SDK has the DirectX shader compiler library. + +``Vulkan::dxc_exe`` + .. versionadded:: 3.25 + + Defined if SDK has the DirectX shader compiler CLI tool. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module defines the following variables: + +``Vulkan_FOUND`` + set to true if Vulkan was found +``Vulkan_INCLUDE_DIRS`` + include directories for Vulkan +``Vulkan_LIBRARIES`` + link against this library to use Vulkan +``Vulkan_VERSION`` + .. versionadded:: 3.23 + + value from ``vulkan/vulkan_core.h`` +``Vulkan_glslc_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the glslc executable. +``Vulkan_glslangValidator_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the glslangValidator executable. +``Vulkan_glslang_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the glslang library. +``Vulkan_shaderc_combined_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the shaderc_combined library. +``Vulkan_SPIRV-Tools_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the SPIRV-Tools library. +``Vulkan_MoltenVK_FOUND`` + .. versionadded:: 3.24 + + True, if the SDK has the MoltenVK library. +``Vulkan_volk_FOUND`` + .. versionadded:: 3.25 + + True, if the SDK has the volk library. + +``Vulkan_dxc_lib_FOUND`` + .. versionadded:: 3.25 + + True, if the SDK has the DirectX shader compiler library. + +``Vulkan_dxc_exe_FOUND`` + .. versionadded:: 3.25 + + True, if the SDK has the DirectX shader compiler CLI tool. + + +The module will also defines these cache variables: + +``Vulkan_INCLUDE_DIR`` + the Vulkan include directory +``Vulkan_LIBRARY`` + the path to the Vulkan library +``Vulkan_GLSLC_EXECUTABLE`` + the path to the GLSL SPIR-V compiler +``Vulkan_GLSLANG_VALIDATOR_EXECUTABLE`` + the path to the glslangValidator tool +``Vulkan_glslang_LIBRARY`` + .. versionadded:: 3.24 + + Path to the glslang library. +``Vulkan_shaderc_combined_LIBRARY`` + .. versionadded:: 3.24 + + Path to the shaderc_combined library. +``Vulkan_SPIRV-Tools_LIBRARY`` + .. versionadded:: 3.24 + + Path to the SPIRV-Tools library. +``Vulkan_MoltenVK_LIBRARY`` + .. versionadded:: 3.24 + + Path to the MoltenVK library. + +``Vulkan_volk_LIBRARY`` + .. versionadded:: 3.25 + + Path to the volk library. + +``Vulkan_dxc_LIBRARY`` + .. versionadded:: 3.25 + + Path to the DirectX shader compiler library. + +``Vulkan_dxc_EXECUTABLE`` + .. versionadded:: 3.25 + + Path to the DirectX shader compiler CLI tool. + +Hints +^^^^^ + +.. versionadded:: 3.18 + +The ``VULKAN_SDK`` environment variable optionally specifies the +location of the Vulkan SDK root directory for the given +architecture. It is typically set by sourcing the toplevel +``setup-env.sh`` script of the Vulkan SDK directory into the shell +environment. + +#]=======================================================================] + +cmake_policy(PUSH) +cmake_policy(SET CMP0057 NEW) + +# Provide compatibility with a common invalid component request that +# was silently ignored prior to CMake 3.24. +if("FATAL_ERROR" IN_LIST Vulkan_FIND_COMPONENTS) + message(AUTHOR_WARNING + "Ignoring unknown component 'FATAL_ERROR'.\n" + "The find_package() command documents no such argument." + ) + list(REMOVE_ITEM Vulkan_FIND_COMPONENTS "FATAL_ERROR") +endif() + +if(IOS) + get_filename_component(Vulkan_Target_SDK "$ENV{VULKAN_SDK}/.." REALPATH) + list(APPEND CMAKE_FRAMEWORK_PATH "${Vulkan_Target_SDK}/iOS/lib") + set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) + set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) + set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) +endif() + +# For backward compatibility as `FindVulkan` in previous CMake versions allow to retrieve `glslc` +# and `glslangValidator` without requesting the corresponding component. +if(NOT glslc IN_LIST Vulkan_FIND_COMPONENTS) + list(APPEND Vulkan_FIND_COMPONENTS glslc) +endif() +if(NOT glslangValidator IN_LIST Vulkan_FIND_COMPONENTS) + list(APPEND Vulkan_FIND_COMPONENTS glslangValidator) +endif() + +if(WIN32) + set(_Vulkan_library_name vulkan-1) + set(_Vulkan_hint_include_search_paths + "$ENV{VULKAN_SDK}/include" + ) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_Vulkan_hint_executable_search_paths + "$ENV{VULKAN_SDK}/bin" + ) + set(_Vulkan_hint_library_search_paths + "$ENV{VULKAN_SDK}/lib" + "$ENV{VULKAN_SDK}/bin" + ) + else() + set(_Vulkan_hint_executable_search_paths + "$ENV{VULKAN_SDK}/bin32" + "$ENV{VULKAN_SDK}/bin" + ) + set(_Vulkan_hint_library_search_paths + "$ENV{VULKAN_SDK}/lib32" + "$ENV{VULKAN_SDK}/bin32" + "$ENV{VULKAN_SDK}/lib" + "$ENV{VULKAN_SDK}/bin" + ) + endif() +else() + set(_Vulkan_library_name vulkan) + set(_Vulkan_hint_include_search_paths + "$ENV{VULKAN_SDK}/include" + ) + set(_Vulkan_hint_executable_search_paths + "$ENV{VULKAN_SDK}/bin" + ) + set(_Vulkan_hint_library_search_paths + "$ENV{VULKAN_SDK}/lib" + ) +endif() +if(APPLE AND DEFINED ENV{VULKAN_SDK}) + list(APPEND _Vulkan_hint_include_search_paths + "${Vulkan_Target_SDK}/macOS/include" + ) + if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + list(APPEND _Vulkan_hint_library_search_paths + "${Vulkan_Target_SDK}/iOS/lib" + ) + elseif(CMAKE_SYSTEM_NAME STREQUAL "tvOS") + list(APPEND _Vulkan_hint_library_search_paths + "${Vulkan_Target_SDK}/tvOS/lib" + ) + else() + list(APPEND _Vulkan_hint_library_search_paths + "${Vulkan_Target_SDK}}/lib" + ) + endif() +endif() + +find_path(Vulkan_INCLUDE_DIR + NAMES vulkan/vulkan.h + HINTS + ${_Vulkan_hint_include_search_paths} +) +message(STATUS "vulkan_include_dir ${Vulkan_INCLUDE_DIR} search paths ${_Vulkan_hint_include_search_paths}") +mark_as_advanced(Vulkan_INCLUDE_DIR) + +find_library(Vulkan_LIBRARY + NAMES ${_Vulkan_library_name} + HINTS + ${_Vulkan_hint_library_search_paths} +) +message(STATUS "vulkan_library ${Vulkan_LIBRARY} search paths ${_Vulkan_hint_library_search_paths}") +mark_as_advanced(Vulkan_LIBRARY) + +find_library(Vulkan_Layer_API_DUMP + NAMES VkLayer_api_dump + HINTS + ${_Vulkan_hint_library_search_paths} +) +mark_as_advanced(Vulkan_Layer_API_DUMP) + +find_library(Vulkan_Layer_SHADER_OBJECT + NAMES VkLayer_khronos_shader_object + HINTS + ${_Vulkan_hint_library_search_paths} +) +mark_as_advanced(VkLayer_khronos_shader_object) + +find_library(Vulkan_Layer_SYNC2 + NAMES VkLayer_khronos_synchronization2 + HINTS + ${_Vulkan_hint_library_search_paths} +) +mark_as_advanced(Vulkan_Layer_SYNC2) + +find_library(Vulkan_Layer_VALIDATION + NAMES VkLayer_khronos_validation + HINTS + ${_Vulkan_hint_library_search_paths} +) +mark_as_advanced(Vulkan_Layer_VALIDATION) + +if(glslc IN_LIST Vulkan_FIND_COMPONENTS) + find_program(Vulkan_GLSLC_EXECUTABLE + NAMES glslc + HINTS + ${_Vulkan_hint_executable_search_paths} + ) + mark_as_advanced(Vulkan_GLSLC_EXECUTABLE) +endif() +if(glslangValidator IN_LIST Vulkan_FIND_COMPONENTS) + find_program(Vulkan_GLSLANG_VALIDATOR_EXECUTABLE + NAMES glslangValidator + HINTS + ${_Vulkan_hint_executable_search_paths} + ) + mark_as_advanced(Vulkan_GLSLANG_VALIDATOR_EXECUTABLE) +endif() +if(glslang IN_LIST Vulkan_FIND_COMPONENTS) + find_library(Vulkan_glslang-spirv_LIBRARY + NAMES SPIRV + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-spirv_LIBRARY) + + find_library(Vulkan_glslang-spirv_DEBUG_LIBRARY + NAMES SPIRVd + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-spirv_DEBUG_LIBRARY) + + find_library(Vulkan_glslang-oglcompiler_LIBRARY + NAMES OGLCompiler + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-oglcompiler_LIBRARY) + + find_library(Vulkan_glslang-oglcompiler_DEBUG_LIBRARY + NAMES OGLCompilerd + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-oglcompiler_DEBUG_LIBRARY) + + find_library(Vulkan_glslang-osdependent_LIBRARY + NAMES OSDependent + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-osdependent_LIBRARY) + + find_library(Vulkan_glslang-osdependent_DEBUG_LIBRARY + NAMES OSDependentd + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-osdependent_DEBUG_LIBRARY) + + find_library(Vulkan_glslang-machineindependent_LIBRARY + NAMES MachineIndependent + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-machineindependent_LIBRARY) + + find_library(Vulkan_glslang-machineindependent_DEBUG_LIBRARY + NAMES MachineIndependentd + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-machineindependent_DEBUG_LIBRARY) + + find_library(Vulkan_glslang-genericcodegen_LIBRARY + NAMES GenericCodeGen + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-genericcodegen_LIBRARY) + + find_library(Vulkan_glslang-genericcodegen_DEBUG_LIBRARY + NAMES GenericCodeGend + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang-genericcodegen_DEBUG_LIBRARY) + + find_library(Vulkan_glslang_LIBRARY + NAMES glslang + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang_LIBRARY) + + find_library(Vulkan_glslang_DEBUG_LIBRARY + NAMES glslangd + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_glslang_DEBUG_LIBRARY) +endif() +if(shaderc_combined IN_LIST Vulkan_FIND_COMPONENTS) + find_library(Vulkan_shaderc_combined_LIBRARY + NAMES shaderc_combined + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_shaderc_combined_LIBRARY) + + find_library(Vulkan_shaderc_combined_DEBUG_LIBRARY + NAMES shaderc_combinedd + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_shaderc_combined_DEBUG_LIBRARY) +endif() +if(SPIRV-Tools IN_LIST Vulkan_FIND_COMPONENTS) + find_library(Vulkan_SPIRV-Tools_LIBRARY + NAMES SPIRV-Tools + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_SPIRV-Tools_LIBRARY) + + find_library(Vulkan_SPIRV-Tools_DEBUG_LIBRARY + NAMES SPIRV-Toolsd + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_SPIRV-Tools_DEBUG_LIBRARY) +endif() +if(MoltenVK IN_LIST Vulkan_FIND_COMPONENTS) + # CMake has a bug in 3.28 that doesn't handle xcframeworks. Do it by hand for now. + if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + if(CMAKE_VERSION VERSION_LESS 3.29) + set( _Vulkan_hint_library_search_paths ${Vulkan_Target_SDK}/ios/lib/MoltenVK.xcframework/ios-arm64) + else () + set( _Vulkan_hint_library_search_paths ${Vulkan_Target_SDK}/ios/lib/) + endif () + endif () + find_library(Vulkan_MoltenVK_LIBRARY + NAMES MoltenVK + HINTS + ${_Vulkan_hint_library_search_paths} + ) + mark_as_advanced(Vulkan_MoltenVK_LIBRARY) + + find_path(Vulkan_MoltenVK_INCLUDE_DIR + NAMES MoltenVK/mvk_vulkan.h + HINTS + ${_Vulkan_hint_include_search_paths} + ) + mark_as_advanced(Vulkan_MoltenVK_INCLUDE_DIR) +endif() +if(volk IN_LIST Vulkan_FIND_COMPONENTS) + find_library(Vulkan_volk_LIBRARY + NAMES volk + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_Volk_LIBRARY) +endif() + +if (dxc IN_LIST Vulkan_FIND_COMPONENTS) + find_library(Vulkan_dxc_LIBRARY + NAMES dxcompiler + HINTS + ${_Vulkan_hint_library_search_paths}) + mark_as_advanced(Vulkan_dxc_LIBRARY) + + find_program(Vulkan_dxc_EXECUTABLE + NAMES dxc + HINTS + ${_Vulkan_hint_executable_search_paths}) + mark_as_advanced(Vulkan_dxc_EXECUTABLE) +endif() + +if(Vulkan_GLSLC_EXECUTABLE) + set(Vulkan_glslc_FOUND TRUE) +else() + set(Vulkan_glslc_FOUND FALSE) +endif() + +if(Vulkan_GLSLANG_VALIDATOR_EXECUTABLE) + set(Vulkan_glslangValidator_FOUND TRUE) +else() + set(Vulkan_glslangValidator_FOUND FALSE) +endif() + +if (Vulkan_dxc_EXECUTABLE) + set(Vulkan_dxc_exe_FOUND TRUE) +else() + set(Vulkan_dxc_exe_FOUND FALSE) +endif() + +function(_Vulkan_set_library_component_found component) + cmake_parse_arguments(PARSE_ARGV 1 _ARG + "NO_WARNING" + "" + "DEPENDENT_COMPONENTS") + + set(all_dependent_component_found TRUE) + foreach(dependent_component IN LISTS _ARG_DEPENDENT_COMPONENTS) + if(NOT Vulkan_${dependent_component}_FOUND) + set(all_dependent_component_found FALSE) + break() + endif() + endforeach() + + if(all_dependent_component_found AND (Vulkan_${component}_LIBRARY OR Vulkan_${component}_DEBUG_LIBRARY)) + set(Vulkan_${component}_FOUND TRUE PARENT_SCOPE) + + # For Windows Vulkan SDK, third party tools binaries are provided with different MSVC ABI: + # - Release binaries uses a runtime library + # - Debug binaries uses a debug runtime library + # This lead to incompatibilities in linking for some configuration types due to CMake-default or project-configured selected MSVC ABI. + if(WIN32 AND NOT _ARG_NO_WARNING) + if(NOT Vulkan_${component}_LIBRARY) + message(WARNING + "Library ${component} for Release configuration is missing, imported target Vulkan::${component} may not be able to link when targeting this build configuration due to incompatible MSVC ABI.") + endif() + if(NOT Vulkan_${component}_DEBUG_LIBRARY) + message(WARNING + "Library ${component} for Debug configuration is missing, imported target Vulkan::${component} may not be able to link when targeting this build configuration due to incompatible MSVC ABI. Consider re-installing the Vulkan SDK and request debug libraries to fix this warning.") + endif() + endif() + else() + set(Vulkan_${component}_FOUND FALSE PARENT_SCOPE) + endif() +endfunction() + +_Vulkan_set_library_component_found(glslang-spirv NO_WARNING) +_Vulkan_set_library_component_found(glslang-oglcompiler NO_WARNING) +_Vulkan_set_library_component_found(glslang-osdependent NO_WARNING) +_Vulkan_set_library_component_found(glslang-machineindependent NO_WARNING) +_Vulkan_set_library_component_found(glslang-genericcodegen NO_WARNING) +_Vulkan_set_library_component_found(glslang + DEPENDENT_COMPONENTS + glslang-spirv + glslang-oglcompiler + glslang-osdependent + glslang-machineindependent + glslang-genericcodegen) +_Vulkan_set_library_component_found(shaderc_combined) +_Vulkan_set_library_component_found(SPIRV-Tools) +_Vulkan_set_library_component_found(volk) +_Vulkan_set_library_component_found(dxc) + +if(Vulkan_MoltenVK_INCLUDE_DIR AND Vulkan_MoltenVK_LIBRARY) + set(Vulkan_MoltenVK_FOUND TRUE) +else() + set(Vulkan_MoltenVK_FOUND FALSE) +endif() + +set(Vulkan_LIBRARIES ${Vulkan_LIBRARY}) +set(Vulkan_INCLUDE_DIRS ${Vulkan_INCLUDE_DIR}) + +# detect version e.g 1.2.189 +set(Vulkan_VERSION "") +if(Vulkan_INCLUDE_DIR) + set(VULKAN_CORE_H ${Vulkan_INCLUDE_DIR}/vulkan/vulkan_core.h) + if(EXISTS ${VULKAN_CORE_H}) + file(STRINGS ${VULKAN_CORE_H} VulkanHeaderVersionLine REGEX "^#define VK_HEADER_VERSION ") + string(REGEX MATCHALL "[0-9]+" VulkanHeaderVersion "${VulkanHeaderVersionLine}") + file(STRINGS ${VULKAN_CORE_H} VulkanHeaderVersionLine2 REGEX "^#define VK_HEADER_VERSION_COMPLETE ") + string(REGEX MATCHALL "[0-9]+" VulkanHeaderVersion2 "${VulkanHeaderVersionLine2}") + list(LENGTH VulkanHeaderVersion2 _len) + # versions >= 1.2.175 have an additional numbers in front of e.g. '0, 1, 2' instead of '1, 2' + if(_len EQUAL 3) + list(REMOVE_AT VulkanHeaderVersion2 0) + endif() + list(APPEND VulkanHeaderVersion2 ${VulkanHeaderVersion}) + list(JOIN VulkanHeaderVersion2 "." Vulkan_VERSION) + endif() +endif() + +if(Vulkan_MoltenVK_FOUND) + set(Vulkan_MoltenVK_VERSION "") + if(Vulkan_MoltenVK_INCLUDE_DIR) + set(VK_MVK_MOLTENVK_H ${Vulkan_MoltenVK_INCLUDE_DIR}/MoltenVK/vk_mvk_moltenvk.h) + if(EXISTS ${VK_MVK_MOLTENVK_H}) + file(STRINGS ${VK_MVK_MOLTENVK_H} _Vulkan_MoltenVK_VERSION_MAJOR REGEX "^#define MVK_VERSION_MAJOR ") + string(REGEX MATCHALL "[0-9]+" _Vulkan_MoltenVK_VERSION_MAJOR "${_Vulkan_MoltenVK_VERSION_MAJOR}") + file(STRINGS ${VK_MVK_MOLTENVK_H} _Vulkan_MoltenVK_VERSION_MINOR REGEX "^#define MVK_VERSION_MINOR ") + string(REGEX MATCHALL "[0-9]+" _Vulkan_MoltenVK_VERSION_MINOR "${_Vulkan_MoltenVK_VERSION_MINOR}") + file(STRINGS ${VK_MVK_MOLTENVK_H} _Vulkan_MoltenVK_VERSION_PATCH REGEX "^#define MVK_VERSION_PATCH ") + string(REGEX MATCHALL "[0-9]+" _Vulkan_MoltenVK_VERSION_PATCH "${_Vulkan_MoltenVK_VERSION_PATCH}") + set(Vulkan_MoltenVK_VERSION "${_Vulkan_MoltenVK_VERSION_MAJOR}.${_Vulkan_MoltenVK_VERSION_MINOR}.${_Vulkan_MoltenVK_VERSION_PATCH}") + unset(_Vulkan_MoltenVK_VERSION_MAJOR) + unset(_Vulkan_MoltenVK_VERSION_MINOR) + unset(_Vulkan_MoltenVK_VERSION_PATCH) + endif() + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Vulkan + REQUIRED_VARS + Vulkan_LIBRARY + Vulkan_INCLUDE_DIR + VERSION_VAR + Vulkan_VERSION + HANDLE_COMPONENTS +) + +if(Vulkan_FOUND AND NOT TARGET Vulkan::Vulkan) + add_library(Vulkan::Vulkan UNKNOWN IMPORTED) + set_target_properties(Vulkan::Vulkan PROPERTIES + IMPORTED_LOCATION "${Vulkan_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") +endif() + +if(Vulkan_FOUND AND NOT TARGET Vulkan::Headers) + add_library(Vulkan::Headers INTERFACE IMPORTED) + set_target_properties(Vulkan::Headers PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") +endif() + +if(Vulkan_FOUND AND Vulkan_GLSLC_EXECUTABLE AND NOT TARGET Vulkan::glslc) + add_executable(Vulkan::glslc IMPORTED) + set_property(TARGET Vulkan::glslc PROPERTY IMPORTED_LOCATION "${Vulkan_GLSLC_EXECUTABLE}") +endif() + +if(Vulkan_FOUND AND Vulkan_GLSLANG_VALIDATOR_EXECUTABLE AND NOT TARGET Vulkan::glslangValidator) + add_executable(Vulkan::glslangValidator IMPORTED) + set_property(TARGET Vulkan::glslangValidator PROPERTY IMPORTED_LOCATION "${Vulkan_GLSLANG_VALIDATOR_EXECUTABLE}") +endif() + +if(Vulkan_FOUND) + if((Vulkan_glslang-spirv_LIBRARY OR Vulkan_glslang-spirv_DEBUG_LIBRARY) AND NOT TARGET Vulkan::glslang-spirv) + add_library(Vulkan::glslang-spirv STATIC IMPORTED) + set_property(TARGET Vulkan::glslang-spirv + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang-spirv_LIBRARY) + set_property(TARGET Vulkan::glslang-spirv APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang-spirv + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang-spirv_LIBRARY}") + endif() + if(Vulkan_glslang-spirv_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang-spirv APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang-spirv + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang-spirv_DEBUG_LIBRARY}") + endif() + endif() + + if((Vulkan_glslang-oglcompiler_LIBRARY OR Vulkan_glslang-oglcompiler_DEBUG_LIBRARY) AND NOT TARGET Vulkan::glslang-oglcompiler) + add_library(Vulkan::glslang-oglcompiler STATIC IMPORTED) + set_property(TARGET Vulkan::glslang-oglcompiler + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang-oglcompiler_LIBRARY) + set_property(TARGET Vulkan::glslang-oglcompiler APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang-oglcompiler + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang-oglcompiler_LIBRARY}") + endif() + if(Vulkan_glslang-oglcompiler_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang-oglcompiler APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang-oglcompiler + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang-oglcompiler_DEBUG_LIBRARY}") + endif() + endif() + + if((Vulkan_glslang-osdependent_LIBRARY OR Vulkan_glslang-osdependent_DEBUG_LIBRARY) AND NOT TARGET Vulkan::glslang-osdependent) + add_library(Vulkan::glslang-osdependent STATIC IMPORTED) + set_property(TARGET Vulkan::glslang-osdependent + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang-osdependent_LIBRARY) + set_property(TARGET Vulkan::glslang-osdependent APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang-osdependent + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang-osdependent_LIBRARY}") + endif() + if(Vulkan_glslang-osdependent_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang-osdependent APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang-osdependent + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang-osdependent_DEBUG_LIBRARY}") + endif() + endif() + + if((Vulkan_glslang-machineindependent_LIBRARY OR Vulkan_glslang-machineindependent_DEBUG_LIBRARY) AND NOT TARGET Vulkan::glslang-machineindependent) + add_library(Vulkan::glslang-machineindependent STATIC IMPORTED) + set_property(TARGET Vulkan::glslang-machineindependent + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang-machineindependent_LIBRARY) + set_property(TARGET Vulkan::glslang-machineindependent APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang-machineindependent + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang-machineindependent_LIBRARY}") + endif() + if(Vulkan_glslang-machineindependent_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang-machineindependent APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang-machineindependent + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang-machineindependent_DEBUG_LIBRARY}") + endif() + endif() + + if((Vulkan_glslang-genericcodegen_LIBRARY OR Vulkan_glslang-genericcodegen_DEBUG_LIBRARY) AND NOT TARGET Vulkan::glslang-genericcodegen) + add_library(Vulkan::glslang-genericcodegen STATIC IMPORTED) + set_property(TARGET Vulkan::glslang-genericcodegen + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang-genericcodegen_LIBRARY) + set_property(TARGET Vulkan::glslang-genericcodegen APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang-genericcodegen + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang-genericcodegen_LIBRARY}") + endif() + if(Vulkan_glslang-genericcodegen_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang-genericcodegen APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang-genericcodegen + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang-genericcodegen_DEBUG_LIBRARY}") + endif() + endif() + + if((Vulkan_glslang_LIBRARY OR Vulkan_glslang_DEBUG_LIBRARY) + AND TARGET Vulkan::glslang-spirv + AND TARGET Vulkan::glslang-oglcompiler + AND TARGET Vulkan::glslang-osdependent + AND TARGET Vulkan::glslang-machineindependent + AND TARGET Vulkan::glslang-genericcodegen + AND NOT TARGET Vulkan::glslang) + add_library(Vulkan::glslang STATIC IMPORTED) + set_property(TARGET Vulkan::glslang + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_glslang_LIBRARY) + set_property(TARGET Vulkan::glslang APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::glslang + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_glslang_LIBRARY}") + endif() + if(Vulkan_glslang_DEBUG_LIBRARY) + set_property(TARGET Vulkan::glslang APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::glslang + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_glslang_DEBUG_LIBRARY}") + endif() + target_link_libraries(Vulkan::glslang + INTERFACE + Vulkan::glslang-spirv + Vulkan::glslang-oglcompiler + Vulkan::glslang-osdependent + Vulkan::glslang-machineindependent + Vulkan::glslang-genericcodegen + ) + endif() + + if((Vulkan_shaderc_combined_LIBRARY OR Vulkan_shaderc_combined_DEBUG_LIBRARY) AND NOT TARGET Vulkan::shaderc_combined) + add_library(Vulkan::shaderc_combined STATIC IMPORTED) + set_property(TARGET Vulkan::shaderc_combined + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_shaderc_combined_LIBRARY) + set_property(TARGET Vulkan::shaderc_combined APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::shaderc_combined + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_shaderc_combined_LIBRARY}") + endif() + if(Vulkan_shaderc_combined_DEBUG_LIBRARY) + set_property(TARGET Vulkan::shaderc_combined APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::shaderc_combined + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_shaderc_combined_DEBUG_LIBRARY}") + endif() + + if(UNIX) + find_package(Threads REQUIRED) + target_link_libraries(Vulkan::shaderc_combined + INTERFACE + Threads::Threads) + endif() + endif() + + if((Vulkan_SPIRV-Tools_LIBRARY OR Vulkan_SPIRV-Tools_DEBUG_LIBRARY) AND NOT TARGET Vulkan::SPIRV-Tools) + add_library(Vulkan::SPIRV-Tools STATIC IMPORTED) + set_property(TARGET Vulkan::SPIRV-Tools + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + if(Vulkan_SPIRV-Tools_LIBRARY) + set_property(TARGET Vulkan::SPIRV-Tools APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::SPIRV-Tools + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_SPIRV-Tools_LIBRARY}") + endif() + if(Vulkan_SPIRV-Tools_DEBUG_LIBRARY) + set_property(TARGET Vulkan::SPIRV-Tools APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Debug) + set_property(TARGET Vulkan::SPIRV-Tools + PROPERTY + IMPORTED_LOCATION_DEBUG "${Vulkan_SPIRV-Tools_DEBUG_LIBRARY}") + endif() + endif() + + if(Vulkan_volk_LIBRARY AND NOT TARGET Vulkan::volk) + add_library(Vulkan::volk STATIC IMPORTED) + set_property(TARGET Vulkan::volk + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + set_property(TARGET Vulkan::volk APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::volk APPEND + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_volk_LIBRARY}") + + if (NOT WIN32) + set_property(TARGET Vulkan::volk APPEND + PROPERTY + IMPORTED_LINK_INTERFACE_LIBRARIES dl) + endif() + endif() + + if (Vulkan_dxc_LIBRARY AND NOT TARGET Vulkan::dxc_lib) + add_library(Vulkan::dxc_lib STATIC IMPORTED) + set_property(TARGET Vulkan::dxc_lib + PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_INCLUDE_DIRS}") + set_property(TARGET Vulkan::dxc_lib APPEND + PROPERTY + IMPORTED_CONFIGURATIONS Release) + set_property(TARGET Vulkan::dxc_lib APPEND + PROPERTY + IMPORTED_LOCATION_RELEASE "${Vulkan_dxc_LIBRARY}") + endif() + + if(Vulkan_dxc_EXECUTABLE AND NOT TARGET Vulkan::dxc_exe) + add_executable(Vulkan::dxc_exe IMPORTED) + set_property(TARGET Vulkan::dxc_exe PROPERTY IMPORTED_LOCATION "${Vulkan_dxc_EXECUTABLE}") + endif() + +endif() + +if(Vulkan_MoltenVK_FOUND) + if(Vulkan_MoltenVK_LIBRARY AND NOT TARGET Vulkan::MoltenVK) + add_library(Vulkan::MoltenVK SHARED IMPORTED) + set_target_properties(Vulkan::MoltenVK + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Vulkan_MoltenVK_INCLUDE_DIR}" + IMPORTED_LOCATION "${Vulkan_MoltenVK_LIBRARY}" + ) + endif() +endif() + +unset(_Vulkan_library_name) +unset(_Vulkan_hint_include_search_paths) +unset(_Vulkan_hint_executable_search_paths) +unset(_Vulkan_hint_library_search_paths) + +cmake_policy(POP) \ No newline at end of file diff --git a/CMake/FindVulkanHpp.cmake b/CMake/FindVulkanHpp.cmake new file mode 100644 index 0000000..2c0e23a --- /dev/null +++ b/CMake/FindVulkanHpp.cmake @@ -0,0 +1,426 @@ +# FindVulkanHpp.cmake +# +# Finds or downloads the Vulkan-Hpp headers and Vulkan Profiles headers +# +# This will define the following variables +# +# VulkanHpp_FOUND +# VulkanHpp_INCLUDE_DIRS +# +# and the following imported targets +# +# VulkanHpp::VulkanHpp +# + +# Try to find the package using standard find_path +find_path(VulkanHpp_INCLUDE_DIR + NAMES vulkan/vulkan.hpp + PATHS + ${Vulkan_INCLUDE_DIR} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include +) + +# Also try to find vulkan.cppm +find_path(VulkanHpp_CPPM_DIR + NAMES vulkan/vulkan.cppm + PATHS + ${Vulkan_INCLUDE_DIR} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include +) + +# Try to find vulkan_profiles.hpp +find_path(VulkanProfiles_INCLUDE_DIR + NAMES vulkan/vulkan_profiles.hpp + PATHS + ${Vulkan_INCLUDE_DIR} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include +) + +# Function to extract Vulkan version from vulkan_core.h +function(extract_vulkan_version VULKAN_CORE_H_PATH OUTPUT_VERSION_TAG) + # Extract the version information from vulkan_core.h + file(STRINGS ${VULKAN_CORE_H_PATH} VULKAN_VERSION_MAJOR_LINE REGEX "^#define VK_VERSION_MAJOR") + file(STRINGS ${VULKAN_CORE_H_PATH} VULKAN_VERSION_MINOR_LINE REGEX "^#define VK_VERSION_MINOR") + file(STRINGS ${VULKAN_CORE_H_PATH} VULKAN_HEADER_VERSION_LINE REGEX "^#define VK_HEADER_VERSION") + + set(VERSION_TAG "v1.3.275") # Default fallback + + if(VULKAN_VERSION_MAJOR_LINE AND VULKAN_VERSION_MINOR_LINE AND VULKAN_HEADER_VERSION_LINE) + string(REGEX REPLACE "^#define VK_VERSION_MAJOR[ \t]+([0-9]+).*$" "\\1" VULKAN_VERSION_MAJOR "${VULKAN_VERSION_MAJOR_LINE}") + string(REGEX REPLACE "^#define VK_VERSION_MINOR[ \t]+([0-9]+).*$" "\\1" VULKAN_VERSION_MINOR "${VULKAN_VERSION_MINOR_LINE}") + string(REGEX REPLACE "^#define VK_HEADER_VERSION[ \t]+([0-9]+).*$" "\\1" VULKAN_HEADER_VERSION "${VULKAN_HEADER_VERSION_LINE}") + + # Construct the version tag + set(VERSION_TAG "v${VULKAN_VERSION_MAJOR}.${VULKAN_VERSION_MINOR}.${VULKAN_HEADER_VERSION}") + else() + # Alternative approach: look for VK_HEADER_VERSION_COMPLETE + file(STRINGS ${VULKAN_CORE_H_PATH} VULKAN_HEADER_VERSION_COMPLETE_LINE REGEX "^#define VK_HEADER_VERSION_COMPLETE") + file(STRINGS ${VULKAN_CORE_H_PATH} VULKAN_HEADER_VERSION_LINE REGEX "^#define VK_HEADER_VERSION") + + if(VULKAN_HEADER_VERSION_COMPLETE_LINE AND VULKAN_HEADER_VERSION_LINE) + # Extract the header version + string(REGEX REPLACE "^#define VK_HEADER_VERSION[ \t]+([0-9]+).*$" "\\1" VULKAN_HEADER_VERSION "${VULKAN_HEADER_VERSION_LINE}") + + # Check if the complete version line contains the major and minor versions + if(VULKAN_HEADER_VERSION_COMPLETE_LINE MATCHES "VK_MAKE_API_VERSION\\(.*,[ \t]*([0-9]+),[ \t]*([0-9]+),[ \t]*VK_HEADER_VERSION\\)") + set(VULKAN_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(VULKAN_VERSION_MINOR "${CMAKE_MATCH_2}") + set(VERSION_TAG "v${VULKAN_VERSION_MAJOR}.${VULKAN_VERSION_MINOR}.${VULKAN_HEADER_VERSION}") + endif() + endif() + endif() + + # Return the version tag + set(${OUTPUT_VERSION_TAG} ${VERSION_TAG} PARENT_SCOPE) +endfunction() + +# Determine the Vulkan version to use for Vulkan-Hpp and Vulkan-Profiles +set(VULKAN_VERSION_TAG "v1.3.275") # Default version + +# Try to detect the Vulkan version +set(VULKAN_CORE_H "") + +# If we're building for Android, try to detect the NDK's Vulkan version +if(DEFINED ANDROID_NDK) + # Find the vulkan_core.h file in the NDK + find_file(VULKAN_CORE_H vulkan_core.h + PATHS + ${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/vulkan + ${ANDROID_NDK}/toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/include/vulkan + ${ANDROID_NDK}/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/include/vulkan + ${ANDROID_NDK}/toolchains/llvm/prebuilt/windows/sysroot/usr/include/vulkan + NO_DEFAULT_PATH + ) + + if(VULKAN_CORE_H) + extract_vulkan_version(${VULKAN_CORE_H} VULKAN_VERSION_TAG) + message(STATUS "Detected NDK Vulkan version: ${VULKAN_VERSION_TAG}") + else() + message(STATUS "Could not find vulkan_core.h in NDK, using default version: ${VULKAN_VERSION_TAG}") + endif() +# For desktop builds, try to detect the Vulkan SDK version +elseif(DEFINED ENV{VULKAN_SDK}) + # Find the vulkan_core.h file in the Vulkan SDK + find_file(VULKAN_CORE_H vulkan_core.h + PATHS + $ENV{VULKAN_SDK}/include/vulkan + NO_DEFAULT_PATH + ) + + if(VULKAN_CORE_H) + extract_vulkan_version(${VULKAN_CORE_H} VULKAN_VERSION_TAG) + message(STATUS "Detected Vulkan SDK version: ${VULKAN_VERSION_TAG}") + else() + message(STATUS "Could not find vulkan_core.h in Vulkan SDK, using default version: ${VULKAN_VERSION_TAG}") + endif() +# If Vulkan package was already found, try to use its include directory +elseif(DEFINED Vulkan_INCLUDE_DIR) + # Find the vulkan_core.h file in the Vulkan include directory + find_file(VULKAN_CORE_H vulkan_core.h + PATHS + ${Vulkan_INCLUDE_DIR}/vulkan + NO_DEFAULT_PATH + ) + + if(VULKAN_CORE_H) + extract_vulkan_version(${VULKAN_CORE_H} VULKAN_VERSION_TAG) + message(STATUS "Detected Vulkan version from include directory: ${VULKAN_VERSION_TAG}") + else() + message(STATUS "Could not find vulkan_core.h in Vulkan include directory, using default version: ${VULKAN_VERSION_TAG}") + endif() +else() + # Try to find vulkan_core.h in system paths + find_file(VULKAN_CORE_H vulkan_core.h + PATHS + /usr/include/vulkan + /usr/local/include/vulkan + ) + + if(VULKAN_CORE_H) + extract_vulkan_version(${VULKAN_CORE_H} VULKAN_VERSION_TAG) + message(STATUS "Detected system Vulkan version: ${VULKAN_VERSION_TAG}") + else() + message(STATUS "Could not find vulkan_core.h in system paths, using default version: ${VULKAN_VERSION_TAG}") + endif() +endif() + +# If the include directory wasn't found, use FetchContent to download and build +if(NOT VulkanHpp_INCLUDE_DIR OR NOT VulkanHpp_CPPM_DIR) + # If not found, use FetchContent to download + include(FetchContent) + + message(STATUS "Vulkan-Hpp not found, fetching from GitHub with version ${VULKAN_VERSION_TAG}...") + FetchContent_Declare( + VulkanHpp + GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Hpp.git + GIT_TAG ${VULKAN_VERSION_TAG} # Use the detected or default version + ) + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Make sure FetchContent is available + include(FetchContent) + + # Populate the content + FetchContent_GetProperties(VulkanHpp SOURCE_DIR VulkanHpp_SOURCE_DIR) + if(NOT VulkanHpp_POPULATED) + FetchContent_Populate(VulkanHpp) + # Get the source directory after populating + FetchContent_GetProperties(VulkanHpp SOURCE_DIR VulkanHpp_SOURCE_DIR) + endif() + + # Set the include directory to the source directory + set(VulkanHpp_INCLUDE_DIR ${VulkanHpp_SOURCE_DIR}) + message(STATUS "VulkanHpp_SOURCE_DIR: ${VulkanHpp_SOURCE_DIR}") + message(STATUS "VulkanHpp_INCLUDE_DIR: ${VulkanHpp_INCLUDE_DIR}") + + # Check if vulkan.cppm exists in the downloaded repository + if(EXISTS "${VulkanHpp_SOURCE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${VulkanHpp_SOURCE_DIR}) + else() + # If vulkan.cppm doesn't exist, we need to create it + set(VulkanHpp_CPPM_DIR ${CMAKE_CURRENT_BINARY_DIR}/VulkanHpp) + file(MAKE_DIRECTORY ${VulkanHpp_CPPM_DIR}/vulkan) + + # Create vulkan.cppm file + file(WRITE "${VulkanHpp_CPPM_DIR}/vulkan/vulkan.cppm" +"// Auto-generated vulkan.cppm file +module; +#include +export module vulkan; +export namespace vk { + using namespace VULKAN_HPP_NAMESPACE; +} +") + endif() +endif() + +# If the Vulkan Profiles include directory wasn't found, use FetchContent to download +if(NOT VulkanProfiles_INCLUDE_DIR) + # If not found, use FetchContent to download + include(FetchContent) + + message(STATUS "Vulkan-Profiles not found, fetching from GitHub main branch...") + FetchContent_Declare( + VulkanProfiles + GIT_REPOSITORY https://github.com/KhronosGroup/Vulkan-Profiles.git + GIT_TAG main # Use main branch instead of a specific tag + ) + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Populate the content + FetchContent_GetProperties(VulkanProfiles SOURCE_DIR VulkanProfiles_SOURCE_DIR) + if(NOT VulkanProfiles_POPULATED) + FetchContent_Populate(VulkanProfiles) + # Get the source directory after populating + FetchContent_GetProperties(VulkanProfiles SOURCE_DIR VulkanProfiles_SOURCE_DIR) + endif() + + # Create the include directory structure if it doesn't exist + set(VulkanProfiles_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/VulkanProfiles/include) + file(MAKE_DIRECTORY ${VulkanProfiles_INCLUDE_DIR}/vulkan) + + # Create a stub vulkan_profiles.hpp file if it doesn't exist + if(NOT EXISTS "${VulkanProfiles_INCLUDE_DIR}/vulkan/vulkan_profiles.hpp") + file(WRITE "${VulkanProfiles_INCLUDE_DIR}/vulkan/vulkan_profiles.hpp" +"// Auto-generated vulkan_profiles.hpp stub file +#pragma once +#include + +namespace vp { + // Stub implementation for Vulkan Profiles + struct ProfileDesc { + const char* name; + uint32_t specVersion; + }; + + inline bool GetProfileSupport(VkPhysicalDevice physicalDevice, const ProfileDesc* pProfile, VkBool32* pSupported) { + *pSupported = VK_TRUE; + return true; + } +} +") + endif() + + message(STATUS "VulkanProfiles_SOURCE_DIR: ${VulkanProfiles_SOURCE_DIR}") + message(STATUS "VulkanProfiles_INCLUDE_DIR: ${VulkanProfiles_INCLUDE_DIR}") +endif() + +# Set the variables +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(VulkanHpp + REQUIRED_VARS VulkanHpp_INCLUDE_DIR + FAIL_MESSAGE "Could NOT find VulkanHpp. Install it or set VulkanHpp_INCLUDE_DIR to the directory containing vulkan/vulkan.hpp" +) + +# Debug output +message(STATUS "VulkanHpp_FOUND: ${VulkanHpp_FOUND}") +message(STATUS "VULKANHPP_FOUND: ${VULKANHPP_FOUND}") + +if(VulkanHpp_FOUND) + set(VulkanHpp_INCLUDE_DIRS ${VulkanHpp_INCLUDE_DIR}) + + # Make sure VulkanHpp_CPPM_DIR is set + if(NOT DEFINED VulkanHpp_CPPM_DIR) + # Check if vulkan.cppm exists in the include directory + if(EXISTS "${VulkanHpp_INCLUDE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${VulkanHpp_INCLUDE_DIR}) + message(STATUS "Found vulkan.cppm in VulkanHpp_INCLUDE_DIR: ${VulkanHpp_CPPM_DIR}") + elseif(DEFINED VulkanHpp_SOURCE_DIR AND EXISTS "${VulkanHpp_SOURCE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${VulkanHpp_SOURCE_DIR}) + message(STATUS "Found vulkan.cppm in VulkanHpp_SOURCE_DIR: ${VulkanHpp_CPPM_DIR}") + elseif(DEFINED vulkanhpp_SOURCE_DIR AND EXISTS "${vulkanhpp_SOURCE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${vulkanhpp_SOURCE_DIR}) + message(STATUS "Found vulkan.cppm in vulkanhpp_SOURCE_DIR: ${VulkanHpp_CPPM_DIR}") + else() + # If vulkan.cppm doesn't exist, we need to create it + set(VulkanHpp_CPPM_DIR ${CMAKE_CURRENT_BINARY_DIR}/VulkanHpp) + file(MAKE_DIRECTORY ${VulkanHpp_CPPM_DIR}/vulkan) + message(STATUS "Creating vulkan.cppm in ${VulkanHpp_CPPM_DIR}") + + # Create vulkan.cppm file + file(WRITE "${VulkanHpp_CPPM_DIR}/vulkan/vulkan.cppm" +"// Auto-generated vulkan.cppm file +module; +#include +export module vulkan; +export namespace vk { + using namespace VULKAN_HPP_NAMESPACE; +} +") + endif() + endif() + + message(STATUS "Final VulkanHpp_CPPM_DIR: ${VulkanHpp_CPPM_DIR}") + + # Add Vulkan Profiles include directory if found + if(VulkanProfiles_INCLUDE_DIR AND EXISTS "${VulkanProfiles_INCLUDE_DIR}/vulkan/vulkan_profiles.hpp") + list(APPEND VulkanHpp_INCLUDE_DIRS ${VulkanProfiles_INCLUDE_DIR}) + message(STATUS "Added Vulkan Profiles include directory: ${VulkanProfiles_INCLUDE_DIR}") + endif() + + # Create an imported target + if(NOT TARGET VulkanHpp::VulkanHpp) + add_library(VulkanHpp::VulkanHpp INTERFACE IMPORTED) + set_target_properties(VulkanHpp::VulkanHpp PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${VulkanHpp_INCLUDE_DIRS}" + ) + endif() +elseif(DEFINED VulkanHpp_SOURCE_DIR OR DEFINED vulkanhpp_SOURCE_DIR) + # If find_package_handle_standard_args failed but we have a VulkanHpp source directory from FetchContent + # Create an imported target + if(NOT TARGET VulkanHpp::VulkanHpp) + add_library(VulkanHpp::VulkanHpp INTERFACE IMPORTED) + + # Determine the source directory + if(DEFINED VulkanHpp_SOURCE_DIR) + set(_vulkanhpp_source_dir ${VulkanHpp_SOURCE_DIR}) + elseif(DEFINED vulkanhpp_SOURCE_DIR) + set(_vulkanhpp_source_dir ${vulkanhpp_SOURCE_DIR}) + endif() + + message(STATUS "Using fallback VulkanHpp source directory: ${_vulkanhpp_source_dir}") + + set_target_properties(VulkanHpp::VulkanHpp PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_vulkanhpp_source_dir}" + ) + endif() + + # Set variables to indicate that VulkanHpp was found + set(VulkanHpp_FOUND TRUE) + set(VULKANHPP_FOUND TRUE) + + # Set include directories + if(DEFINED _vulkanhpp_source_dir) + set(VulkanHpp_INCLUDE_DIR ${_vulkanhpp_source_dir}) + elseif(DEFINED VulkanHpp_SOURCE_DIR) + set(VulkanHpp_INCLUDE_DIR ${VulkanHpp_SOURCE_DIR}) + elseif(DEFINED vulkanhpp_SOURCE_DIR) + set(VulkanHpp_INCLUDE_DIR ${vulkanhpp_SOURCE_DIR}) + endif() + set(VulkanHpp_INCLUDE_DIRS ${VulkanHpp_INCLUDE_DIR}) + + # Add Vulkan Profiles include directory if found + if(VulkanProfiles_INCLUDE_DIR AND EXISTS "${VulkanProfiles_INCLUDE_DIR}/vulkan/vulkan_profiles.hpp") + list(APPEND VulkanHpp_INCLUDE_DIRS ${VulkanProfiles_INCLUDE_DIR}) + message(STATUS "Added Vulkan Profiles include directory to fallback: ${VulkanProfiles_INCLUDE_DIR}") + endif() + + # Make sure VulkanHpp_CPPM_DIR is set + if(NOT DEFINED VulkanHpp_CPPM_DIR) + # Check if vulkan.cppm exists in the downloaded repository + if(DEFINED VulkanHpp_INCLUDE_DIR AND EXISTS "${VulkanHpp_INCLUDE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${VulkanHpp_INCLUDE_DIR}) + message(STATUS "Found vulkan.cppm in VulkanHpp_INCLUDE_DIR: ${VulkanHpp_CPPM_DIR}") + elseif(DEFINED _vulkanhpp_source_dir AND EXISTS "${_vulkanhpp_source_dir}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${_vulkanhpp_source_dir}) + message(STATUS "Found vulkan.cppm in _vulkanhpp_source_dir: ${VulkanHpp_CPPM_DIR}") + elseif(DEFINED VulkanHpp_SOURCE_DIR AND EXISTS "${VulkanHpp_SOURCE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${VulkanHpp_SOURCE_DIR}) + message(STATUS "Found vulkan.cppm in VulkanHpp_SOURCE_DIR: ${VulkanHpp_CPPM_DIR}") + elseif(DEFINED vulkanhpp_SOURCE_DIR AND EXISTS "${vulkanhpp_SOURCE_DIR}/vulkan/vulkan.cppm") + set(VulkanHpp_CPPM_DIR ${vulkanhpp_SOURCE_DIR}) + message(STATUS "Found vulkan.cppm in vulkanhpp_SOURCE_DIR: ${VulkanHpp_CPPM_DIR}") + else() + # If vulkan.cppm doesn't exist, we need to create it + set(VulkanHpp_CPPM_DIR ${CMAKE_CURRENT_BINARY_DIR}/VulkanHpp) + file(MAKE_DIRECTORY ${VulkanHpp_CPPM_DIR}/vulkan) + message(STATUS "Creating vulkan.cppm in ${VulkanHpp_CPPM_DIR}") + + # Create vulkan.cppm file + file(WRITE "${VulkanHpp_CPPM_DIR}/vulkan/vulkan.cppm" +"// Auto-generated vulkan.cppm file +module; +#include +export module vulkan; +export namespace vk { + using namespace VULKAN_HPP_NAMESPACE; +} +") + endif() + endif() + + message(STATUS "Final VulkanHpp_CPPM_DIR: ${VulkanHpp_CPPM_DIR}") +endif() + +mark_as_advanced(VulkanHpp_INCLUDE_DIR VulkanHpp_CPPM_DIR) diff --git a/CMake/Findglm.cmake b/CMake/Findglm.cmake new file mode 100644 index 0000000..fdf113c --- /dev/null +++ b/CMake/Findglm.cmake @@ -0,0 +1,133 @@ +# Findglm.cmake +# +# Finds the GLM library +# +# This will define the following variables +# +# glm_FOUND +# glm_INCLUDE_DIRS +# +# and the following imported targets +# +# glm::glm +# + +# Try to find the package using pkg-config first +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_glm QUIET glm) +endif() + +# Find the include directory +find_path(glm_INCLUDE_DIR + NAMES glm/glm.hpp + PATHS + ${PC_glm_INCLUDE_DIRS} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include + PATH_SUFFIXES glm +) + +# If the include directory wasn't found, use FetchContent to download and build +if(NOT glm_INCLUDE_DIR) + # If not found, use FetchContent to download and build + include(FetchContent) + + message(STATUS "GLM not found, fetching from GitHub...") + FetchContent_Declare( + glm + GIT_REPOSITORY https://github.com/g-truc/glm.git + GIT_TAG 0.9.9.8 # Use a specific tag for stability + ) + + # Define a function to update the CMake minimum required version + function(update_glm_cmake_version) + # Get the source directory + FetchContent_GetProperties(glm SOURCE_DIR glm_SOURCE_DIR) + + # Update the minimum required CMake version + file(READ "${glm_SOURCE_DIR}/CMakeLists.txt" GLM_CMAKE_CONTENT) + string(REPLACE "cmake_minimum_required(VERSION 3.2" + "cmake_minimum_required(VERSION 3.5" + GLM_CMAKE_CONTENT "${GLM_CMAKE_CONTENT}") + file(WRITE "${glm_SOURCE_DIR}/CMakeLists.txt" "${GLM_CMAKE_CONTENT}") + endfunction() + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # First, declare and populate the content + FetchContent_GetProperties(glm) + if(NOT glm_POPULATED) + FetchContent_Populate(glm) + # Update the CMake version before making it available + update_glm_cmake_version() + endif() + + # Now make it available (this will process the CMakeLists.txt) + FetchContent_MakeAvailable(glm) + + # Get the include directory from the target + if(TARGET glm) + get_target_property(glm_INCLUDE_DIR glm INTERFACE_INCLUDE_DIRECTORIES) + if(NOT glm_INCLUDE_DIR) + # If we can't get the include directory from the target, use the source directory + set(glm_INCLUDE_DIR ${glm_SOURCE_DIR}) + endif() + else() + # GLM might not create a target, so use the source directory + set(glm_INCLUDE_DIR ${glm_SOURCE_DIR}) + endif() +endif() + +# Set the variables +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(glm + REQUIRED_VARS glm_INCLUDE_DIR +) + +if(glm_FOUND) + set(glm_INCLUDE_DIRS ${glm_INCLUDE_DIR}) + + # Create an imported target + if(NOT TARGET glm::glm) + add_library(glm::glm INTERFACE IMPORTED) + set_target_properties(glm::glm PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${glm_INCLUDE_DIRS}" + ) + endif() +elseif(TARGET glm) + # If find_package_handle_standard_args failed but we have a glm target from FetchContent + # Create an alias for the glm target + if(NOT TARGET glm::glm) + add_library(glm::glm ALIAS glm) + endif() + + # Set variables to indicate that glm was found + set(glm_FOUND TRUE) + set(GLM_FOUND TRUE) + + # Set include directories + get_target_property(glm_INCLUDE_DIR glm INTERFACE_INCLUDE_DIRECTORIES) + if(glm_INCLUDE_DIR) + set(glm_INCLUDE_DIRS ${glm_INCLUDE_DIR}) + else() + # If we can't get the include directory from the target, use the source directory + set(glm_INCLUDE_DIR ${glm_SOURCE_DIR}) + set(glm_INCLUDE_DIRS ${glm_INCLUDE_DIR}) + endif() +endif() + +mark_as_advanced(glm_INCLUDE_DIR) diff --git a/CMake/Findnlohmann_json.cmake b/CMake/Findnlohmann_json.cmake new file mode 100644 index 0000000..61dc66a --- /dev/null +++ b/CMake/Findnlohmann_json.cmake @@ -0,0 +1,154 @@ +# Findnlohmann_json.cmake +# +# Finds the nlohmann_json library +# +# This will define the following variables +# +# nlohmann_json_FOUND +# nlohmann_json_INCLUDE_DIRS +# +# and the following imported targets +# +# nlohmann_json::nlohmann_json +# + +# Try to find the package using pkg-config first +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_nlohmann_json QUIET nlohmann_json) +endif() + +# Find the include directory +find_path(nlohmann_json_INCLUDE_DIR + NAMES nlohmann/json.hpp json.hpp + PATHS + ${PC_nlohmann_json_INCLUDE_DIRS} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include + PATH_SUFFIXES nlohmann json +) + +# If the include directory wasn't found, use FetchContent to download and build +if(NOT nlohmann_json_INCLUDE_DIR) + # If not found, use FetchContent to download and build + include(FetchContent) + + message(STATUS "nlohmann_json not found, fetching from GitHub...") + FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.12.0 # Use a specific tag for stability + ) + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Populate the content but don't configure it yet + FetchContent_GetProperties(nlohmann_json) + if(NOT nlohmann_json_POPULATED) + FetchContent_Populate(nlohmann_json) + + if(ANDROID) + # Update the minimum required CMake version before including the CMakeLists.txt + file(READ "${nlohmann_json_SOURCE_DIR}/CMakeLists.txt" NLOHMANN_JSON_CMAKE_CONTENT) + string(REPLACE "cmake_minimum_required(VERSION 3.1" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.2" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.3" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.4" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.5" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.6" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.7" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.8" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.9" + "cmake_minimum_required(VERSION 3.10" + NLOHMANN_JSON_CMAKE_CONTENT "${NLOHMANN_JSON_CMAKE_CONTENT}") + file(WRITE "${nlohmann_json_SOURCE_DIR}/CMakeLists.txt" "${NLOHMANN_JSON_CMAKE_CONTENT}") + endif() + + # Now add the subdirectory manually + add_subdirectory(${nlohmann_json_SOURCE_DIR} ${nlohmann_json_BINARY_DIR}) + else() + # If already populated, just make it available + FetchContent_MakeAvailable(nlohmann_json) + endif() + + # Get the include directory from the target + if(TARGET nlohmann_json) + get_target_property(nlohmann_json_INCLUDE_DIR nlohmann_json INTERFACE_INCLUDE_DIRECTORIES) + if(NOT nlohmann_json_INCLUDE_DIR) + # If we can't get the include directory from the target, use the source directory + set(nlohmann_json_INCLUDE_DIR ${nlohmann_json_SOURCE_DIR}/include) + endif() + else() + # nlohmann_json might not create a target, so use the source directory + set(nlohmann_json_INCLUDE_DIR ${nlohmann_json_SOURCE_DIR}/include) + endif() +endif() + +# Set the variables +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(nlohmann_json + REQUIRED_VARS nlohmann_json_INCLUDE_DIR +) + +if(nlohmann_json_FOUND) + set(nlohmann_json_INCLUDE_DIRS ${nlohmann_json_INCLUDE_DIR}) + + # Create an imported target + if(NOT TARGET nlohmann_json::nlohmann_json) + add_library(nlohmann_json::nlohmann_json INTERFACE IMPORTED) + set_target_properties(nlohmann_json::nlohmann_json PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${nlohmann_json_INCLUDE_DIRS}" + ) + endif() +elseif(TARGET nlohmann_json) + # If find_package_handle_standard_args failed but we have a nlohmann_json target from FetchContent + # Create an alias for the nlohmann_json target + if(NOT TARGET nlohmann_json::nlohmann_json) + add_library(nlohmann_json::nlohmann_json ALIAS nlohmann_json) + endif() + + # Set variables to indicate that nlohmann_json was found + set(nlohmann_json_FOUND TRUE) + set(NLOHMANN_JSON_FOUND TRUE) + + # Set include directories + get_target_property(nlohmann_json_INCLUDE_DIR nlohmann_json INTERFACE_INCLUDE_DIRECTORIES) + if(nlohmann_json_INCLUDE_DIR) + set(nlohmann_json_INCLUDE_DIRS ${nlohmann_json_INCLUDE_DIR}) + else() + # If we can't get the include directory from the target, use the source directory + set(nlohmann_json_INCLUDE_DIR ${nlohmann_json_SOURCE_DIR}/include) + set(nlohmann_json_INCLUDE_DIRS ${nlohmann_json_INCLUDE_DIR}) + endif() +endif() + +mark_as_advanced(nlohmann_json_INCLUDE_DIR) diff --git a/CMake/Findstb.cmake b/CMake/Findstb.cmake new file mode 100644 index 0000000..6ccf72f --- /dev/null +++ b/CMake/Findstb.cmake @@ -0,0 +1,86 @@ +# Findstb.cmake +# +# Finds the stb library (specifically stb_image.h) +# +# This will define the following variables +# +# stb_FOUND +# stb_INCLUDE_DIRS +# +# and the following imported targets +# +# stb::stb +# + +# Try to find the package using pkg-config first +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_stb QUIET stb) +endif() + +# Find the include directory +find_path(stb_INCLUDE_DIR + NAMES stb_image.h + PATHS + ${PC_stb_INCLUDE_DIRS} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include + PATH_SUFFIXES stb +) + +# If the include directory wasn't found, use FetchContent to download and build +if(NOT stb_INCLUDE_DIR) + # If not found, use FetchContent to download and build + include(FetchContent) + + message(STATUS "stb_image.h not found, fetching from GitHub...") + FetchContent_Declare( + stb + GIT_REPOSITORY https://github.com/nothings/stb.git + GIT_TAG master # stb doesn't use version tags, so we use master + ) + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Populate the content + FetchContent_GetProperties(stb) + if(NOT stb_POPULATED) + FetchContent_Populate(stb) + endif() + + # stb is a header-only library with no CMakeLists.txt, so we just need to set the include directory + set(stb_INCLUDE_DIR ${stb_SOURCE_DIR}) +endif() + +# Set the variables +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(stb + REQUIRED_VARS stb_INCLUDE_DIR +) + +if(stb_FOUND) + set(stb_INCLUDE_DIRS ${stb_INCLUDE_DIR}) + + # Create an imported target + if(NOT TARGET stb::stb) + add_library(stb::stb INTERFACE IMPORTED) + set_target_properties(stb::stb PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${stb_INCLUDE_DIRS}" + ) + endif() +endif() + +mark_as_advanced(stb_INCLUDE_DIR) \ No newline at end of file diff --git a/CMake/Findtinygltf.cmake b/CMake/Findtinygltf.cmake new file mode 100644 index 0000000..b241235 --- /dev/null +++ b/CMake/Findtinygltf.cmake @@ -0,0 +1,162 @@ +# Findtinygltf.cmake +# +# Finds the tinygltf library +# +# This will define the following variables +# +# tinygltf_FOUND +# tinygltf_INCLUDE_DIRS +# +# and the following imported targets +# +# tinygltf::tinygltf +# + +# First, try to find nlohmann_json +find_package(nlohmann_json QUIET) +if(NOT nlohmann_json_FOUND) + include(FetchContent) + message(STATUS "nlohmann_json not found, fetching v3.12.0 from GitHub...") + FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG v3.12.0 # Use a specific tag for stability + ) + FetchContent_MakeAvailable(nlohmann_json) +endif() + +# Try to find tinygltf using standard find_package +find_path(tinygltf_INCLUDE_DIR + NAMES tiny_gltf.h + PATHS + ${PC_tinygltf_INCLUDE_DIRS} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include + PATH_SUFFIXES tinygltf include +) + +# If not found, use FetchContent to download and build +if(NOT tinygltf_INCLUDE_DIR) + # If not found, use FetchContent to download and build + include(FetchContent) + + message(STATUS "tinygltf not found, fetching from GitHub...") + FetchContent_Declare( + tinygltf + GIT_REPOSITORY https://github.com/syoyo/tinygltf.git + GIT_TAG v2.8.18 # Use a specific tag for stability + ) + + # Set policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Populate the content but don't configure it yet + FetchContent_GetProperties(tinygltf) + if(NOT tinygltf_POPULATED) + FetchContent_Populate(tinygltf) + + # Update the minimum required CMake version to avoid deprecation warning + file(READ "${tinygltf_SOURCE_DIR}/CMakeLists.txt" TINYGLTF_CMAKE_CONTENT) + string(REPLACE "cmake_minimum_required(VERSION 3.6)" + "cmake_minimum_required(VERSION 3.10)" + TINYGLTF_CMAKE_CONTENT "${TINYGLTF_CMAKE_CONTENT}") + file(WRITE "${tinygltf_SOURCE_DIR}/CMakeLists.txt" "${TINYGLTF_CMAKE_CONTENT}") + + # Create a symbolic link to make nlohmann/json.hpp available + if(EXISTS "${tinygltf_SOURCE_DIR}/json.hpp") + file(MAKE_DIRECTORY "${tinygltf_SOURCE_DIR}/nlohmann") + file(CREATE_LINK "${tinygltf_SOURCE_DIR}/json.hpp" "${tinygltf_SOURCE_DIR}/nlohmann/json.hpp" SYMBOLIC) + endif() + + # Set tinygltf to header-only mode + set(TINYGLTF_HEADER_ONLY ON CACHE BOOL "Use header only version" FORCE) + set(TINYGLTF_INSTALL OFF CACHE BOOL "Do not install tinygltf" FORCE) + + # Add the subdirectory after modifying the CMakeLists.txt + add_subdirectory(${tinygltf_SOURCE_DIR} ${tinygltf_BINARY_DIR}) + else() + # If already populated, just make it available + FetchContent_MakeAvailable(tinygltf) + endif() + + # Get the include directory from the target + get_target_property(tinygltf_INCLUDE_DIR tinygltf INTERFACE_INCLUDE_DIRECTORIES) + if(NOT tinygltf_INCLUDE_DIR) + # If we can't get the include directory from the target, use the source directory + FetchContent_GetProperties(tinygltf SOURCE_DIR tinygltf_SOURCE_DIR) + set(tinygltf_INCLUDE_DIR ${tinygltf_SOURCE_DIR}) + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(tinygltf + REQUIRED_VARS tinygltf_INCLUDE_DIR +) + +if(tinygltf_FOUND) + set(tinygltf_INCLUDE_DIRS ${tinygltf_INCLUDE_DIR}) + + if(NOT TARGET tinygltf::tinygltf) + add_library(tinygltf::tinygltf INTERFACE IMPORTED) + set_target_properties(tinygltf::tinygltf PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${tinygltf_INCLUDE_DIRS}" + INTERFACE_COMPILE_DEFINITIONS "TINYGLTF_IMPLEMENTATION;TINYGLTF_NO_EXTERNAL_IMAGE;TINYGLTF_NO_STB_IMAGE;TINYGLTF_NO_STB_IMAGE_WRITE" + ) + if(TARGET nlohmann_json::nlohmann_json) + target_link_libraries(tinygltf::tinygltf INTERFACE nlohmann_json::nlohmann_json) + endif() + endif() +elseif(TARGET tinygltf) + # If find_package_handle_standard_args failed but we have a tinygltf target from FetchContent + # Create an alias for the tinygltf target + if(NOT TARGET tinygltf::tinygltf) + add_library(tinygltf_wrapper INTERFACE) + target_link_libraries(tinygltf_wrapper INTERFACE tinygltf) + target_compile_definitions(tinygltf_wrapper INTERFACE + TINYGLTF_IMPLEMENTATION + TINYGLTF_NO_EXTERNAL_IMAGE + TINYGLTF_NO_STB_IMAGE + TINYGLTF_NO_STB_IMAGE_WRITE + ) + if(TARGET nlohmann_json::nlohmann_json) + target_link_libraries(tinygltf_wrapper INTERFACE nlohmann_json::nlohmann_json) + endif() + add_library(tinygltf::tinygltf ALIAS tinygltf_wrapper) + endif() + + # Set variables to indicate that tinygltf was found + set(tinygltf_FOUND TRUE) + set(TINYGLTF_FOUND TRUE) + + # Set include directories + get_target_property(tinygltf_INCLUDE_DIR tinygltf INTERFACE_INCLUDE_DIRECTORIES) + if(tinygltf_INCLUDE_DIR) + set(tinygltf_INCLUDE_DIRS ${tinygltf_INCLUDE_DIR}) + else() + # If we can't get the include directory from the target, use the source directory + FetchContent_GetProperties(tinygltf SOURCE_DIR tinygltf_SOURCE_DIR) + set(tinygltf_INCLUDE_DIR ${tinygltf_SOURCE_DIR}) + set(tinygltf_INCLUDE_DIRS ${tinygltf_INCLUDE_DIR}) + + # Explicitly set the include directory on the target + if(TARGET tinygltf) + set_target_properties(tinygltf PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${tinygltf_INCLUDE_DIR}" + ) + endif() +endif() +endif() + +mark_as_advanced(tinygltf_INCLUDE_DIR) diff --git a/CMake/Findtinyobjloader.cmake b/CMake/Findtinyobjloader.cmake new file mode 100644 index 0000000..4b1fb44 --- /dev/null +++ b/CMake/Findtinyobjloader.cmake @@ -0,0 +1,160 @@ +# Findtinyobjloader.cmake +# Find the tinyobjloader library +# +# This module defines the following variables: +# tinyobjloader_FOUND - True if tinyobjloader was found +# tinyobjloader_INCLUDE_DIRS - Include directories for tinyobjloader +# tinyobjloader_LIBRARIES - Libraries to link against tinyobjloader +# +# It also defines the following targets: +# tinyobjloader::tinyobjloader + +# Try to find the package using pkg-config first +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_tinyobjloader QUIET tinyobjloader) +endif() + +# Find the include directory +find_path(tinyobjloader_INCLUDE_DIR + NAMES tiny_obj_loader.h + PATHS + ${PC_tinyobjloader_INCLUDE_DIRS} + /usr/include + /usr/local/include + $ENV{VULKAN_SDK}/include + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../include + PATH_SUFFIXES tinyobjloader tiny_obj_loader +) + +# Find the library +find_library(tinyobjloader_LIBRARY + NAMES tinyobjloader + PATHS + ${PC_tinyobjloader_LIBRARY_DIRS} + /usr/lib + /usr/local/lib + $ENV{VULKAN_SDK}/lib + ${ANDROID_NDK}/sources/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../attachments/lib + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../external + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../third_party + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../../lib + PATH_SUFFIXES lib +) + +# If the include directory wasn't found, use FetchContent to download and build +if(NOT tinyobjloader_INCLUDE_DIR) + # If not found, use FetchContent to download and build + include(FetchContent) + + message(STATUS "tinyobjloader not found, fetching from GitHub...") + FetchContent_Declare( + tinyobjloader + GIT_REPOSITORY https://github.com/tinyobjloader/tinyobjloader.git + GIT_TAG v2.0.0rc10 # Use a specific tag for stability + ) + + # Set options before making tinyobjloader available + set(TINYOBJLOADER_BUILD_TEST_LOADER OFF CACHE BOOL "Do not build test loader" FORCE) + set(TINYOBJLOADER_BUILD_OBJ_STICHER OFF CACHE BOOL "Do not build obj sticher" FORCE) + set(TINYOBJLOADER_INSTALL OFF CACHE BOOL "Do not install tinyobjloader" FORCE) + + # Update CMake policy to suppress the deprecation warning + if(POLICY CMP0169) + cmake_policy(SET CMP0169 OLD) + endif() + + # Populate the content but don't configure it yet + FetchContent_GetProperties(tinyobjloader) + if(NOT tinyobjloader_POPULATED) + FetchContent_Populate(tinyobjloader) + + # Update the minimum required CMake version before including the CMakeLists.txt + file(READ "${tinyobjloader_SOURCE_DIR}/CMakeLists.txt" TINYOBJLOADER_CMAKE_CONTENT) + string(REPLACE "cmake_minimum_required(VERSION 3.2)" + "cmake_minimum_required(VERSION 3.10)" + TINYOBJLOADER_CMAKE_CONTENT "${TINYOBJLOADER_CMAKE_CONTENT}") + string(REPLACE "cmake_minimum_required(VERSION 3.5)" + "cmake_minimum_required(VERSION 3.10)" + TINYOBJLOADER_CMAKE_CONTENT "${TINYOBJLOADER_CMAKE_CONTENT}") + file(WRITE "${tinyobjloader_SOURCE_DIR}/CMakeLists.txt" "${TINYOBJLOADER_CMAKE_CONTENT}") + + # Now add the subdirectory manually + add_subdirectory(${tinyobjloader_SOURCE_DIR} ${tinyobjloader_BINARY_DIR}) + else() + # If already populated, just make it available + FetchContent_MakeAvailable(tinyobjloader) + endif() + + # Get the include directory from the target + get_target_property(tinyobjloader_INCLUDE_DIR tinyobjloader INTERFACE_INCLUDE_DIRECTORIES) + if(NOT tinyobjloader_INCLUDE_DIR) + # If we can't get the include directory from the target, use the source directory + set(tinyobjloader_INCLUDE_DIR ${tinyobjloader_SOURCE_DIR}) + endif() +endif() + +# Set the variables +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(tinyobjloader + REQUIRED_VARS tinyobjloader_INCLUDE_DIR +) + +if(tinyobjloader_FOUND) + set(tinyobjloader_INCLUDE_DIRS ${tinyobjloader_INCLUDE_DIR}) + + if(tinyobjloader_LIBRARY) + set(tinyobjloader_LIBRARIES ${tinyobjloader_LIBRARY}) + else() + # tinyobjloader is a header-only library, so no library is needed + set(tinyobjloader_LIBRARIES "") + endif() + + # Create an imported target + if(NOT TARGET tinyobjloader::tinyobjloader) + add_library(tinyobjloader::tinyobjloader INTERFACE IMPORTED) + set_target_properties(tinyobjloader::tinyobjloader PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${tinyobjloader_INCLUDE_DIRS}" + ) + if(tinyobjloader_LIBRARIES) + set_target_properties(tinyobjloader::tinyobjloader PROPERTIES + INTERFACE_LINK_LIBRARIES "${tinyobjloader_LIBRARIES}" + ) + endif() + endif() +elseif(TARGET tinyobjloader) + # If find_package_handle_standard_args failed but we have a tinyobjloader target from FetchContent + # Create an alias for the tinyobjloader target + if(NOT TARGET tinyobjloader::tinyobjloader) + add_library(tinyobjloader::tinyobjloader ALIAS tinyobjloader) + endif() + + # Set variables to indicate that tinyobjloader was found + set(tinyobjloader_FOUND TRUE) + set(TINYOBJLOADER_FOUND TRUE) + + # Set include directories + get_target_property(tinyobjloader_INCLUDE_DIR tinyobjloader INTERFACE_INCLUDE_DIRECTORIES) + if(tinyobjloader_INCLUDE_DIR) + set(tinyobjloader_INCLUDE_DIRS ${tinyobjloader_INCLUDE_DIR}) + else() + # If we can't get the include directory from the target, use the source directory + set(tinyobjloader_INCLUDE_DIR ${tinyobjloader_SOURCE_DIR}) + set(tinyobjloader_INCLUDE_DIRS ${tinyobjloader_INCLUDE_DIR}) + endif() +endif() + +mark_as_advanced(tinyobjloader_INCLUDE_DIR tinyobjloader_LIBRARY) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..636970f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,202 @@ +cmake_minimum_required (VERSION 3.29) + +project (VulkanTutorial) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake") + +# Add option to enable/disable C++ 20 module +option(ENABLE_CPP20_MODULE "Enable C++ 20 module support for Vulkan" OFF) + +# Enable C++ module dependency scanning only if C++ 20 module is enabled +if(ENABLE_CPP20_MODULE) + set(CMAKE_CXX_SCAN_FOR_MODULES ON) +endif() + +find_package (glfw3 REQUIRED) +find_package (glm REQUIRED) +find_package (Vulkan 1.4.335 REQUIRED) # Require Vulkan SDK version 1.4.335 or higher +find_package (tinyobjloader REQUIRED) +find_package (tinygltf REQUIRED) +find_package (KTX REQUIRED) + +# set up Vulkan C++ module only if enabled +if(ENABLE_CPP20_MODULE) + add_library(VulkanCppModule) + add_library(Vulkan::cppm ALIAS VulkanCppModule) + + target_compile_definitions(VulkanCppModule + PUBLIC VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1 VULKAN_HPP_NO_STRUCT_CONSTRUCTORS=1 + ) + target_include_directories(VulkanCppModule + PUBLIC + "${Vulkan_INCLUDE_DIR}" + ) + target_link_libraries(VulkanCppModule + PUBLIC + Vulkan::Vulkan + ) + + set_target_properties(VulkanCppModule PROPERTIES CXX_STANDARD 20) + + # Add MSVC-specific compiler options for proper C++ module support + if(MSVC) + target_compile_options(VulkanCppModule PRIVATE + /std:c++latest # Use latest C++ standard for better module support + /permissive- # Standards conformance mode + /Zc:__cplusplus # Enable correct __cplusplus macro + /EHsc # Enable C++ exception handling + /Zc:preprocessor # Use conforming preprocessor + /translateInclude # Automatically translate #include to import for standard library + ) + endif() + + target_sources(VulkanCppModule + PUBLIC + FILE_SET cxx_modules TYPE CXX_MODULES + BASE_DIRS + "${Vulkan_INCLUDE_DIR}" + FILES + "${Vulkan_INCLUDE_DIR}/vulkan/vulkan.cppm" + ) + + + # Add the vulkan.cppm file directly as a source file + target_sources(VulkanCppModule + PRIVATE + "${Vulkan_INCLUDE_DIR}/vulkan/vulkan.cppm" + ) +else() + # Create a dummy interface library when C++ 20 module is disabled + add_library(VulkanCppModule INTERFACE) + add_library(Vulkan::cppm ALIAS VulkanCppModule) + target_link_libraries(VulkanCppModule INTERFACE Vulkan::Vulkan) + target_compile_definitions(VulkanCppModule + INTERFACE VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1 VULKAN_HPP_NO_STRUCT_CONSTRUCTORS=1 + ) +endif() + +find_package(stb REQUIRED) +set(STB_INCLUDEDIR ${stb_INCLUDE_DIRS}) + +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) + +include(CheckIPOSupported) +check_ipo_supported(RESULT LTO_SUPPORTED OUTPUT LTO_ERROR) + +if(LTO_SUPPORTED AND CMAKE_BUILD_TYPE STREQUAL "Release") + message(STATUS "LTO activated") + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +else() + message(WARNING "LTO not supported : ${LTO_ERROR}") +endif() + +function (add_shaders_target TARGET) + cmake_parse_arguments ("SHADER" "" "CHAPTER_NAME" "SOURCES" ${ARGN}) + set (SHADERS_DIR ${SHADER_CHAPTER_NAME}/shaders) + add_custom_command ( + OUTPUT ${SHADERS_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADERS_DIR} + ) + add_custom_command ( + OUTPUT ${SHADERS_DIR}/frag.spv ${SHADERS_DIR}/vert.spv + COMMAND glslang::validator + ARGS --target-env vulkan1.0 ${SHADER_SOURCES} --quiet + WORKING_DIRECTORY ${SHADERS_DIR} + DEPENDS ${SHADERS_DIR} ${SHADER_SOURCES} + COMMENT "Compiling Shaders" + VERBATIM + ) + add_custom_target (${TARGET} DEPENDS ${SHADERS_DIR}/frag.spv ${SHADERS_DIR}/vert.spv) +endfunction () + +function (add_slang_shader_target TARGET) + cmake_parse_arguments ("SHADER" "" "CHAPTER_NAME" "SOURCES" ${ARGN}) + set (SHADERS_DIR ${SHADER_CHAPTER_NAME}/shaders) + #file(GLOB HAS_COMPUTE ${CHAPTER_SHADER}.comp) + set (ENTRY_POINTS -entry vertMain -entry fragMain) + if(HAS_COMPUTE) + list(APPEND ENTRY_POINTS -entry compMain) + endif() + add_custom_command ( + OUTPUT ${SHADERS_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${SHADERS_DIR} + ) + add_custom_command ( + OUTPUT ${SHADERS_DIR}/slang.spv + COMMAND ${SLANGC_EXECUTABLE} ${SHADER_SOURCES} -target spirv -profile spirv_1_4+spvRayQueryKHR -emit-spirv-directly -fvk-use-entrypoint-name ${ENTRY_POINTS} -o slang.spv + WORKING_DIRECTORY ${SHADERS_DIR} + DEPENDS ${SHADERS_DIR} ${SHADER_SOURCES} + COMMENT "Compiling Slang Shaders" + VERBATIM + ) + add_custom_target (${TARGET} DEPENDS ${SHADERS_DIR}/slang.spv) +endfunction() + +function (add_chapter CHAPTER_NAME) + cmake_parse_arguments (CHAPTER "" "SHADER" "LIBS;TEXTURES;MODELS" ${ARGN}) + add_executable (${CHAPTER_NAME} src/${CHAPTER_NAME}.cpp) + set_target_properties (${CHAPTER_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}) + set_target_properties (${CHAPTER_NAME} PROPERTIES CXX_STANDARD 20) + target_link_libraries (${CHAPTER_NAME} Vulkan::cppm glfw) + target_include_directories (${CHAPTER_NAME} PRIVATE ${STB_INCLUDEDIR}) + + # Add compile definition if C++ 20 module is enabled + if(ENABLE_CPP20_MODULE) + target_compile_definitions(${CHAPTER_NAME} PRIVATE USE_CPP20_MODULES=1) + 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(${CHAPTER_NAME} PRIVATE "VULKAN_HPP_HANDLE_ERROR_OUT_OF_DATE_AS_SUCCESS" ) + + if(WIN32) + if(${CMAKE_GENERATOR} MATCHES "Visual Studio.*") + set_target_properties(${CHAPTER_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/${CHAPTER_NAME}") + endif() + endif() + + if (DEFINED CHAPTER_SHADER) + set (CHAPTER_SHADER_TARGET ${CHAPTER_NAME}_shader) + file (GLOB SHADER_SOURCES ${CHAPTER_SHADER}.frag ${CHAPTER_SHADER}.vert ${CHAPTER_SHADER}.comp) + if (SHADER_SOURCES) + add_shaders_target (${CHAPTER_SHADER_TARGET} CHAPTER_NAME ${CHAPTER_NAME} SOURCES ${SHADER_SOURCES}) + add_dependencies (${CHAPTER_NAME} ${CHAPTER_SHADER_TARGET}) + endif() + + set (CHAPTER_SHADER_SLANG_TARGET ${CHAPTER_NAME}_slang_shader) + file (GLOB SHADER_SLANG_SOURCES ${CHAPTER_SHADER}.slang) + if(SHADER_SLANG_SOURCES) + add_slang_shader_target( ${CHAPTER_SHADER_SLANG_TARGET} CHAPTER_NAME ${CHAPTER_NAME} SOURCES ${SHADER_SLANG_SOURCES}) + add_dependencies(${CHAPTER_NAME} ${CHAPTER_SHADER_SLANG_TARGET}) + endif() + endif () + if (DEFINED CHAPTER_LIBS) + target_link_libraries (${CHAPTER_NAME} ${CHAPTER_LIBS}) + endif () + if (DEFINED CHAPTER_MODELS) + list(TRANSFORM CHAPTER_MODELS PREPEND "${CMAKE_SOURCE_DIR}/assets/") + file (COPY ${CHAPTER_MODELS} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/models) + endif () + if (DEFINED CHAPTER_TEXTURES) + list(TRANSFORM CHAPTER_TEXTURES PREPEND "${CMAKE_SOURCE_DIR}/textures/") + file (COPY ${CHAPTER_TEXTURES} DESTINATION ${CMAKE_BINARY_DIR}/${CHAPTER_NAME}/textures) + endif () +endfunction () + +add_chapter (main + SHADER shaders/main + TEXTURES texture.jpg +) + +#add_chapter (38_ray_tracing +# SHADER 38_ray_tracing +# MODELS plant_on_table.obj +# MODELS plant_on_table.mtl +# TEXTURES plant_on_table_textures/nettle_plant_diff_4k.png +# TEXTURES plant_on_table_textures/potted_plant_02_pot_diff_1k.png +# TEXTURES plant_on_table_textures/wooden_picnic_table_bottom_diff_1k.png +# TEXTURES plant_on_table_textures/wooden_picnic_table_top_diff_1k.png +# LIBS glm::glm tinyobjloader::tinyobjloader) diff --git a/compile_shaders.sh b/compile_shaders.sh new file mode 100755 index 0000000..d4b7a61 --- /dev/null +++ b/compile_shaders.sh @@ -0,0 +1 @@ +slangc shaders/main.slang -target spirv -profile spirv_1_4 -emit-spirv-directly -fvk-use-entrypoint-name -entry vertMain -entry fragMain -o shaders/slang.spv diff --git a/shaders/main.slang b/shaders/main.slang new file mode 100644 index 0000000..14c8123 --- /dev/null +++ b/shaders/main.slang @@ -0,0 +1,34 @@ +struct VSInput { + float3 inPosition; + float3 inColor; + float2 inTexCoord; +}; + +struct UniformBuffer { + float4x4 model; + float4x4 view; + float4x4 proj; +}; +ConstantBuffer ubo; + +struct VSOutput { + float4 pos : SV_Position; + float3 fragColor; + float2 fragTexCoord; +}; + +[shader("vertex")] +VSOutput vertMain(VSInput input) { + VSOutput output; + output.pos = mul(ubo.proj, mul(ubo.view, mul(ubo.model, float4(input.inPosition, 1.0)))); + output.fragColor = input.inColor; + output.fragTexCoord = input.inTexCoord; + return output; +} + +Sampler2D texture; + +[shader("fragment")] +float4 fragMain(VSOutput vertIn) : SV_Target { + return texture.Sample(vertIn.fragTexCoord); +} diff --git a/shaders/shader.slang b/shaders/shader.slang new file mode 100644 index 0000000..7de09a7 --- /dev/null +++ b/shaders/shader.slang @@ -0,0 +1,30 @@ +static float2 positions[3] = float2[]( + float2(0.0, -0.5), + float2(0.5, 0.5), + float2(-0.5, 0.5) +); + +static float3 colors[3] = float3[] ( + float3(1.0, 0.0, 0.0), + float3(0.0, 1.0, 0.0), + float3(0.0, 0.0, 1.0) +); + +struct VertexOutput { + float3 color; + float4 sv_position : SV_Position; +}; + +[shader("vertex")] +VertexOutput vertMain(uint vid : SV_VertexID) { + VertexOutput output; + output.sv_position = float4(positions[vid], 0.0, 1.0); + output.color = colors[vid]; + return output; +} + +[shader("fragment")] +float4 fragMain(VertexOutput inVert) : SV_Target { + float3 color = inVert.color; + return float4(color, 1.0); +} diff --git a/shaders/slang.spv b/shaders/slang.spv new file mode 100644 index 0000000..87c2941 Binary files /dev/null and b/shaders/slang.spv differ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ed86d62 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,1593 @@ +//========== Includes ========== +// General includes +#include +#include +#include +#include // std::clamp +#include +#include +#include // uint32_t +#include +#include // std::numeric_limits +#include // to load shaders +#include +#include +#include +#include +#include +// Vulkan Includes +#include "vulkan/vulkan.hpp" +#include +#include +#if defined(__INTELLISENSE__) || !defined(USE_CPP20_MODULES) +#include +#else +import vulkan_hpp; +#endif +// GLFW Includes +#define GLFW_INCLUDE_VULKAN +#include +// GLM - Linear algebra +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +#include +// Images +#define STB_IMAGE_IMPLEMENTATION +#include + +// Consts +constexpr uint32_t WIDTH = 800; +constexpr uint32_t HEIGHT = 600; +constexpr int MAX_FRAMES_IN_FLIGHT = 2; + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +#ifdef NDEBUG +constexpr bool enableValidationLayers = false; +#else +constexpr bool enableValidationLayers = true; +#endif + +// Vertices +struct Vertex { + glm::vec3 pos; + glm::vec3 color; + glm::vec2 texCoord; + + static vk::VertexInputBindingDescription getBindingDescription() { + return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; + } + + static std::array getAttributeDescriptions() { + return { + vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, pos) ), + vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), + vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) + }; + } +}; + +const std::vector vertices = { + {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + + {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} +}; + +const std::vector indices = { + 0, 1, 2, 2, 3, 0, + 4, 5, 6, 6, 7, 4 +}; + + +//Uniform buffer +struct UniformBufferObject { + glm::mat4 model; + glm::mat4 view; + glm::mat4 proj; +}; + +class HelloTriangleApplication { + public: + void run() { + #ifdef DEBUG + std::cout << "Global initialisation...\n"; + #endif + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + #ifdef DEBUG + std::cout << "Bye Bye !\n"; + #endif + + } + private: + //========== CLASS MEMBERS ========== + GLFWwindow *window = nullptr; + vk::raii::Context context; + vk::raii::Instance instance = nullptr; + vk::raii::DebugUtilsMessengerEXT debugMessenger = nullptr; + vk::raii::SurfaceKHR surface = nullptr; + vk::raii::PhysicalDevice physicalDevice = nullptr; + vk::raii::Device device = nullptr; + vk::raii::Queue queue = nullptr; + // Swap chain + vk::raii::SwapchainKHR swapChain = nullptr; + std::vector swapChainImages; + vk::Format swapChainImageFormat = vk::Format::eUndefined; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; + // Decriptor Set (used for Uniform buffers) + vk::raii::DescriptorSetLayout descriptorSetLayout = nullptr; + // Pools + vk::raii::CommandPool commandPool = nullptr; + vk::raii::DescriptorPool descriptorPool = nullptr; + std::vector descriptorSets; + // Pipeline + vk::raii::PipelineLayout pipelineLayout = nullptr; + vk::raii::Pipeline graphicsPipeline = nullptr; + // Buffers + // Vertices - Buffers + std::vector commandBuffers; + vk::raii::Buffer vertexBuffer = nullptr; + vk::raii::DeviceMemory vertexBufferMemory = nullptr; + // Index - Buffers + vk::raii::Buffer indexBuffer = nullptr; + vk::raii::DeviceMemory indexBufferMemory = nullptr; + // Uniform Buffers - Buffers + std::vector uniformBuffers; + std::vector uniformBuffersMemory; + std::vector uniformBuffersMapped; + // Sync + std::vector presentCompleteSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + // Image texture + vk::raii::Image textureImage = nullptr; + vk::raii::DeviceMemory textureImageMemory = nullptr; + vk::raii::ImageView textureImageView = nullptr; + vk::raii::Sampler textureSampler = nullptr; + // Depth images + vk::raii::Image depthImage = nullptr; + vk::raii::DeviceMemory depthImageMemory = nullptr; + vk::raii::ImageView depthImageView = nullptr; + // Indexes + uint32_t queueIndex = ~0; + uint32_t frameIndex = 0; + // Bools + bool framebufferResized = false; + + // Device Extensions + std::vector deviceExtensions = { + vk::KHRSwapchainExtensionName + }; + + //========== Inits ========== + + void initWindow() { + #ifdef DEBUG + std::cout << "Creating GLFW window...\n"; + #endif + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + + window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); + #ifdef DEBUG + std::cout << "Window created !\n"; + #endif + } + + void initVulkan() { + #ifdef DEBUG + std::cout << "\tInitialing Vulkan...\n"; + #endif + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createDescriptorSetLayout(); + createGraphicsPipeline(); + createCommandPool(); + createDepthResources(); + createTextureImage(); + createTextureImageView(); + createTextureSampler(); + createVertexBuffer(); + createIndexBuffer(); + createUniformBuffers(); + createDescriptorPool(); + createDescriptorSets(); + createCommandBuffers(); + createSyncObjects(); + #ifdef DEBUG + std::cout << "\tVulkan is initialised !\n"; + #endif + } + + void mainLoop() { + #ifdef DEBUG + std::cout << "Main Loop...\n"; + #endif + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + device.waitIdle(); + } + + void cleanupSwapChain() { + swapChainImageViews.clear(); + swapChain = nullptr; + } + + void cleanup() { + #ifdef DEBUG + std::cout << "Cleaning up...\n"; + #endif + cleanupSwapChain(); + surface = nullptr; + glfwDestroyWindow(window); + glfwTerminate(); + #ifdef DEBUG + std::cout << "Done !\n"; + #endif + } + + + //========== UTILS ========== + void createInstance() { + #ifdef DEBUG + std::cout << "Creating a Vulkan Instance...\n"; + #endif + constexpr vk::ApplicationInfo appInfo{ + .pApplicationName = "Hello Vulkan", + .applicationVersion = VK_MAKE_VERSION( 1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = vk::ApiVersion14 }; + + // Get the required leyers + std::vector requiredLayers; + if (enableValidationLayers) { + requiredLayers.assign(validationLayers.begin(), validationLayers.end()); + } + + // Check if the required layers are supported by the Vulkan implementation + auto layerProperties = context.enumerateInstanceLayerProperties(); + if (std::ranges::any_of(requiredLayers, [&layerProperties](auto const& requiredLayer) { + return std::ranges::none_of(layerProperties, + [requiredLayer](auto const& layerProperty) + { return strcmp(layerProperty.layerName, requiredLayer) == 0; }); + })) + { + throw std::runtime_error("One or more required layers are not supported!\nAborting..."); + } + + // Get the required extensions + auto requiredExtensions = getRequiredExtensions(); + + // Check if the required extensions are supported by the Vulkan implementation + auto extensionsProperties = context.enumerateInstanceExtensionProperties(); + for (auto const &requiredExtension : requiredExtensions) + { + if (std::ranges::none_of(extensionsProperties, + [requiredExtension](auto const &extensionsProperty) + { return strcmp(extensionsProperty.extensionName, requiredExtension) == 0; })) + { + throw std::runtime_error("Required extension not supported: " + std::string(requiredExtension) + "\nAborting..."); + } + } + + vk::InstanceCreateInfo createInfo{ + .pApplicationInfo = &appInfo, + .enabledLayerCount = static_cast(requiredLayers.size()), + .ppEnabledLayerNames = requiredLayers.data(), + .enabledExtensionCount = static_cast(requiredExtensions.size()), + .ppEnabledExtensionNames = requiredExtensions.data()}; + + instance = vk::raii::Instance(context, createInfo); + #ifdef DEBUG + std::cout << requiredExtensions.size() << " extensions loaded !\n"; + std::cout << requiredLayers.size() << " layers loaded !\n"; + + std::cout << "Finished creating the Vulkan Instance !\n"; + #endif + } + + void setupDebugMessenger () { + #ifdef DEBUG + std::cout << "Setting up the messenger !\n"; + #endif + if (!enableValidationLayers) { + return; + } + + vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); + vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags(vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{ + .messageSeverity = severityFlags, + .messageType = messageTypeFlags, + .pfnUserCallback = &debugCallback }; + + debugMessenger = instance.createDebugUtilsMessengerEXT(debugUtilsMessengerCreateInfoEXT); + #ifdef DEBUG + std::cout << "Messenger set up !\n"; + #endif + } + + void pickPhysicalDevice() { + #ifdef DEBUG + std::cout << "Selecting the GPU...\n"; + #endif + std::vector devices = instance.enumeratePhysicalDevices(); + const auto devIter = std::ranges::find_if( + devices, + [&](auto const &device) { + // Check if the device supports the Vulkan 1.3 API version + bool supportsVulkan1_3 = device.getProperties().apiVersion >= VK_API_VERSION_1_3; + + // Check if any of the queue families support graphics operations + auto queueFamilies = device.getQueueFamilyProperties(); + bool supportsGraphics = std::ranges::any_of(queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); }); + return supportsVulkan1_3 && supportsGraphics; + }); + if (devIter != devices.end()) { + physicalDevice = *devIter; + #ifdef DEBUG + std::cout << "GPU selected !\n"; + #endif + } + else { + throw std::runtime_error("Failed to find a suitable GPU!\nAborting..."); + } + } + + + void createLogicalDevice() { + #ifdef DEBUG + std::cout << "Creating a logical device !\n"; + #endif + + //std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + uint32_t queueIndex = findQueueFamilies(physicalDevice); // This is an index + + + vk::StructureChain< + vk::PhysicalDeviceFeatures2, + vk::PhysicalDeviceVulkan13Features, + vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT + > featureChain = { + {.features = {.samplerAnisotropy = true}}, + { + .synchronization2 = true, + .dynamicRendering = true, + }, // All features from Vulkan 1.3 + { .extendedDynamicState = true } + }; + + + // Create the device: + float queuePriority = 0.5f; + vk::DeviceQueueCreateInfo deviceQueueCreateInfo { + .queueFamilyIndex = queueIndex, + .queueCount = 1, + .pQueuePriorities = &queuePriority + }; + vk::DeviceCreateInfo deviceCreateInfo { + .pNext = &featureChain.get(), + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &deviceQueueCreateInfo, + .enabledExtensionCount = static_cast(deviceExtensions.size()), + .ppEnabledExtensionNames = deviceExtensions.data() + }; + + device = vk::raii::Device( physicalDevice, deviceCreateInfo ); + queue = vk::raii::Queue( device, queueIndex, 0 ); + #ifdef DEBUG + std::cout << "Logical device created\n"; + #endif + } + + void createSurface() { + #ifdef DEBUG + std::cout << "Creating a surface !\n"; + #endif + VkSurfaceKHR _surface; + if (glfwCreateWindowSurface(*instance, window, nullptr, &_surface) != 0) { + throw std::runtime_error("Failed to create Window surface !\nAborting..."); + } + surface = vk::raii::SurfaceKHR(instance, _surface); + #ifdef DEBUG + std::cout << "Surface created !\n"; + #endif + } + + void createSwapChain() { + #ifdef DEBUG + std::cout << "Creating Swap Chain...\n"; + #endif + auto surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(*surface); + auto swapChainSurfaceFormat = chooseSwapSurfaceFormat(physicalDevice.getSurfaceFormatsKHR(*surface)); + swapChainExtent = chooseSwapExtent(surfaceCapabilities); + auto minImageCount = std::max(3u, surfaceCapabilities.minImageCount); + minImageCount = ( surfaceCapabilities.maxImageCount > 0 && minImageCount > surfaceCapabilities.maxImageCount ) ? surfaceCapabilities.maxImageCount : minImageCount; + + vk::SwapchainCreateInfoKHR swapChainCreateInfo { + .flags = vk::SwapchainCreateFlagsKHR(), + .surface = *surface, + .minImageCount = minImageCount, + .imageFormat = swapChainSurfaceFormat.format, + .imageColorSpace = swapChainSurfaceFormat.colorSpace, + .imageExtent = swapChainExtent, + .imageArrayLayers = 1, + .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageSharingMode = vk::SharingMode::eExclusive, + .preTransform = surfaceCapabilities.currentTransform, + .compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, + .presentMode = chooseSwapPresentMode(physicalDevice.getSurfacePresentModesKHR(*surface)), + .clipped = true, + .oldSwapchain = nullptr + }; + swapChain = vk::raii::SwapchainKHR(device, swapChainCreateInfo); + swapChainImages = swapChain.getImages(); + swapChainImageFormat = swapChainSurfaceFormat.format; + #ifdef DEBUG + std::cout << "Swap Chain Created !\n"; + #endif + } + + void createImageViews() { + #ifdef DEBUG + std::cout << "Creating Image View...\n"; + #endif + swapChainImageViews.clear(); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + swapChainImageViews.emplace_back(createImageView( + swapChainImages[i], + swapChainImageFormat, + vk::ImageAspectFlagBits::eColor + )); + } + #ifdef DEBUG + std::cout << "Image View created !\n"; + #endif + } + + void createDescriptorSetLayout() { + #ifdef DEBUG + std::cout << "Creating the descriptor set...\n"; + #endif + std::array bindings = { + vk::DescriptorSetLayoutBinding( + 0, + vk::DescriptorType::eUniformBuffer, + 1, + vk::ShaderStageFlagBits::eVertex, + nullptr + ), + vk::DescriptorSetLayoutBinding( + 1, + vk::DescriptorType::eCombinedImageSampler, + 1, + vk::ShaderStageFlagBits::eFragment, + nullptr + ) + }; + vk::DescriptorSetLayoutCreateInfo layoutInfo { + .bindingCount = static_cast(bindings.size()), + .pBindings = bindings.data() + }; + + descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); + + #ifdef DEBUG + std::cout << "Descriptor set created !\n"; + #endif + } + + void createGraphicsPipeline() { + #ifdef DEBUG + std::cout << "Creating the Graphics Pipeline...\n"; + #endif + vk::raii::ShaderModule shaderModule = createShaderModule(readFile("shaders/slang.spv")); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo { + .stage = vk::ShaderStageFlagBits::eVertex, + .module = shaderModule, + .pName = "vertMain" + }; + vk::PipelineShaderStageCreateInfo fragShaderStageInfo { + .stage = vk::ShaderStageFlagBits::eFragment, + .module = shaderModule, + .pName = "fragMain" + }; + vk::PipelineShaderStageCreateInfo shaderStages[] { + vertShaderStageInfo, + fragShaderStageInfo + }; + + // Passing vertices into the pipeline + auto bindingDescription = Vertex::getBindingDescription(); + auto attributeDescription = Vertex::getAttributeDescriptions(); + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo { + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast( attributeDescription.size() ), + .pVertexAttributeDescriptions = attributeDescription.data() + }; + + vk::PipelineInputAssemblyStateCreateInfo inputAssembly { + .topology = vk::PrimitiveTopology::eTriangleList + }; + + vk::PipelineViewportStateCreateInfo viewportState { + .viewportCount = 1, + .scissorCount = 1 + }; + + vk::PipelineRasterizationStateCreateInfo rasterizer { + .depthClampEnable = vk::False, + .rasterizerDiscardEnable = vk::False, + .polygonMode = vk::PolygonMode::eFill, + .cullMode = vk::CullModeFlagBits::eBack, + .frontFace = vk::FrontFace::eCounterClockwise, + .depthBiasEnable = vk::False, + .depthBiasSlopeFactor = 1.0f, + .lineWidth = 1.0f + }; + + vk::PipelineMultisampleStateCreateInfo multisampling { + .rasterizationSamples = vk::SampleCountFlagBits::e1, + .sampleShadingEnable = vk::False + }; + + vk::PipelineDepthStencilStateCreateInfo depthStencil { + .depthTestEnable = vk::True, + .depthWriteEnable = vk::True, + .depthCompareOp = vk::CompareOp::eLess, + .depthBoundsTestEnable = vk::False, + .stencilTestEnable = vk::False + }; + + // Color blending + vk::PipelineColorBlendAttachmentState colorBlendAttachment { + .blendEnable = vk::False, + .colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA + }; + + vk::PipelineColorBlendStateCreateInfo colorBlending { + .logicOpEnable = vk::False, + .logicOp = vk::LogicOp::eCopy, + .attachmentCount = 1, + .pAttachments = &colorBlendAttachment + }; + + // Dynamic States + std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor + }; + + vk::PipelineDynamicStateCreateInfo dynamicState { + .dynamicStateCount = static_cast(dynamicStates.size()), + .pDynamicStates = dynamicStates.data() + }; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo { + .setLayoutCount = 1, + .pSetLayouts = &*descriptorSetLayout, + .pushConstantRangeCount = 0 + }; + + pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); + vk::Format depthFormat = findDepthFormat(); + + vk::PipelineRenderingCreateInfo pipelineRenderingCreateInfo { + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &swapChainImageFormat, + .depthAttachmentFormat = depthFormat + }; + + // Pipeline creation + vk::GraphicsPipelineCreateInfo pipelineInfo { + .pNext = &pipelineRenderingCreateInfo, + .stageCount = 2, + .pStages = shaderStages, + .pVertexInputState = &vertexInputInfo, + .pInputAssemblyState = &inputAssembly, + .pViewportState = &viewportState, + .pRasterizationState = &rasterizer, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depthStencil, + .pColorBlendState = &colorBlending, + .pDynamicState = &dynamicState, + .layout = pipelineLayout, + .renderPass = nullptr, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = -1 + }; + + graphicsPipeline = vk::raii::Pipeline(device, nullptr, pipelineInfo); + + #ifdef DEBUG + std::cout << "Pipeline created !\n"; + #endif + } + + void createCommandPool() { + #ifdef DEBUG + std::cout << "Creating a command pool\n"; + #endif + vk::CommandPoolCreateInfo poolInfo { + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = queueIndex + }; + + commandPool = vk::raii::CommandPool(device, poolInfo); + #ifdef DEBUG + std::cout << "Command pool created\n"; + #endif + } + + void createDepthResources() { + #ifdef DEBUG + std::cout << "Creating a depth ressouce...\n"; + #endif + vk::Format depthFormat = findDepthFormat(); + + createImage( + swapChainExtent.width, + swapChainExtent.height, + depthFormat, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eDepthStencilAttachment, + vk::MemoryPropertyFlagBits::eDeviceLocal, + depthImage, + depthImageMemory + ); + depthImageView = createImageView( + depthImage, + depthFormat, + vk::ImageAspectFlagBits::eDepth + ); + #ifdef DEBUG + std::cout << "Depth ressouce created !\n"; + #endif + } + + void createTextureImage(){ + #ifdef DEBUG + std::cout << "Creating a texture image...\n"; + #endif + int texWidth, texHeight, texChannels; + stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + vk::DeviceSize imageSize = texWidth * texHeight * 4; + + if (!pixels) { + throw std::runtime_error("Failed to load texture image!\nAborting..."); + } + // Temporary buffers + vk::raii::Buffer stagingBuffer( {} ); + vk::raii::DeviceMemory stagingBufferMemory( {} ); + createBuffer( + imageSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + stagingBuffer, + stagingBufferMemory + ); + + void* data = stagingBufferMemory.mapMemory(0, imageSize); + memcpy(data, pixels, imageSize); + stagingBufferMemory.unmapMemory(); + stbi_image_free(pixels); + + createImage( + texWidth, + texHeight, + vk::Format::eR8G8B8A8Srgb, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal, + textureImage, + textureImageMemory + ); + + transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + + #ifdef DEBUG + std::cout << "Texture image created !\n"; + #endif + } + + void createTextureImageView() { + #ifdef DEBUG + std::cout << "Creating the texture image...\n"; + #endif + textureImageView = createImageView( + textureImage, + vk::Format::eR8G8B8A8Srgb, + vk::ImageAspectFlagBits::eColor + ); + #ifdef DEBUG + std::cout << "Texture Image created !\n"; + #endif + } + + void createImage( + uint32_t width, + uint32_t height, + vk::Format format, + vk::ImageTiling tiling, + vk::ImageUsageFlags usage, + vk::MemoryPropertyFlags properties, + vk::raii::Image &image, + vk::raii::DeviceMemory &imageMemory + ) { + #ifdef DEBUG + std::cout << "Creating an image with usage " << vk::to_string(usage) << '\n'; + #endif + vk::ImageCreateInfo imageInfo { + .imageType = vk::ImageType::e2D, + .format = format, + .extent = { width, height, 1 }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive + }; + + image = vk::raii::Image(device, imageInfo); + + vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo { + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) + }; + + imageMemory = vk::raii::DeviceMemory(device, allocInfo); + image.bindMemory(imageMemory, 0); + #ifdef DEBUG + std::cout << "Created image with usage " << vk::to_string(usage) << '\n'; + #endif + } + + void createTextureSampler() { + #ifdef DEBUG + std::cout << "Creating a texture sampler...\n"; + #endif + vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); + vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::True, + .compareOp = vk::CompareOp::eAlways, + .minLod = 0.0f, + .maxLod = 0.0f, + .borderColor = vk::BorderColor::eIntOpaqueBlack, + .unnormalizedCoordinates = vk::False + }; + + textureSampler = vk::raii::Sampler(device, samplerInfo); + + #ifdef DEBUG + std::cout << "Texture sampler created !\n"; + #endif + } + + void createVertexBuffer() { + #ifdef DEBUG + std::cout << "Creating a vertex buffer...\n"; + #endif + vk::raii::Buffer stagingBuffer = nullptr; + vk::raii::DeviceMemory stagingBufferMemory = nullptr; + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + stagingBuffer, + stagingBufferMemory + ); + + void* dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(dataStaging, vertices.data(), (size_t)bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, + vk::MemoryPropertyFlagBits::eDeviceLocal, + vertexBuffer, + vertexBufferMemory + ); + copyBuffer(stagingBuffer, vertexBuffer, bufferSize); + #ifdef DEBUG + std::cout << "Vertex buffer created !\n"; + #endif + } + + void copyBuffer( + vk::raii::Buffer & srcBuffer, + vk::raii::Buffer & dstBuffer, + vk::DeviceSize size + ) { + #ifdef DEBUG + std::cout << "Copying buffer " << vk::to_string(srcBuffer.objectType) << " to " << vk::to_string(dstBuffer.objectType) << "...\n"; + #endif + vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); + + commandCopyBuffer.copyBuffer(srcBuffer, dstBuffer, vk::BufferCopy(0, 0, size)); + + endSingleTimeCommands(commandCopyBuffer); + + #ifdef DEBUG + std::cout << "Copied buffer " << vk::to_string(srcBuffer.objectType) << " to " << vk::to_string(dstBuffer.objectType) << "!\n"; + #endif + } + + void createIndexBuffer() { + #ifdef DEBUG + std::cout << "Creating an index buffer...\n"; + #endif + vk::DeviceSize bufferSize = sizeof( indices[0] ) * indices.size(); + + vk::raii::Buffer stagingBuffer( {} ); + vk::raii::DeviceMemory stagingBufferMemory( {} ); + createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + stagingBuffer, + stagingBufferMemory + ); + + void* data = stagingBufferMemory.mapMemory(0, bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); + stagingBufferMemory.unmapMemory(); + + createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, + vk::MemoryPropertyFlagBits::eDeviceLocal, + indexBuffer, + indexBufferMemory + ); + + copyBuffer(stagingBuffer, indexBuffer, bufferSize); + #ifdef DEBUG + std::cout << "Index buffer created !\n"; + #endif + } + + void createUniformBuffers() { + #ifdef DEBUG + std::cout << "Creating uniform buffers...\n"; + #endif + uniformBuffers.clear(); + uniformBuffersMemory.clear(); + uniformBuffersMapped.clear(); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + vk::raii::Buffer buffer( {} ); + vk::raii::DeviceMemory bufferMem( {} ); + createBuffer( + bufferSize, + vk::BufferUsageFlagBits::eUniformBuffer, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, + buffer, + bufferMem + ); + + uniformBuffers.emplace_back(std::move(buffer)); + uniformBuffersMemory.emplace_back(std::move(bufferMem)); + uniformBuffersMapped.emplace_back( uniformBuffersMemory[i].mapMemory(0, bufferSize)); + } + #ifdef DEBUG + std::cout << "Uniform buffers created !\n"; + #endif + } + + void createDescriptorPool() { + #ifdef DEBUG + std::cout << "Creating a descriptor pool...\n"; + #endif + std::array poolSize { + vk::DescriptorPoolSize( + vk::DescriptorType::eUniformBuffer, + MAX_FRAMES_IN_FLIGHT + ), + vk::DescriptorPoolSize( + vk::DescriptorType::eCombinedImageSampler, + MAX_FRAMES_IN_FLIGHT + ) + }; + vk::DescriptorPoolCreateInfo poolInfo { + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data() + }; + + descriptorPool = vk::raii::DescriptorPool(device, poolInfo); + + #ifdef DEBUG + std::cout << "Descriptor pool created !\n"; + #endif + } + + void createDescriptorSets() { + #ifdef DEBUG + std::cout << "Creating a descriptor set...\n"; + #endif + std::vector layouts( MAX_FRAMES_IN_FLIGHT, *descriptorSetLayout ); + vk::DescriptorSetAllocateInfo allocInfo { + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data() + }; + + descriptorSets.clear(); + descriptorSets = device.allocateDescriptorSets(allocInfo); + + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + vk::DescriptorBufferInfo bufferInfo { + .buffer = uniformBuffers[i], + .offset = 0, + .range = sizeof(UniformBufferObject) + }; + + vk::DescriptorImageInfo imageInfo { + .sampler = textureSampler, + .imageView = textureImageView, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal + }; + std::array descriptorWrites { + vk::WriteDescriptorSet { + .dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo + }, + vk::WriteDescriptorSet { + .dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo + } + }; + + device.updateDescriptorSets(descriptorWrites, {}); + } + #ifdef DEBUG + std::cout << "Descriptor sets created !\n"; + #endif + } + + void createCommandBuffers() { + #ifdef DEBUG + std::cout << "Creating a command buffers\n"; + #endif + + vk::CommandBufferAllocateInfo allocInfo{ + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = MAX_FRAMES_IN_FLIGHT + }; + commandBuffers = vk::raii::CommandBuffers(device, allocInfo); + + #ifdef DEBUG + std::cout << "Command buffer created\n"; + #endif + } + + void recordCommandBuffer(uint32_t imageIndex) { + auto &commandBuffer = commandBuffers[frameIndex]; + commandBuffer.begin( {} ); + + transition_image_layout( + swapChainImages[frameIndex], + vk::ImageLayout::eUndefined, + vk::ImageLayout::eColorAttachmentOptimal, + {}, + vk::AccessFlagBits2::eColorAttachmentWrite, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::ImageAspectFlagBits::eColor + ); + + transition_image_layout( + *depthImage, + vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests, + vk::ImageAspectFlagBits::eDepth + ); + + vk::ClearValue clearColor = vk::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f); + vk::ClearValue clearDepth = vk::ClearDepthStencilValue(1.0f, 0); + vk::RenderingAttachmentInfo attachementInfo = { + .imageView = swapChainImageViews[imageIndex], + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = clearColor + }; + + vk::RenderingAttachmentInfo depthAttachmentInfo = { + .imageView = depthImageView, + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eDontCare, + .clearValue = clearDepth + }; + + vk::RenderingInfo renderingInfo = { + .renderArea = { + .offset = { 0, 0 }, + .extent = swapChainExtent + }, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachementInfo, + .pDepthAttachment = &depthAttachmentInfo + }; + + commandBuffer.beginRendering(renderingInfo); + commandBuffer.bindPipeline( vk::PipelineBindPoint::eGraphics, graphicsPipeline ); + commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); + commandBuffer.bindIndexBuffer( *indexBuffer, 0, vk::IndexType::eUint16 ); + // Dynamic ViewPort + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0,0), swapChainExtent)); + // The actual rendering + commandBuffer.bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, + pipelineLayout, + 0, + *descriptorSets[frameIndex], + nullptr + ); + commandBuffer.drawIndexed( indices.size(), 1, 0, 0, 0); + commandBuffer.endRendering(); + transition_image_layout( + swapChainImages[imageIndex], + vk::ImageLayout::eColorAttachmentOptimal, + vk::ImageLayout::ePresentSrcKHR, + vk::AccessFlagBits2::eColorAttachmentWrite, + {}, + vk::PipelineStageFlagBits2::eColorAttachmentOutput, + vk::PipelineStageFlagBits2::eBottomOfPipe, + vk::ImageAspectFlagBits::eColor + ); + + commandBuffer.end(); + } + + void transition_image_layout ( + vk::Image image, + vk::ImageLayout oldLayout, + vk::ImageLayout newLayout, + vk::AccessFlags2 srcAccessMask, + vk::AccessFlags2 dstAccessMask, + vk::PipelineStageFlags2 srcStageMask, + vk::PipelineStageFlags2 dstStageMask, + vk::ImageAspectFlags image_aspect_flags + ){ + vk::ImageMemoryBarrier2 barrier = { + .srcStageMask = srcStageMask, + .srcAccessMask = srcAccessMask, + .dstStageMask = dstStageMask, + .dstAccessMask = dstAccessMask, + .oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = { + .aspectMask = image_aspect_flags, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1 + } + }; + + vk::DependencyInfo dependencyInfo = { + .dependencyFlags = {}, + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &barrier + }; + + commandBuffers[frameIndex].pipelineBarrier2(dependencyInfo); + } + + // Warning, the above function and the following have the same name, but not the same purpose, + // one is for the Pipeline (image displayed) and the other is for the texture image. + + void transitionImageLayout( + const vk::raii::Image& image, + vk::ImageLayout oldLayout, + vk::ImageLayout newLayout + ) { + auto commandBuffer = beginSingleTimeCommands(); + + vk::PipelineStageFlags sourceStage; + vk::PipelineStageFlags destinationStage; + + vk::ImageMemoryBarrier barrier { + .oldLayout = oldLayout, + .newLayout = newLayout, + .image = image, + .subresourceRange = { + vk::ImageAspectFlagBits::eColor, + 0, + 1, + 0, + 1 + } + }; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) { + barrier.srcAccessMask = {}; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + + sourceStage = vk::PipelineStageFlagBits::eTopOfPipe; + destinationStage = vk::PipelineStageFlagBits::eTransfer; + } else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + sourceStage = vk::PipelineStageFlagBits::eTransfer; + destinationStage = vk::PipelineStageFlagBits::eFragmentShader; + } else { + throw std::invalid_argument("Unsupported layout transition!"); + } + + commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); + + endSingleTimeCommands(commandBuffer); + } + + void copyBufferToImage( + const vk::raii::Buffer &buffer, + vk::raii::Image &image, + uint32_t width, + uint32_t height + ) { + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + + vk::BufferImageCopy region{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { + vk::ImageAspectFlagBits::eColor, + 0, + 0, + 1 + }, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1} + }; + commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); + endSingleTimeCommands(commandBuffer); + } + + void updateUniformBuffer(uint32_t currentImage) { + static auto startTime = std::chrono::high_resolution_clock::now(); + + auto currentTime = std::chrono::high_resolution_clock::now(); + float time = std::chrono::duration(currentTime - startTime).count(); + + UniformBufferObject ubo {}; + ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj[1][1] *= -1; + + memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); + } + + void drawFrame(){ + auto fenceResult = device.waitForFences(*inFlightFences[frameIndex], vk::True, UINT64_MAX); + if (fenceResult != vk::Result::eSuccess) { + throw std::runtime_error("Failed to wait for fence !\nAborting..."); + } + device.resetFences(*inFlightFences[frameIndex]); + + auto [result, imageIndex] = swapChain.acquireNextImage(UINT64_MAX, *presentCompleteSemaphores[frameIndex], nullptr); + + if (result == vk::Result::eErrorOutOfDateKHR) { + #ifdef DEBUG + std::cout << "SwapChain out of date ! \n"; + #endif + recreateSwapChain(); + #ifdef DEBUG + std::cout << "Fixed the swapchain !\n"; + #endif + return; + } else if (result != vk::Result::eSuccess && result != vk::Result::eSuboptimalKHR) { + assert(result == vk::Result::eTimeout || result == vk::Result::eNotReady); + throw std::runtime_error("Failed to acquire SwapChain image !\nAborting..."); + } + + device.resetFences(*inFlightFences[frameIndex]); + + commandBuffers[frameIndex].reset(); + recordCommandBuffer(imageIndex); + + vk::PipelineStageFlags waitDestinationStageMask( vk::PipelineStageFlagBits::eColorAttachmentOutput ); + const vk::SubmitInfo submitInfo { + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*presentCompleteSemaphores[frameIndex], + .pWaitDstStageMask = &waitDestinationStageMask, + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffers[frameIndex], + .signalSemaphoreCount = 1, + .pSignalSemaphores = &*renderFinishedSemaphores[imageIndex] + }; + updateUniformBuffer(frameIndex); + queue.submit(submitInfo, *inFlightFences[frameIndex]); + + const vk::PresentInfoKHR presentInfoKHR { + .waitSemaphoreCount = 1, + .pWaitSemaphores = &*renderFinishedSemaphores[imageIndex], + .swapchainCount = 1, + .pSwapchains = &*swapChain, + .pImageIndices = &imageIndex + }; + + result = queue.presentKHR(presentInfoKHR); + + if ((result == vk::Result::eSuboptimalKHR) || (result == vk::Result::eErrorOutOfDateKHR) || framebufferResized) { + framebufferResized = false; + recreateSwapChain(); + } else { + assert(result == vk::Result::eSuccess); + } + + frameIndex = (frameIndex + 1) % MAX_FRAMES_IN_FLIGHT; + } + + void createSyncObjects() { + assert(presentCompleteSemaphores.empty() && renderFinishedSemaphores.empty() && inFlightFences.empty()); + + vk::SemaphoreCreateInfo semaphoreInfo = {}; + vk::FenceCreateInfo fenceInfo = { + .flags = vk::FenceCreateFlagBits::eSignaled + }; + + for (size_t i = 0; i < swapChainImages.size(); i++) { + renderFinishedSemaphores.emplace_back(device, semaphoreInfo); + } + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + presentCompleteSemaphores.emplace_back(device, semaphoreInfo); + inFlightFences.emplace_back(device, fenceInfo); + } + } + + void recreateSwapChain() { + #ifdef DEBUG + std::cout << "Re-creating the SwapChain...\n"; + #endif + + int width { 0 }; + int height { 0 }; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + device.waitIdle(); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createDepthResources(); + + #ifdef DEBUG + std::cout << "SwapChain re-created sucessfully !\n"; + #endif + } + + void createBuffer( + vk::DeviceSize size, + vk::BufferUsageFlags usage, + vk::MemoryPropertyFlags properties, + vk::raii::Buffer& buffer, + vk::raii::DeviceMemory& bufferMemory + ) { + #ifdef DEBUG + std::cout << "Creating a buffer of type: " << vk::to_string(usage) << '\n'; + #endif + + vk::BufferCreateInfo bufferInfo { + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive + }; + buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo { + .allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties) + }; + + bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(*bufferMemory, 0); + + #ifdef DEBUG + std::cout << "Buffer of type: " << vk::to_string(usage) << " sucessfully created !\n"; + #endif + } + + static VKAPI_ATTR vk::Bool32 VKAPI_CALL debugCallback( + vk::DebugUtilsMessageSeverityFlagBitsEXT severity, + vk::DebugUtilsMessageTypeFlagsEXT type, + const vk::DebugUtilsMessengerCallbackDataEXT *pCallbackData, + void * + ){ + if (severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError || severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { + #ifdef DEBUG + std::cerr << "Validation layer: type " << to_string(type) << " msg: " << pCallbackData->pMessage << std::endl; + #endif + } + + return vk::False; + } + + static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); + app->framebufferResized = true; + } + + uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { + vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + throw std::runtime_error("Failed to find a suitable memory type !\nAborting..."); + } + + std::vector getRequiredExtensions() { + uint32_t glfwExtensionCount = 0; + auto glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + if (enableValidationLayers) { + extensions.push_back(vk::EXTDebugUtilsExtensionName); + } + + return extensions; + } + + uint32_t findQueueFamilies(vk::raii::PhysicalDevice physicalDevice) { + #ifdef DEBUG + std::cout << "Selecting a queue...\n"; + #endif + // Get the index of the first queue that support graphics + std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); + + for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) { + if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) && physicalDevice.getSurfaceSupportKHR(qfpIndex, *surface)) { + // The queue has both graphics and present + #ifdef DEBUG + std::cout << "Found a graphics and present queue !\n"; + #endif + queueIndex = qfpIndex; + return static_cast(qfpIndex); + } + } + + //return static_cast( std::distance( queueFamilyProperties.begin(), queueFamilyProperty ) ); + throw std::runtime_error("Failed to find a queue with graphics and present !\n Aborting...\n"); + } + + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return availableFormat; + } + } + return availableFormats[0]; + } + + vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) { + return availablePresentMode; + } + } + return vk::PresentModeKHR::eFifo; + } + + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + int width, height; + glfwGetFramebufferSize(window, &width, &height); + return { + std::clamp(width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width), + std::clamp(height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height) + }; + } + + static std::vector readFile(const std::string& filename) { + #ifdef DEBUG + std::cout << "Loading " << filename << " from disk...\n"; + #endif + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("Failed to open file !\n"); + } + + std::vector buffer(file.tellg()); + file.seekg(0, std::ios::beg); + file.read(buffer.data(), static_cast(buffer.size())); + file.close(); + #ifdef DEBUG + std::cout << "File loaded with size of " << buffer.size() << '\n'; + #endif + return buffer; + } + + [[nodiscard]] vk::raii::ShaderModule createShaderModule(const std::vector& code) const { + #ifdef DEBUG + std::cout << "Creating shader module\n"; + #endif + vk::ShaderModuleCreateInfo createInfo{ + .codeSize = code.size() * sizeof(char), + .pCode = reinterpret_cast(code.data()) + }; + vk::raii::ShaderModule shaderModule { device, createInfo }; + #ifdef DEBUG + std::cout << "Shader module created\n"; + #endif + return shaderModule; + } + + vk::raii::CommandBuffer beginSingleTimeCommands() { + vk::CommandBufferAllocateInfo allocInfo { + .commandPool = commandPool, + .level = vk::CommandBufferLevel::ePrimary, + .commandBufferCount = 1 + }; + + vk::raii::CommandBuffer commandBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); + + vk::CommandBufferBeginInfo beginInfo{ .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; + commandBuffer.begin(beginInfo); + + return commandBuffer; + } + + void endSingleTimeCommands(vk::raii::CommandBuffer& commandBuffer){ + commandBuffer.end(); + + vk::SubmitInfo submitInfo { + .commandBufferCount = 1, + .pCommandBuffers = &*commandBuffer + }; + queue.submit(submitInfo, nullptr); + queue.waitIdle(); + } + + vk::raii::ImageView createImageView( + vk::Image image, + vk::Format format, + vk::ImageAspectFlags aspectFlags + ) { + vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange = { + aspectFlags, + 0, + 1, + 0, + 1 + } + }; + + return vk::raii::ImageView( device, viewInfo ); + } + + vk::Format findSupportedFormat( + const std::vector& candidates, + vk::ImageTiling tiling, + vk::FormatFeatureFlags features + ){ + for (const auto format : candidates) { + vk::FormatProperties props = physicalDevice.getFormatProperties(format); + + if (tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & features) == features) { + return format; + } + if (tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & features) == features) { + return format; + } + } + + throw std::runtime_error("Failed to find supported format !"); + } + + vk::Format findDepthFormat() { + return findSupportedFormat( + { + vk::Format::eD32Sfloat, + vk::Format::eD32SfloatS8Uint, + vk::Format::eD24UnormS8Uint + }, + vk::ImageTiling::eOptimal, + vk::FormatFeatureFlagBits::eDepthStencilAttachment + ); + } + + bool hasStensilComponent(vk::Format format) { + return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint; + } + +}; + +int main() { + #ifdef DEBUG + std::cout << "Starting the app...\n"; + #endif + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what(); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/textures/texture.jpg b/textures/texture.jpg new file mode 100644 index 0000000..975e338 Binary files /dev/null and b/textures/texture.jpg differ