diff --git a/.gitignore b/.gitignore index 7ee4ff1903e902c4715c6e2b0c3e784ed5755aaf..76a0fa8e507371af6821b220a402666e79c340a3 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,8 @@ cmake-build-release/ # GUI configuration files imgui.ini + +# Generated source and header files for shaders +*.hxx +*.cxx + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 33b70018e368ecc3ad019ea33e57485814eb233a..63fda17212cf97f2857ac1891dfad9dd052cbe6a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,8 @@ variables: RUN: value: "all" - description: "The tests that should run. Possible values: ubuntu, win, all." - GIT_DEPTH: 1 + description: "The tests that should run. Possible values: ubuntu, win-msvc, win-mingw, mac, all." + GIT_DEPTH: 15 stages: - build @@ -17,13 +17,13 @@ build_ubuntu_gcc: - ubuntu-gcc-cached variables: GIT_SUBMODULE_STRATEGY: recursive - timeout: 10m + timeout: 15m retry: 1 script: - mkdir debug - cd debug - cmake -DCMAKE_BUILD_TYPE=Debug .. - - cmake --build . + - cmake --build . -j 4 artifacts: name: "Documentation - $CI_PIPELINE_ID" paths: @@ -34,13 +34,13 @@ build_ubuntu_gcc: build_win10_msvc: only: variables: - - $RUN =~ /\bwin.*/i || $RUN =~ /\ball.*/i + - $RUN =~ /\bwin-msvc.*/i || $RUN =~ /\ball.*/i stage: build tags: - win10-msvc-cached variables: GIT_SUBMODULE_STRATEGY: recursive - timeout: 10m + timeout: 15m retry: 0 script: - cd 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\' @@ -49,7 +49,43 @@ build_win10_msvc: - mkdir debug - cd debug - cmake -DCMAKE_BUILD_TYPE=Debug .. - - cmake --build . + - cmake --build . -j 4 + +build_win10_mingw: + only: + variables: + - $RUN =~ /\bwin-mingw.*/i || $RUN =~ /\ball.*/i + stage: build + tags: + - win10-mingw-cached + variables: + GIT_SUBMODULE_STRATEGY: recursive + timeout: 15m + retry: 0 + script: + - mkdir debug + - cd debug + - cmake --no-warn-unused-cli -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_C_COMPILER:FILEPATH=C:\msys64\mingw64\bin\x86_64-w64-mingw32-gcc.exe -DCMAKE_CXX_COMPILER:FILEPATH=C:\msys64\mingw64\bin\x86_64-w64-mingw32-g++.exe .. -G "Unix Makefiles" + - cmake --build . -j 4 + +build_mac_clang: + only: + variables: + - $RUN =~ /\bmac.*/i || $RUN =~ /\ball.*/i + stage: build + tags: + - catalina-clang-cached + variables: + GIT_SUBMODULE_STRATEGY: recursive + timeout: 15m + retry: 1 + script: + - mkdir debug + - cd debug + - export LDFLAGS="-L/usr/local/opt/llvm/lib" + - export CPPFLAGS="-I/usr/local/opt/llvm/include" + - cmake -DCMAKE_C_COMPILER="/usr/local/opt/llvm/bin/clang" -DCMAKE_CXX_COMPILER="/usr/local/opt/llvm/bin/clang++" -DCMAKE_BUILD_TYPE=Debug .. + - cmake --build . -j 4 deploy_doc_develop: only: diff --git a/.gitmodules b/.gitmodules index e0aaf2d17c340f98ae875f7e0f1238bfe04f7e5d..cc3bf1fcd2e1eb8117cbcc7222b04f7041fea520 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,9 @@ [submodule "modules/gui/lib/imgui"] path = modules/gui/lib/imgui url = https://github.com/ocornut/imgui.git +[submodule "lib/VulkanMemoryAllocator-Hpp"] + path = lib/VulkanMemoryAllocator-Hpp + url = https://github.com/malte-v/VulkanMemoryAllocator-Hpp.git +[submodule "modules/upscaling/lib/FidelityFX-FSR"] + path = modules/upscaling/lib/FidelityFX-FSR + url = https://github.com/GPUOpen-Effects/FidelityFX-FSR.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ae078a428a8e5e640ed8dc7bcc2f4e58e159c6b..dfafe1cd084d4b324c233d502e301c24a5ee95e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,8 @@ set(vkcv_flags ${CMAKE_CXX_FLAGS}) # enabling warnings in the debug build if (vkcv_build_debug) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - set(vkcv_flags ${vkcv_flags} " -Weverything") + #set(vkcv_flags ${vkcv_flags} " -Weverything") + set(vkcv_flags ${vkcv_flags} " -Wextra -Wall") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(vkcv_flags ${vkcv_flags} " -Wextra -Wall -pedantic") else() diff --git a/config/Libraries.cmake b/config/Libraries.cmake index ec014f84c820abf4988b070d5b733be08c377319..512669ce85a96f8cc94d8181994cfe458fa8b604 100644 --- a/config/Libraries.cmake +++ b/config/Libraries.cmake @@ -3,7 +3,13 @@ set(vkcv_config_lib ${vkcv_config}/lib) set(vkcv_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_lib}) if(NOT WIN32) - set(vkcv_libraries stdc++fs) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(vkcv_libraries stdc++fs) + endif() + + if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") + list(APPEND vkcv_flags -Xpreprocessor) + endif() # optimization for loading times list(APPEND vkcv_flags -pthread) @@ -19,6 +25,7 @@ set(vkcv_config_msg " - Library: ") include(${vkcv_config_lib}/GLFW.cmake) # glfw-x11 / glfw-wayland # libglfw3-dev include(${vkcv_config_lib}/Vulkan.cmake) # vulkan-intel / vulkan-radeon / nvidia # libvulkan-dev include(${vkcv_config_lib}/SPIRV_Cross.cmake) # SPIRV-Cross # libspirv_cross_c_shared +include(${vkcv_config_lib}/VulkanMemoryAllocator.cmake) # VulkanMemoryAllocator # cleanup of compiler flags if (vkcv_flags) @@ -33,6 +40,9 @@ endif () # fix dependencies for different Linux distros (looking at you Ubuntu) include(${vkcv_config_ext}/CheckLibraries.cmake) +# add custom function to include a file like a shader as string +include(${vkcv_config_ext}/IncludeShader.cmake) + # cleanup of compiler definitions aka preprocessor variables if (vkcv_definitions) list(REMOVE_DUPLICATES vkcv_definitions) diff --git a/config/Sources.cmake b/config/Sources.cmake index 54bb3485ed975669668d987787975f019aa6358b..41cd0c20f2106dc02700d9b23227f3e6c34a057a 100644 --- a/config/Sources.cmake +++ b/config/Sources.cmake @@ -6,6 +6,9 @@ set(vkcv_sources ${vkcv_include}/vkcv/Core.hpp ${vkcv_source}/vkcv/Core.cpp + + ${vkcv_include}/vkcv/File.hpp + ${vkcv_source}/vkcv/File.cpp ${vkcv_include}/vkcv/PassConfig.hpp ${vkcv_source}/vkcv/PassConfig.cpp @@ -21,6 +24,8 @@ set(vkcv_sources ${vkcv_include}/vkcv/Buffer.hpp + ${vkcv_include}/vkcv/PushConstants.hpp + ${vkcv_include}/vkcv/BufferManager.hpp ${vkcv_source}/vkcv/BufferManager.cpp diff --git a/config/ext/IncludeShader.cmake b/config/ext/IncludeShader.cmake new file mode 100644 index 0000000000000000000000000000000000000000..e67a8716fb32a953c93a3c6624f0d459a025e950 --- /dev/null +++ b/config/ext/IncludeShader.cmake @@ -0,0 +1,75 @@ + +function(include_shader shader include_dir source_dir) + if (NOT EXISTS ${shader}) + message(WARNING "Shader file does not exist: ${shader}") + else() + get_filename_component(filename ${shader} NAME) + file(SIZE ${shader} filesize) + + set(include_target_file ${include_dir}/${filename}.hxx) + set(source_target_file ${source_dir}/${filename}.cxx) + + if ((EXISTS ${source_target_file}) AND (EXISTS ${include_target_file})) + file(TIMESTAMP ${shader} shader_timestamp "%Y-%m-%dT%H:%M:%S") + file(TIMESTAMP ${source_target_file} source_timestamp "%Y-%m-%dT%H:%M:%S") + + string(COMPARE GREATER ${shader_timestamp} ${source_timestamp} shader_update) + else() + set(shader_update true) + endif() + + if (shader_update) + string(TOUPPER ${filename} varname) + string(REPLACE "." "_" varname ${varname}) + + set(shader_header "#pragma once\n") + string(APPEND shader_header "// This file is auto-generated via cmake, so don't touch it!\n") + string(APPEND shader_header "extern unsigned char ${varname} [${filesize}]\;\n") + string(APPEND shader_header "extern unsigned int ${varname}_LEN\;\n") + string(APPEND shader_header "const std::string ${varname}_SHADER (reinterpret_cast<const char*>(${varname}), ${varname}_LEN)\;") + + file(WRITE ${include_target_file} ${shader_header}) + + find_program(xxd_program "xxd") + + if (EXISTS ${xxd_program}) + get_filename_component(shader_directory ${shader} DIRECTORY) + + add_custom_command( + OUTPUT ${source_target_file} + WORKING_DIRECTORY "${shader_directory}" + COMMAND xxd -i -C "${filename}" "${source_target_file}" + COMMENT "Processing shader into source files: ${shader}" + ) + else() + set(shader_source "// This file is auto-generated via cmake, so don't touch it!\n") + string(APPEND shader_source "unsigned char ${varname}[] = {") + + math(EXPR max_fileoffset "${filesize} - 1" OUTPUT_FORMAT DECIMAL) + + message(STATUS "Processing shader into source files: ${shader}") + + foreach(fileoffset RANGE ${max_fileoffset}) + file(READ ${shader} shader_source_byte OFFSET ${fileoffset} LIMIT 1 HEX) + + math(EXPR offset_modulo "${fileoffset} % 12" OUTPUT_FORMAT DECIMAL) + + if (${offset_modulo} EQUAL 0) + string(APPEND shader_source "\n ") + endif() + + if (${fileoffset} LESS ${max_fileoffset}) + string(APPEND shader_source "0x${shader_source_byte}, ") + else() + string(APPEND shader_source "0x${shader_source_byte}\n") + endif() + endforeach() + + string(APPEND shader_source "}\;\n") + string(APPEND shader_source "unsigned int ${varname}_LEN = ${filesize}\;") + + file(WRITE ${source_target_file} ${shader_source}) + endif() + endif() + endif() +endfunction() diff --git a/config/lib/GLFW.cmake b/config/lib/GLFW.cmake index 1b68d8aa97ba59158a7bd805ab2470f554f705aa..9668694de5b6887c163f74b626c71873e3f611f8 100644 --- a/config/lib/GLFW.cmake +++ b/config/lib/GLFW.cmake @@ -10,6 +10,7 @@ else() add_subdirectory(${vkcv_lib}/glfw) list(APPEND vkcv_libraries glfw) + list(APPEND vkcv_includes ${vkcv_lib_path}/glfw/include) message(${vkcv_config_msg} " GLFW - " ${glfw3_VERSION}) else() diff --git a/config/lib/SPIRV_Cross.cmake b/config/lib/SPIRV_Cross.cmake index 2e705d7d5a006e3851d14d22a57fd667c61c79f5..00ae45527d0f49c5632d71e19509871d437ae691 100644 --- a/config/lib/SPIRV_Cross.cmake +++ b/config/lib/SPIRV_Cross.cmake @@ -25,6 +25,7 @@ else() add_subdirectory(${vkcv_lib}/SPIRV-Cross) list(APPEND vkcv_libraries spirv-cross-cpp) + list(APPEND vkcv_includes ${vkcv_lib_path}/SPIV-Cross/include) message(${vkcv_config_msg} " SPIRV Cross - " ${SPIRV_CROSS_VERSION}) else() diff --git a/config/lib/VulkanMemoryAllocator.cmake b/config/lib/VulkanMemoryAllocator.cmake new file mode 100644 index 0000000000000000000000000000000000000000..5f670ff04633e1747accb8f1598fee028a287168 --- /dev/null +++ b/config/lib/VulkanMemoryAllocator.cmake @@ -0,0 +1,22 @@ + +if (EXISTS "${vkcv_lib_path}/VulkanMemoryAllocator-Hpp") + set(VMA_HPP_PATH "${vkcv_lib_path}/VulkanMemoryAllocator-Hpp" CACHE INTERNAL "") + + set(VMA_RECORDING_ENABLED OFF CACHE INTERNAL "") + set(VMA_USE_STL_CONTAINERS OFF CACHE INTERNAL "") + set(VMA_STATIC_VULKAN_FUNCTIONS ON CACHE INTERNAL "") + set(VMA_DYNAMIC_VULKAN_FUNCTIONS OFF CACHE INTERNAL "") + set(VMA_DEBUG_ALWAYS_DEDICATED_MEMORY OFF CACHE INTERNAL "") + set(VMA_DEBUG_INITIALIZE_ALLOCATIONS OFF CACHE INTERNAL "") + set(VMA_DEBUG_GLOBAL_MUTEX OFF CACHE INTERNAL "") + set(VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT OFF CACHE INTERNAL "") + + add_subdirectory(${vkcv_config_lib}/vma) + + list(APPEND vkcv_libraries VulkanMemoryAllocator) + list(APPEND vkcv_includes ${vkcv_lib_path}/VulkanMemoryAllocator-Hpp) + + message(${vkcv_config_msg} " VMA - ") +else() + message(WARNING "VulkanMemoryAllocator is required..! Update the submodules!") +endif () diff --git a/config/lib/vma/CMakeLists.txt b/config/lib/vma/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..a2c018f2b4894e5ce8e2851ca10f981e2af36605 --- /dev/null +++ b/config/lib/vma/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.9) + +project(VulkanMemoryAllocator) + +find_package(Vulkan REQUIRED) + +option(VMA_HPP_PATH "Location of C++ headers" "") + +message(STATUS "VMA_BUILD_SAMPLE = ${VMA_BUILD_SAMPLE}") +message(STATUS "VMA_BUILD_SAMPLE_SHADERS = ${VMA_BUILD_SAMPLE_SHADERS}") +message(STATUS "VMA_BUILD_REPLAY = ${VMA_BUILD_REPLAY}") + +option(VMA_RECORDING_ENABLED "Enable VMA memory recording for debugging" OFF) +option(VMA_USE_STL_CONTAINERS "Use C++ STL containers instead of VMA's containers" OFF) +option(VMA_STATIC_VULKAN_FUNCTIONS "Link statically with Vulkan API" OFF) +option(VMA_DYNAMIC_VULKAN_FUNCTIONS "Fetch pointers to Vulkan functions internally (no static linking)" ON) +option(VMA_DEBUG_ALWAYS_DEDICATED_MEMORY "Every allocation will have its own memory block" OFF) +option(VMA_DEBUG_INITIALIZE_ALLOCATIONS "Automatically fill new allocations and destroyed allocations with some bit pattern" OFF) +option(VMA_DEBUG_GLOBAL_MUTEX "Enable single mutex protecting all entry calls to the library" OFF) +option(VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT "Never exceed VkPhysicalDeviceLimits::maxMemoryAllocationCount and return error" OFF) + +message(STATUS "VMA_RECORDING_ENABLED = ${VMA_RECORDING_ENABLED}") +message(STATUS "VMA_USE_STL_CONTAINERS = ${VMA_USE_STL_CONTAINERS}") +message(STATUS "VMA_DYNAMIC_VULKAN_FUNCTIONS = ${VMA_DYNAMIC_VULKAN_FUNCTIONS}") +message(STATUS "VMA_DEBUG_ALWAYS_DEDICATED_MEMORY = ${VMA_DEBUG_ALWAYS_DEDICATED_MEMORY}") +message(STATUS "VMA_DEBUG_INITIALIZE_ALLOCATIONS = ${VMA_DEBUG_INITIALIZE_ALLOCATIONS}") +message(STATUS "VMA_DEBUG_GLOBAL_MUTEX = ${VMA_DEBUG_GLOBAL_MUTEX}") +message(STATUS "VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT = ${VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT}") + +add_library(VulkanMemoryAllocator vma.cpp) + +set_target_properties( + VulkanMemoryAllocator PROPERTIES + + CXX_EXTENSIONS OFF + # Use C++14 + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON +) + +target_include_directories(VulkanMemoryAllocator PUBLIC ${VMA_HPP_PATH}) + +# Only link to Vulkan if static linking is used +if (NOT ${VMA_DYNAMIC_VULKAN_FUNCTIONS}) + target_link_libraries(VulkanMemoryAllocator PUBLIC Vulkan::Vulkan) +endif() + +target_compile_definitions( + VulkanMemoryAllocator + + PUBLIC + VMA_USE_STL_CONTAINERS=$<BOOL:${VMA_USE_STL_CONTAINERS}> + VMA_DYNAMIC_VULKAN_FUNCTIONS=$<BOOL:${VMA_DYNAMIC_VULKAN_FUNCTIONS}> + VMA_DEBUG_ALWAYS_DEDICATED_MEMORY=$<BOOL:${VMA_DEBUG_ALWAYS_DEDICATED_MEMORY}> + VMA_DEBUG_INITIALIZE_ALLOCATIONS=$<BOOL:${VMA_DEBUG_INITIALIZE_ALLOCATIONS}> + VMA_DEBUG_GLOBAL_MUTEX=$<BOOL:${VMA_DEBUG_GLOBAL_MUTEX}> + VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT=$<BOOL:${VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT}> + VMA_RECORDING_ENABLED=$<BOOL:${VMA_RECORDING_ENABLED}> +) diff --git a/config/lib/vma/vma.cpp b/config/lib/vma/vma.cpp new file mode 100644 index 0000000000000000000000000000000000000000..307c27f096bd1bae2b1deb2ca5994f132adc92cc --- /dev/null +++ b/config/lib/vma/vma.cpp @@ -0,0 +1,58 @@ + +#ifndef NDEBUG +#define _DEBUG +#endif + +#ifndef _MSVC_LANG +#ifdef __MINGW32__ +#include <stdint.h> +#include <stdlib.h> + +class VmaMutex { +public: + VmaMutex() : m_locked(false) {} + + void Lock() { + while (m_locked); + m_locked = true; + } + + void Unlock() { + m_locked = false; + } +private: + bool m_locked; +}; + +#define VMA_MUTEX VmaMutex + +template <typename T> +T* custom_overestimate_malloc(size_t size) { + return new T[size + (sizeof(T) - 1) / sizeof(T)]; +} + +void* custom_aligned_malloc(size_t alignment, size_t size) { + if (alignment > 4) { + return custom_overestimate_malloc<uint64_t>(size); + } else + if (alignment > 2) { + return custom_overestimate_malloc<uint32_t>(size); + } else + if (alignment > 1) { + return custom_overestimate_malloc<uint16_t>(size); + } else { + return custom_overestimate_malloc<uint8_t>(size); + } +} + +void custom_free(void *ptr) { + delete[] reinterpret_cast<uint8_t*>(ptr); +} + +#define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) (custom_aligned_malloc(alignment, size)) +#define VMA_SYSTEM_FREE(ptr) (custom_free(ptr)) +#endif +#endif + +#define VMA_IMPLEMENTATION +#include "vk_mem_alloc.hpp" diff --git a/include/vkcv/Buffer.hpp b/include/vkcv/Buffer.hpp index ae935ba9501d4d7776cad7e3ba190a2dd02e5e38..f5cd183d21c4ae9a5849ff09fc54af70667c12c6 100644 --- a/include/vkcv/Buffer.hpp +++ b/include/vkcv/Buffer.hpp @@ -76,8 +76,8 @@ namespace vkcv { {} [[nodiscard]] - static Buffer<T> create(BufferManager* manager, BufferType type, size_t count, BufferMemoryType memoryType) { - return Buffer<T>(manager, manager->createBuffer(type, count * sizeof(T), memoryType), type, count, memoryType); + static Buffer<T> create(BufferManager* manager, BufferType type, size_t count, BufferMemoryType memoryType, bool supportIndirect) { + return Buffer<T>(manager, manager->createBuffer(type, count * sizeof(T), memoryType, supportIndirect), type, count, memoryType); } }; diff --git a/include/vkcv/BufferManager.hpp b/include/vkcv/BufferManager.hpp index 9eb80d70862a79a01593e6fe4c3aabe98d253ac8..7bec33d8c4fa752be2487a849c16eaeeea0e6237 100644 --- a/include/vkcv/BufferManager.hpp +++ b/include/vkcv/BufferManager.hpp @@ -2,6 +2,7 @@ #include <vector> #include <vulkan/vulkan.hpp> +#include <vk_mem_alloc.hpp> #include "Handles.hpp" @@ -30,9 +31,8 @@ namespace vkcv struct Buffer { vk::Buffer m_handle; - vk::DeviceMemory m_memory; + vma::Allocation m_allocation; size_t m_size = 0; - void* m_mapped = nullptr; bool m_mappable = false; }; @@ -70,7 +70,7 @@ namespace vkcv * @param memoryType Type of buffers memory * @return New buffer handle */ - BufferHandle createBuffer(BufferType type, size_t size, BufferMemoryType memoryType); + BufferHandle createBuffer(BufferType type, size_t size, BufferMemoryType memoryType, bool supportIndirect); /** * Returns the Vulkan buffer handle of a buffer diff --git a/include/vkcv/Context.hpp b/include/vkcv/Context.hpp index 1c01a6134ba1642b3a130a7a4d3d299cc3f7b875..824713fd1e29cbb8b7e60b22768c0019daaa9938 100644 --- a/include/vkcv/Context.hpp +++ b/include/vkcv/Context.hpp @@ -1,8 +1,10 @@ #pragma once #include <vulkan/vulkan.hpp> +#include <vk_mem_alloc.hpp> #include "QueueManager.hpp" +#include "DrawcallRecording.hpp" namespace vkcv { @@ -32,12 +34,15 @@ namespace vkcv [[nodiscard]] const QueueManager& getQueueManager() const; + + [[nodiscard]] + const vma::Allocator& getAllocator() const; static Context create(const char *applicationName, uint32_t applicationVersion, - std::vector<vk::QueueFlagBits> queueFlags, - std::vector<const char *> instanceExtensions, - std::vector<const char *> deviceExtensions); + const std::vector<vk::QueueFlagBits>& queueFlags, + const std::vector<const char *>& instanceExtensions, + const std::vector<const char *>& deviceExtensions); private: /** @@ -47,11 +52,14 @@ namespace vkcv * @param physicalDevice Vulkan-PhysicalDevice * @param device Vulkan-Device */ - Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device, QueueManager&& queueManager) noexcept; + Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device, + QueueManager&& queueManager, vma::Allocator&& allocator) noexcept; vk::Instance m_Instance; vk::PhysicalDevice m_PhysicalDevice; vk::Device m_Device; QueueManager m_QueueManager; + vma::Allocator m_Allocator; + }; } diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp index cbbe1e908cdb74891ab9bfe4416c03e487e76b26..7b5c1d94a6519e626249d55c65da19b1e8f95044 100644 --- a/include/vkcv/Core.hpp +++ b/include/vkcv/Core.hpp @@ -138,9 +138,9 @@ namespace vkcv static Core create(Window &window, const char *applicationName, uint32_t applicationVersion, - std::vector<vk::QueueFlagBits> queueFlags = {}, - std::vector<const char*> instanceExtensions = {}, - std::vector<const char*> deviceExtensions = {}); + const std::vector<vk::QueueFlagBits>& queueFlags = {}, + const std::vector<const char*>& instanceExtensions = {}, + const std::vector<const char*>& deviceExtensions = {}); /** * Creates a basic vulkan graphics pipeline using @p config from the pipeline config class and returns it using the @p handle. @@ -163,7 +163,7 @@ namespace vkcv */ [[nodiscard]] PipelineHandle createComputePipeline( - const ShaderProgram &config, + const ShaderProgram &shaderProgram, const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts); /** @@ -185,8 +185,8 @@ namespace vkcv * return Buffer-Object */ template<typename T> - Buffer<T> createBuffer(vkcv::BufferType type, size_t count, BufferMemoryType memoryType = BufferMemoryType::DEVICE_LOCAL) { - return Buffer<T>::create(m_BufferManager.get(), type, count, memoryType); + Buffer<T> createBuffer(vkcv::BufferType type, size_t count, BufferMemoryType memoryType = BufferMemoryType::DEVICE_LOCAL, bool supportIndirect = false) { + return Buffer<T>::create(m_BufferManager.get(), type, count, memoryType, supportIndirect); } /** @@ -196,11 +196,13 @@ namespace vkcv * @param minFilter Minimizing filter * @param mipmapMode Mipmapping filter * @param addressMode Address mode + * @param mipLodBias Mip level of detail bias * @return Sampler handle */ [[nodiscard]] SamplerHandle createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter, - SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode); + SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode, + float mipLodBias = 0.0f); /** * Creates an #Image with a given format, width, height and depth. @@ -223,9 +225,13 @@ namespace vkcv Multisampling multisampling = Multisampling::None); [[nodiscard]] - const uint32_t getImageWidth(ImageHandle imageHandle); + uint32_t getImageWidth(const ImageHandle& image); + [[nodiscard]] - const uint32_t getImageHeight(ImageHandle imageHandle); + uint32_t getImageHeight(const ImageHandle& image); + + [[nodiscard]] + vk::Format getImageFormat(const ImageHandle& image); /** TODO: * @param setDescriptions @@ -243,19 +249,35 @@ namespace vkcv bool beginFrame(uint32_t& width, uint32_t& height); void recordDrawcallsToCmdStream( - const CommandStreamHandle cmdStreamHandle, + const CommandStreamHandle cmdStreamHandle, const PassHandle renderpassHandle, const PipelineHandle pipelineHandle, - const PushConstantData &pushConstantData, + const PushConstants &pushConstants, const std::vector<DrawcallInfo> &drawcalls, const std::vector<ImageHandle> &renderTargets); + void recordMeshShaderDrawcalls( + const CommandStreamHandle cmdStreamHandle, + const PassHandle renderpassHandle, + const PipelineHandle pipelineHandle, + const PushConstants& pushConstantData, + const std::vector<MeshShaderDrawcall>& drawcalls, + const std::vector<ImageHandle>& renderTargets); + void recordComputeDispatchToCmdStream( CommandStreamHandle cmdStream, PipelineHandle computePipeline, const uint32_t dispatchCount[3], const std::vector<DescriptorSetUsage> &descriptorSetUsages, - const PushConstantData& pushConstantData); + const PushConstants& pushConstants); + + void recordComputeIndirectDispatchToCmdStream( + const CommandStreamHandle cmdStream, + const PipelineHandle computePipeline, + const vkcv::BufferHandle buffer, + const size_t bufferArgOffset, + const std::vector<DescriptorSetUsage>& descriptorSetUsages, + const PushConstants& pushConstants); /** * @brief end recording and present image @@ -283,15 +305,21 @@ namespace vkcv const RecordCommandFunction &record, const FinishCommandFunction &finish); - void submitCommandStream(const CommandStreamHandle handle); - void prepareSwapchainImageForPresent(const CommandStreamHandle handle); - void prepareImageForSampling(const CommandStreamHandle cmdStream, const ImageHandle image); - void prepareImageForStorage(const CommandStreamHandle cmdStream, const ImageHandle image); - void recordImageMemoryBarrier(const CommandStreamHandle cmdStream, const ImageHandle image); - void recordBufferMemoryBarrier(const CommandStreamHandle cmdStream, const BufferHandle buffer); - void resolveMSAAImage(CommandStreamHandle cmdStream, ImageHandle src, ImageHandle dst); + void submitCommandStream(const CommandStreamHandle& handle); + void prepareSwapchainImageForPresent(const CommandStreamHandle& handle); + void prepareImageForSampling(const CommandStreamHandle& cmdStream, const ImageHandle& image); + void prepareImageForStorage(const CommandStreamHandle& cmdStream, const ImageHandle& image); + void recordImageMemoryBarrier(const CommandStreamHandle& cmdStream, const ImageHandle& image); + void recordBufferMemoryBarrier(const CommandStreamHandle& cmdStream, const BufferHandle& buffer); + void resolveMSAAImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst); + [[nodiscard]] vk::ImageView getSwapchainImageView() const; + + void recordMemoryBarrier(const CommandStreamHandle& cmdStream); + + void recordBlitImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst, + SamplerFilterType filterType); }; } diff --git a/include/vkcv/DescriptorConfig.hpp b/include/vkcv/DescriptorConfig.hpp index 7dbbc4ed4b5b2175bec3984e51570fa9f65acb39..f2a089fd624c02c57db4a65c4a101c4acff371b1 100644 --- a/include/vkcv/DescriptorConfig.hpp +++ b/include/vkcv/DescriptorConfig.hpp @@ -9,6 +9,7 @@ namespace vkcv { vk::DescriptorSet vulkanHandle; vk::DescriptorSetLayout layout; + size_t poolIndex; }; /* @@ -20,7 +21,9 @@ namespace vkcv STORAGE_BUFFER, SAMPLER, IMAGE_SAMPLED, - IMAGE_STORAGE + IMAGE_STORAGE, + UNIFORM_BUFFER_DYNAMIC, + STORAGE_BUFFER_DYNAMIC }; /* diff --git a/include/vkcv/DescriptorWrites.hpp b/include/vkcv/DescriptorWrites.hpp index f28a6c91e189b13413ffefec0f05e5a0a358ee26..28de2ed7fa6b7e71bfa49b67a337f80f2e05ddcf 100644 --- a/include/vkcv/DescriptorWrites.hpp +++ b/include/vkcv/DescriptorWrites.hpp @@ -20,16 +20,15 @@ namespace vkcv { uint32_t mipLevel; }; - struct UniformBufferDescriptorWrite { - inline UniformBufferDescriptorWrite(uint32_t binding, BufferHandle buffer) : binding(binding), buffer(buffer) {}; - uint32_t binding; - BufferHandle buffer; - }; - - struct StorageBufferDescriptorWrite { - inline StorageBufferDescriptorWrite(uint32_t binding, BufferHandle buffer) : binding(binding), buffer(buffer) {}; + struct BufferDescriptorWrite { + inline BufferDescriptorWrite(uint32_t binding, BufferHandle buffer, bool dynamic = false, + uint32_t offset = 0, uint32_t size = 0) : + binding(binding), buffer(buffer), dynamic(dynamic), offset(offset), size(size) {}; uint32_t binding; BufferHandle buffer; + bool dynamic; + uint32_t offset; + uint32_t size; }; struct SamplerDescriptorWrite { @@ -41,8 +40,8 @@ namespace vkcv { struct DescriptorWrites { std::vector<SampledImageDescriptorWrite> sampledImageWrites; std::vector<StorageImageDescriptorWrite> storageImageWrites; - std::vector<UniformBufferDescriptorWrite> uniformBufferWrites; - std::vector<StorageBufferDescriptorWrite> storageBufferWrites; + std::vector<BufferDescriptorWrite> uniformBufferWrites; + std::vector<BufferDescriptorWrite> storageBufferWrites; std::vector<SamplerDescriptorWrite> samplerWrites; }; } \ No newline at end of file diff --git a/include/vkcv/DrawcallRecording.hpp b/include/vkcv/DrawcallRecording.hpp index 9f162a499a38d5633703f70eec8a8682e3328d72..37cf02d9102fcab5abd10ada711f67b721bcb52b 100644 --- a/include/vkcv/DrawcallRecording.hpp +++ b/include/vkcv/DrawcallRecording.hpp @@ -2,6 +2,7 @@ #include <vulkan/vulkan.hpp> #include <vkcv/Handles.hpp> #include <vkcv/DescriptorConfig.hpp> +#include <vkcv/PushConstants.hpp> namespace vkcv { struct VertexBufferBinding { @@ -12,28 +13,41 @@ namespace vkcv { vk::Buffer buffer; }; + enum class IndexBitCount{ + Bit16, + Bit32 + }; + struct DescriptorSetUsage { - inline DescriptorSetUsage(uint32_t setLocation, vk::DescriptorSet vulkanHandle) noexcept - : setLocation(setLocation), vulkanHandle(vulkanHandle) {} + inline DescriptorSetUsage(uint32_t setLocation, vk::DescriptorSet vulkanHandle, + const std::vector<uint32_t>& dynamicOffsets = {}) noexcept + : setLocation(setLocation), vulkanHandle(vulkanHandle), dynamicOffsets(dynamicOffsets) {} - const uint32_t setLocation; - const vk::DescriptorSet vulkanHandle; + const uint32_t setLocation; + const vk::DescriptorSet vulkanHandle; + const std::vector<uint32_t> dynamicOffsets; }; struct Mesh { - inline Mesh(std::vector<VertexBufferBinding> vertexBufferBindings, vk::Buffer indexBuffer, size_t indexCount) noexcept - : vertexBufferBindings(vertexBufferBindings), indexBuffer(indexBuffer), indexCount(indexCount){} + + inline Mesh(){} + + inline Mesh( + std::vector<VertexBufferBinding> vertexBufferBindings, + vk::Buffer indexBuffer, + size_t indexCount, + IndexBitCount indexBitCount = IndexBitCount::Bit16) noexcept + : + vertexBufferBindings(vertexBufferBindings), + indexBuffer(indexBuffer), + indexCount(indexCount), + indexBitCount(indexBitCount) {} std::vector<VertexBufferBinding> vertexBufferBindings; vk::Buffer indexBuffer; size_t indexCount; - }; - - struct PushConstantData { - inline PushConstantData(void* data, size_t sizePerDrawcall) : data(data), sizePerDrawcall(sizePerDrawcall) {} + IndexBitCount indexBitCount; - void* data; - size_t sizePerDrawcall; }; struct DrawcallInfo { @@ -49,7 +63,24 @@ namespace vkcv { const DrawcallInfo &drawcall, vk::CommandBuffer cmdBuffer, vk::PipelineLayout pipelineLayout, - const PushConstantData &pushConstantData, + const PushConstants &pushConstants, const size_t drawcallIndex); -} \ No newline at end of file + void InitMeshShaderDrawFunctions(vk::Device device); + + struct MeshShaderDrawcall { + inline MeshShaderDrawcall(const std::vector<DescriptorSetUsage> descriptorSets, uint32_t taskCount) + : descriptorSets(descriptorSets), taskCount(taskCount) {} + + std::vector<DescriptorSetUsage> descriptorSets; + uint32_t taskCount; + }; + + void recordMeshShaderDrawcall( + vk::CommandBuffer cmdBuffer, + vk::PipelineLayout pipelineLayout, + const PushConstants& pushConstantData, + const uint32_t pushConstantOffset, + const MeshShaderDrawcall& drawcall, + const uint32_t firstTask); +} diff --git a/include/vkcv/Event.hpp b/include/vkcv/Event.hpp index da5cbc72fbb3eee3a71a35c1da6fe32dff06b057..604e3a444dc3bffd2841cb69cd99746d59af523d 100644 --- a/include/vkcv/Event.hpp +++ b/include/vkcv/Event.hpp @@ -1,7 +1,12 @@ #pragma once #include <functional> + +#ifndef __MINGW32__ #include <mutex> +#endif + +#include <vector> namespace vkcv { @@ -27,7 +32,10 @@ namespace vkcv { private: std::vector< event_function<T...> > m_functions; uint32_t m_id_counter; + +#ifndef __MINGW32__ std::mutex m_mutex; +#endif public: @@ -75,14 +83,18 @@ namespace vkcv { * locks the event so its function handles won't be called */ void lock() { +#ifndef __MINGW32__ m_mutex.lock(); +#endif } /** * unlocks the event so its function handles can be called after locking */ void unlock() { +#ifndef __MINGW32__ m_mutex.unlock(); +#endif } explicit event(bool locked = false) { diff --git a/include/vkcv/File.hpp b/include/vkcv/File.hpp new file mode 100644 index 0000000000000000000000000000000000000000..06f1c48593853147140b2c8c68c675d52c9dfaec --- /dev/null +++ b/include/vkcv/File.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include <filesystem> + +namespace vkcv { + + std::filesystem::path generateTemporaryFilePath(); + + std::filesystem::path generateTemporaryDirectoryPath(); + +} diff --git a/include/vkcv/Image.hpp b/include/vkcv/Image.hpp index 85ab2b81e2718b3890ba361c988d5db0e40e84c7..3fca76f70315c0e08e404d7acd8c2010a3501c24 100644 --- a/include/vkcv/Image.hpp +++ b/include/vkcv/Image.hpp @@ -31,21 +31,21 @@ namespace vkcv { uint32_t getDepth() const; [[nodiscard]] - vkcv::ImageHandle getHandle() const; + const vkcv::ImageHandle& getHandle() const; [[nodiscard]] uint32_t getMipCount() const; void switchLayout(vk::ImageLayout newLayout); - void fill(void* data, size_t size = SIZE_MAX); + void fill(const void* data, size_t size = SIZE_MAX); void generateMipChainImmediate(); void recordMipChainGeneration(const vkcv::CommandStreamHandle& cmdStream); private: // TODO: const qualifier removed, very hacky!!! // Else you cannot recreate an image. Pls fix. ImageManager* m_manager; - ImageHandle m_handle; + ImageHandle m_handle; Image(ImageManager* manager, const ImageHandle& handle); diff --git a/include/vkcv/Logger.hpp b/include/vkcv/Logger.hpp index d484711f642506926b1281a830fb2c9caf8240a2..bb60561e80baadfcac4956223d9313893547068f 100644 --- a/include/vkcv/Logger.hpp +++ b/include/vkcv/Logger.hpp @@ -5,6 +5,7 @@ namespace vkcv { enum class LogLevel { + RAW_INFO, INFO, WARNING, ERROR @@ -12,6 +13,7 @@ namespace vkcv { constexpr auto getLogOutput(LogLevel level) { switch (level) { + case LogLevel::RAW_INFO: case LogLevel::INFO: return stdout; default: @@ -21,6 +23,7 @@ namespace vkcv { constexpr const char* getLogName(LogLevel level) { switch (level) { + case LogLevel::RAW_INFO: case LogLevel::INFO: return "INFO"; case LogLevel::WARNING: @@ -41,24 +44,35 @@ namespace vkcv { #define __PRETTY_FUNCTION__ __FUNCSIG__ #endif -#define vkcv_log(level, ...) { \ - char output_message [ \ - VKCV_DEBUG_MESSAGE_LEN \ - ]; \ - snprintf( \ - output_message, \ - VKCV_DEBUG_MESSAGE_LEN, \ - __VA_ARGS__ \ - ); \ - fprintf( \ - getLogOutput(level), \ - "[%s]: %s [%s, line %d: %s]\n", \ - vkcv::getLogName(level), \ - output_message, \ - __FILE__, \ - __LINE__, \ - __PRETTY_FUNCTION__ \ - ); \ +#define vkcv_log(level, ...) { \ + char output_message [ \ + VKCV_DEBUG_MESSAGE_LEN \ + ]; \ + snprintf( \ + output_message, \ + VKCV_DEBUG_MESSAGE_LEN, \ + __VA_ARGS__ \ + ); \ + auto output = getLogOutput(level); \ + if (level != vkcv::LogLevel::RAW_INFO) { \ + fprintf( \ + output, \ + "[%s]: %s [%s, line %d: %s]\n", \ + vkcv::getLogName(level), \ + output_message, \ + __FILE__, \ + __LINE__, \ + __PRETTY_FUNCTION__ \ + ); \ + } else { \ + fprintf( \ + output, \ + "[%s]: %s\n", \ + vkcv::getLogName(level), \ + output_message \ + ); \ + } \ + fflush(output); \ } #else diff --git a/include/vkcv/PushConstants.hpp b/include/vkcv/PushConstants.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d974fbe6241daf948b13929305fb24aff5ec06f5 --- /dev/null +++ b/include/vkcv/PushConstants.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include <vector> +#include <vulkan/vulkan.hpp> + +#include "Logger.hpp" + +namespace vkcv { + + class PushConstants { + private: + std::vector<uint8_t> m_data; + size_t m_sizePerDrawcall; + + public: + template<typename T> + PushConstants() : PushConstants(sizeof(T)) {} + + explicit PushConstants(size_t sizePerDrawcall) : + m_data(), + m_sizePerDrawcall(sizePerDrawcall) {} + + PushConstants(const PushConstants& other) = default; + PushConstants(PushConstants&& other) = default; + + ~PushConstants() = default; + + PushConstants& operator=(const PushConstants& other) = default; + PushConstants& operator=(PushConstants&& other) = default; + + [[nodiscard]] + size_t getSizePerDrawcall() const { + return m_sizePerDrawcall; + } + + [[nodiscard]] + size_t getFullSize() const { + return m_data.size(); + } + + [[nodiscard]] + size_t getDrawcallCount() const { + return (m_data.size() / m_sizePerDrawcall); + } + + void clear() { + m_data.clear(); + } + + template<typename T = uint8_t> + bool appendDrawcall(const T& value) { + if (sizeof(T) != m_sizePerDrawcall) { + vkcv_log(LogLevel::WARNING, "Size (%lu) of value does not match the specified size per drawcall (%lu)", + sizeof(value), m_sizePerDrawcall); + return false; + } + + const size_t offset = m_data.size(); + m_data.resize(offset + sizeof(value)); + std::memcpy(m_data.data() + offset, &value, sizeof(value)); + return true; + } + + template<typename T = uint8_t> + T& getDrawcall(size_t index) { + const size_t offset = (index * m_sizePerDrawcall); + return *reinterpret_cast<T*>(m_data.data() + offset); + } + + template<typename T = uint8_t> + const T& getDrawcall(size_t index) const { + const size_t offset = (index * m_sizePerDrawcall); + return *reinterpret_cast<const T*>(m_data.data() + offset); + } + + [[nodiscard]] + const void* getDrawcallData(size_t index) const { + const size_t offset = (index * m_sizePerDrawcall); + return reinterpret_cast<const void*>(m_data.data() + offset); + } + + [[nodiscard]] + const void* getData() const { + if (m_data.empty()) { + return nullptr; + } else { + return m_data.data(); + } + } + + }; + +} diff --git a/include/vkcv/QueueManager.hpp b/include/vkcv/QueueManager.hpp index ac043b2d351014ea79fcae0d0fc439bb64a87b72..0919d20d8e07fee67ceb2f393c29b4a53c51b857 100644 --- a/include/vkcv/QueueManager.hpp +++ b/include/vkcv/QueueManager.hpp @@ -32,8 +32,8 @@ namespace vkcv { const std::vector<Queue> &getTransferQueues() const; static void queueCreateInfosQueueHandles(vk::PhysicalDevice &physicalDevice, - std::vector<float> &queuePriorities, - std::vector<vk::QueueFlagBits> &queueFlags, + const std::vector<float> &queuePriorities, + const std::vector<vk::QueueFlagBits> &queueFlags, std::vector<vk::DeviceQueueCreateInfo> &queueCreateInfos, std::vector<std::pair<int, int>> &queuePairsGraphics, std::vector<std::pair<int, int>> &queuePairsCompute, diff --git a/include/vkcv/ShaderStage.hpp b/include/vkcv/ShaderStage.hpp index d671b87b55ac7a5a8926e479c77fa991dd90c665..773c8ca34e17e45b81deeca1f38a2b1be8a6821b 100644 --- a/include/vkcv/ShaderStage.hpp +++ b/include/vkcv/ShaderStage.hpp @@ -3,7 +3,7 @@ #include <vulkan/vulkan.hpp> namespace vkcv { - + enum class ShaderStage : VkShaderStageFlags { VERTEX = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eVertex), TESS_CONTROL = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eTessellationControl), @@ -34,5 +34,4 @@ namespace vkcv { constexpr ShaderStages operator~(ShaderStage stage) noexcept { return ~(ShaderStages(stage)); } - } diff --git a/lib/VulkanMemoryAllocator-Hpp b/lib/VulkanMemoryAllocator-Hpp new file mode 160000 index 0000000000000000000000000000000000000000..3a61240a5354ce56c222969a69825aabb6ba0a21 --- /dev/null +++ b/lib/VulkanMemoryAllocator-Hpp @@ -0,0 +1 @@ +Subproject commit 3a61240a5354ce56c222969a69825aabb6ba0a21 diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 28b2184b2a83515a514f1428733bcf8cf1499633..4b576e7119ebe769eafd1b6abb033b4fb02a3ec1 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -1,8 +1,11 @@ # Add new modules here: add_subdirectory(asset_loader) -add_subdirectory(material) add_subdirectory(camera) add_subdirectory(gui) +add_subdirectory(material) +add_subdirectory(meshlet) +add_subdirectory(scene) add_subdirectory(shader_compiler) add_subdirectory(testing) +add_subdirectory(upscaling) diff --git a/modules/asset_loader/CMakeLists.txt b/modules/asset_loader/CMakeLists.txt index c5a1fd0eb9620d3a95af1c52a9e7456337047c7b..870c16279b1578224a966a4a123a465413333555 100644 --- a/modules/asset_loader/CMakeLists.txt +++ b/modules/asset_loader/CMakeLists.txt @@ -31,10 +31,10 @@ include(config/FX_GLTF.cmake) include(config/STB.cmake) # link the required libraries to the module -target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv) +target_link_libraries(vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv ${vkcv_libraries}) # including headers of dependencies and the VkCV framework -target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_loader_includes}) +target_include_directories(vkcv_asset_loader SYSTEM BEFORE PRIVATE ${vkcv_asset_loader_includes} ${vkcv_includes}) # add the own include directory for public headers target_include_directories(vkcv_asset_loader BEFORE PUBLIC ${vkcv_asset_loader_include}) diff --git a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp index 471870fb1e5af3d3c448a66611d9754db9597f85..25d7d5b36d00bad8587fbe082f8ebd041d84fde6 100644 --- a/modules/asset_loader/include/vkcv/asset/asset_loader.hpp +++ b/modules/asset_loader/include/vkcv/asset/asset_loader.hpp @@ -11,17 +11,7 @@ #include <cstdint> #include <filesystem> -/** These macros define limits of the following structs. Implementations can - * test against these limits when performing sanity checks. The main constraint - * expressed is that of the data type: Material indices are identified by a - * uint8_t in the VertexGroup struct, so there can't be more than UINT8_MAX - * materials in the mesh. Should these limits be too narrow, the data type has - * to be changed, but the current ones should be generous enough for most use - * cases. */ -#define MAX_MATERIALS_PER_MESH UINT8_MAX -#define MAX_VERTICES_PER_VERTEX_GROUP UINT32_MAX - -/** LOADING MESHES +/* LOADING MESHES * The description of meshes is a hierarchy of structures with the Mesh at the * top. * @@ -46,53 +36,89 @@ namespace vkcv::asset { -/** This enum matches modes in fx-gltf, the library returns a standard mode - * (TRIANGLES) if no mode is given in the file. */ +/** + * These return codes are limited to the asset loader module. If unified return + * codes are defined for the vkcv framework, these will be used instead. + */ +#define ASSET_ERROR 0 +#define ASSET_SUCCESS 1 + +/** + * This enum matches modes in fx-gltf, the library returns a standard mode + * (TRIANGLES) if no mode is given in the file. + */ enum class PrimitiveMode : uint8_t { - POINTS=0, LINES, LINELOOP, LINESTRIP, TRIANGLES, TRIANGLESTRIP, - TRIANGLEFAN + POINTS = 0, + LINES = 1, + LINELOOP = 2, + LINESTRIP = 3, + TRIANGLES = 4, + TRIANGLESTRIP = 5, + TRIANGLEFAN = 6 }; -/** The indices in the index buffer can be of different bit width. */ -enum class IndexType : uint8_t { UNDEFINED=0, UINT8=1, UINT16=2, UINT32=3 }; - -typedef struct { - // TODO define struct for samplers (low priority) - // NOTE: glTF defines samplers based on OpenGL, which can not be - // directly translated to Vulkan. Specifically, OpenGL (and glTF) - // define a different set of Min/Mag-filters than Vulkan. -} Sampler; - -/** struct for defining the loaded texture */ -typedef struct { - int sampler; // index into the sampler array of the Scene - uint8_t channels; // number of channels - uint16_t w, h; // width and height of the texture - std::vector<uint8_t> data; // binary data of the decoded texture -} Texture; +/** + * The indices in the index buffer can be of different bit width. + */ +enum class IndexType : uint8_t { + UNDEFINED=0, + UINT8=1, + UINT16=2, + UINT32=3 +}; -/** The asset loader module only supports the PBR-MetallicRoughness model for - * materials.*/ -typedef struct { - uint16_t textureMask; // bit mask with active texture targets - // Indices into the Array.textures array - int baseColor, metalRough, normal, occlusion, emissive; - // Scaling factors for each texture target - struct { float r, g, b, a; } baseColorFactor; - float metallicFactor, roughnessFactor; - float normalScale; - float occlusionStrength; - struct { float r, g, b; } emissiveFactor; -} Material; +/** + * This struct defines a sampler for a texture object. All values here can + * directly be passed to VkSamplerCreateInfo. + * NOTE that glTF defines samplers based on OpenGL, which can not be directly + * translated to Vulkan. The vkcv::asset::Sampler struct defined here adheres + * to the Vulkan spec, having alerady translated the flags from glTF to Vulkan. + * Since glTF does not specify border sampling for more than two dimensions, + * the addressModeW is hardcoded to a default: VK_SAMPLER_ADDRESS_MODE_REPEAT. + */ +struct Sampler { + int minFilter, magFilter; + int mipmapMode; + float minLOD, maxLOD; + int addressModeU, addressModeV, addressModeW; +}; -/** Flags for the bit-mask in the Material struct. To check if a material has a +/** + * This struct describes a (partially) loaded texture. + * The data member is not populated after calling probeScene() but only when + * calling loadMesh(), loadScene() or loadTexture(). Note that textures are + * currently always loaded with 4 channels as RGBA, even if the image has just + * RGB or is grayscale. In the case where the glTF-file does not provide a URI + * but references a buffer view for the raw data, the path member will be empty + * even though the rest is initialized properly. + * NOTE: Loading textures without URI is untested. + */ +struct Texture { + std::filesystem::path path; // URI to the encoded texture data + int sampler; // index into the sampler array of the Scene + + union { int width; int w; }; + union { int height; int h; }; + int channels; + + std::vector<uint8_t> data; // binary data of the decoded texture +}; + +/** + * Flags for the bit-mask in the Material struct. To check if a material has a * certain texture target, you can use the hasTexture() function below, passing - * the material struct and the enum. */ + * the material struct and the enum. + */ enum class PBRTextureTarget { - baseColor=1, metalRough=2, normal=4, occlusion=8, emissive=16 + baseColor=1, + metalRough=2, + normal=4, + occlusion=8, + emissive=16 }; -/** This macro translates the index of an enum in the defined order to an +/** + * This macro translates the index of an enum in the defined order to an * integer with a single bit set in the corresponding place. It is used for * working with the bitmask of texture targets ("textureMask") in the Material * struct: @@ -103,100 +129,196 @@ enum class PBRTextureTarget { * contact with bit-level operations. */ #define bitflag(ENUM) (0x1u << ((unsigned)(ENUM))) -/** To signal that a certain texture target is active in a Material struct, its - * bit is set in the textureMask. You can use this function to check that: - * Material mat = ...; - * if (materialHasTexture(&mat, baseColor)) {...} */ -bool materialHasTexture(const Material *const m, const PBRTextureTarget t); +/** + * The asset loader module only supports the PBR-MetallicRoughness model for + * materials. + */ +struct Material { + uint16_t textureMask; // bit mask with active texture targets + + // Indices into the Scene.textures vector + int baseColor, metalRough, normal, occlusion, emissive; + + // Scaling factors for each texture target + struct { float r, g, b, a; } baseColorFactor; + float metallicFactor, roughnessFactor; + float normalScale; + float occlusionStrength; + struct { float r, g, b; } emissiveFactor; + + /** + * To signal that a certain texture target is active in this Material + * struct, its bit is set in the textureMask. You can use this function + * to check that: + * if (myMaterial.hasTexture(baseColor)) {...} + * + * @param t The target to query for + * @return Boolean to signal whether the texture target is active in + * the material. + */ + bool hasTexture(PBRTextureTarget target) const; +}; -/** With these enums, 0 is reserved to signal uninitialized or invalid data. */ +/* With these enums, 0 is reserved to signal uninitialized or invalid data. */ enum class PrimitiveType : uint32_t { UNDEFINED = 0, POSITION = 1, NORMAL = 2, TEXCOORD_0 = 3, TEXCOORD_1 = 4, - TANGENT = 5 + TANGENT = 5, + COLOR_0 = 6, + COLOR_1 = 7, + JOINTS_0 = 8, + WEIGHTS_0 = 9 }; -/** These integer values are used the same way in OpenGL, Vulkan and glTF. This +/** + * These integer values are used the same way in OpenGL, Vulkan and glTF. This * enum is not needed for translation, it's only for the programmers * convenience (easier to read in if/switch statements etc). While this enum * exists in (almost) the same definition in the fx-gltf library, we want to - * avoid exposing that dependency, thus it is re-defined here. */ + * avoid exposing that dependency, thus it is re-defined here. + */ enum class ComponentType : uint16_t { - NONE = 0, INT8 = 5120, UINT8 = 5121, INT16 = 5122, UINT16 = 5123, - UINT32 = 5125, FLOAT32 = 5126 + NONE = 0, + INT8 = 5120, + UINT8 = 5121, + INT16 = 5122, + UINT16 = 5123, + UINT32 = 5125, + FLOAT32 = 5126 }; -/** This struct describes one vertex attribute of a vertex buffer. */ -typedef struct { +/** + * This struct describes one vertex attribute of a vertex buffer. + */ +struct VertexAttribute { PrimitiveType type; // POSITION, NORMAL, ... + uint32_t offset; // offset in bytes uint32_t length; // length of ... in bytes uint32_t stride; // stride in bytes - ComponentType componentType; // eg. 5126 for float - uint8_t componentCount; // eg. 3 for vec3 -} VertexAttribute; + + ComponentType componentType; // eg. 5126 for float + uint8_t componentCount; // eg. 3 for vec3 +}; -/** This struct represents one (possibly the only) part of a mesh. There is +/** + * This struct represents one (possibly the only) part of a mesh. There is * always one vertexBuffer and zero or one indexBuffer (indexed rendering is * common but not always used). If there is no index buffer, this is indicated * by indexBuffer.data being empty. Each vertex buffer can have one or more - * vertex attributes. */ -typedef struct { + * vertex attributes. + */ +struct VertexGroup { enum PrimitiveMode mode; // draw as points, lines or triangle? - size_t numIndices, numVertices; + size_t numIndices; + size_t numVertices; + struct { enum IndexType type; // data type of the indices std::vector<uint8_t> data; // binary data of the index buffer } indexBuffer; + struct { std::vector<uint8_t> data; // binary data of the vertex buffer std::vector<VertexAttribute> attributes; // description of one } vertexBuffer; + struct { float x, y, z; } min; // bounding box lower left struct { float x, y, z; } max; // bounding box upper right + int materialIndex; // index to one of the materials -} VertexGroup; +}; -/** This struct represents a single mesh as it was loaded from a glTF file. It +/** + * This struct represents a single mesh as it was loaded from a glTF file. It * consists of at least one VertexGroup, which then references other resources - * such as Materials. */ -typedef struct { + * such as Materials. + */ +struct Mesh { std::string name; std::array<float, 16> modelMatrix; std::vector<int> vertexGroups; -} Mesh; +}; -/** The scene struct is simply a collection of objects in the scene as well as +/** + * The scene struct is simply a collection of objects in the scene as well as * the resources used by those objects. - * For now the only type of object are the meshes and they are represented in a - * flat array. - * Note that parent-child relations are not yet possible. */ -typedef struct { + * Note that parent-child relations are not yet possible. + */ +struct Scene { std::vector<Mesh> meshes; std::vector<VertexGroup> vertexGroups; std::vector<Material> materials; std::vector<Texture> textures; std::vector<Sampler> samplers; -} Scene; + std::vector<std::string> uris; +}; /** - * Load every mesh from the glTF file, as well as materials and textures. + * Parse the given glTF file and create a shallow description of the content. + * Only the meta-data of the objects in the scene is loaded, not the binary + * content. The rationale is to provide a means of probing the content of a + * glTF file without the costly process of loading and decoding large amounts + * of data. The returned Scene struct can be used to search for specific meshes + * in the scene, that can then be loaded on their own using the loadMesh() + * function. Note that the Scene struct received as output argument will be + * overwritten by this function. + * After this function completes, the returned Scene struct is completely + * initialized and all information is final, except for the missing binary + * data. This means that indices to vectors will remain valid even when the + * shallow scene struct is filled with data by loadMesh(). + * Note that for URIs only (local) filesystem paths are supported, no + * URLs using network protocols etc. * - * @param path must be the path to a glTF or glb file. + * @param path must be the path to a glTF- or glb-file. + * @param scene is a reference to a Scene struct that will be filled with the + * meta-data of all objects described in the glTF file. + * @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS + */ +int probeScene(const std::filesystem::path &path, Scene &scene); + +/** + * This function loads a single mesh from the given file and adds it to the + * given scene. The scene must already be initialized (via probeScene()). + * The mesh_index refers to the Scenes meshes array and identifies the mesh to + * load. To find the mesh you want, iterate over the probed scene and check the + * meshes details (eg. name). + * Besides the mesh, this function will also add any associated data to the + * Scene struct such as Materials and Textures required by the Mesh. + * + * @param path must be the path to a glTF- or glb-file. + * @param scene is the scene struct to which the results will be written. + * @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS + */ +int loadMesh(Scene &scene, int mesh_index); + +/** + * Load every mesh from the glTF file, as well as materials, textures and other + * associated objects. + * + * @param path must be the path to a glTF- or glb-file. * @param scene is a reference to a Scene struct that will be filled with the * content of the glTF file being loaded. - * */ -int loadScene(const std::string &path, Scene &scene); - -struct TextureData { - int width; - int height; - int componentCount; - std::vector<char*> data; -}; -TextureData loadTexture(const std::filesystem::path& path); + * @return ASSET_ERROR on failure, otherwise ASSET_SUCCESS + */ +int loadScene(const std::filesystem::path &path, Scene &scene); + +/** + * Simply loads a single image at the given path and returns a Texture + * struct describing it. This is for special use cases only (eg. + * loading a font atlas) and not meant to be used for regular assets. + * The sampler is set to -1, signalling that this Texture was loaded + * outside the context of a glTF-file. + * If there was an error loading or decoding the image, the returned struct + * will be cleared to all 0 with path and data being empty; make sure to always + * check that !data.empty() before using the struct. + * + * @param path must be the path to an image file. + * @return Texture struct describing the loaded image. + */ +Texture loadTexture(const std::filesystem::path& path); -} +} // end namespace vkcv::asset diff --git a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp index e3d3072543bd33e1f5a67ae7dac61d229005947a..571d965a400de7197b6fb46f163c4099a5b353f1 100644 --- a/modules/asset_loader/src/vkcv/asset/asset_loader.cpp +++ b/modules/asset_loader/src/vkcv/asset/asset_loader.cpp @@ -1,387 +1,784 @@ #include "vkcv/asset/asset_loader.hpp" #include <iostream> #include <string.h> // memcpy(3) +#include <set> #include <stdlib.h> // calloc(3) +#include <vulkan/vulkan.hpp> #include <fx/gltf.h> #include <stb_image.h> #include <vkcv/Logger.hpp> #include <algorithm> + namespace vkcv::asset { -/** -* convert the accessor type from the fx-gltf library to an unsigned int -* @param type -* @return unsigned integer representation -*/ -// TODO Return proper error code (we need to define those as macros or enums, -// will discuss during the next core meeting if that should happen on the scope -// of the vkcv framework or just this module) -uint8_t convertTypeToInt(const fx::gltf::Accessor::Type type) { - switch (type) { - case fx::gltf::Accessor::Type::None : - return 0; - case fx::gltf::Accessor::Type::Scalar : - return 1; - case fx::gltf::Accessor::Type::Vec2 : - return 2; - case fx::gltf::Accessor::Type::Vec3 : - return 3; - case fx::gltf::Accessor::Type::Vec4 : - return 4; - default: return 10; // TODO add cases for matrices (or maybe change the type in the struct itself) + /** + * This function unrolls nested exceptions via recursion and prints them + * @param e The exception being thrown + * @param path The path to the file that was responsible for the exception + */ + static void recurseExceptionPrint(const std::exception& e, const std::string &path) { + vkcv_log(LogLevel::ERROR, "Loading file %s: %s", path.c_str(), e.what()); + + try { + std::rethrow_if_nested(e); + } catch (const std::exception& nested) { + recurseExceptionPrint(nested, path); + } } -} -/** - * This function unrolls nested exceptions via recursion and prints them - * @param e error code - * @param path path to file that is responsible for error - */ -void print_what (const std::exception& e, const std::string &path) { - vkcv_log(LogLevel::ERROR, "Loading file %s: %s", - path.c_str(), e.what()); + /** + * Returns the component count for an accessor type of the fx-gltf library. + * @param type The accessor type + * @return An unsigned integer count + */ + static uint32_t getComponentCount(const fx::gltf::Accessor::Type type) { + switch (type) { + case fx::gltf::Accessor::Type::Scalar: + return 1; + case fx::gltf::Accessor::Type::Vec2: + return 2; + case fx::gltf::Accessor::Type::Vec3: + return 3; + case fx::gltf::Accessor::Type::Vec4: + case fx::gltf::Accessor::Type::Mat2: + return 4; + case fx::gltf::Accessor::Type::Mat3: + return 9; + case fx::gltf::Accessor::Type::Mat4: + return 16; + case fx::gltf::Accessor::Type::None: + default: + return 0; + } + } - try { - std::rethrow_if_nested(e); - } catch (const std::exception& nested) { - print_what(nested, path); + static uint32_t getComponentSize(ComponentType type) { + switch (type) { + case ComponentType::INT8: + case ComponentType::UINT8: + return 1; + case ComponentType::INT16: + case ComponentType::UINT16: + return 2; + case ComponentType::UINT32: + case ComponentType::FLOAT32: + return 4; + default: + return 0; + } } -} -/** Translate the component type used in the index accessor of fx-gltf to our - * enum for index type. The reason we have defined an incompatible enum that - * needs translation is that only a subset of component types is valid for - * indices and we want to catch these incompatibilities here. */ -enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &t) -{ - switch (t) { - case fx::gltf::Accessor::ComponentType::UnsignedByte: - return IndexType::UINT8; - case fx::gltf::Accessor::ComponentType::UnsignedShort: - return IndexType::UINT16; - case fx::gltf::Accessor::ComponentType::UnsignedInt: - return IndexType::UINT32; - default: - vkcv_log(LogLevel::ERROR, "Index type not supported: %u", static_cast<uint16_t>(t)); - return IndexType::UNDEFINED; + /** + * Translate the component type used in the index accessor of fx-gltf to our + * enum for index type. The reason we have defined an incompatible enum that + * needs translation is that only a subset of component types is valid for + * indices and we want to catch these incompatibilities here. + * @param t The component type + * @return The vkcv::IndexType enum representation + */ + enum IndexType getIndexType(const enum fx::gltf::Accessor::ComponentType &type) { + switch (type) { + case fx::gltf::Accessor::ComponentType::UnsignedByte: + return IndexType::UINT8; + case fx::gltf::Accessor::ComponentType::UnsignedShort: + return IndexType::UINT16; + case fx::gltf::Accessor::ComponentType::UnsignedInt: + return IndexType::UINT32; + default: + vkcv_log(LogLevel::ERROR, "Index type not supported: %u", static_cast<uint16_t>(type)); + return IndexType::UNDEFINED; + } } -} - -/** - * This function computes the modelMatrix out of the data given in the gltf file. It also checks, whether a modelMatrix was given. - * @param translation possible translation vector (default 0,0,0) - * @param scale possible scale vector (default 1,1,1) - * @param rotation possible rotation, given in quaternion (default 0,0,0,1) - * @param matrix possible modelmatrix (default identity) - * @return model Matrix as an array of floats - */ -std::array<float, 16> computeModelMatrix(std::array<float, 3> translation, std::array<float, 3> scale, std::array<float, 4> rotation, std::array<float, 16> matrix){ - std::array<float, 16> modelMatrix = {1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1}; - if (matrix != modelMatrix){ - return matrix; - } else { - // translation - modelMatrix[3] = translation[0]; - modelMatrix[7] = translation[1]; - modelMatrix[11] = translation[2]; - // rotation and scale - auto a = rotation[0]; - auto q1 = rotation[1]; - auto q2 = rotation[2]; - auto q3 = rotation[3]; - - modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0]; - modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1]; - modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2]; - - modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0]; - modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1]; - modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2]; - - modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0]; - modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1]; - modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2]; - - // flip y, because GLTF uses y up, but vulkan -y up - modelMatrix[5] *= -1; - - return modelMatrix; - } - -} - -bool materialHasTexture(const Material *const m, const PBRTextureTarget t) -{ - return m->textureMask & bitflag(t); -} - -int loadScene(const std::string &path, Scene &scene){ - fx::gltf::Document sceneObjects; - - try { - if (path.rfind(".glb", (path.length()-4)) != std::string::npos) { - sceneObjects = fx::gltf::LoadFromBinary(path); - } else { - sceneObjects = fx::gltf::LoadFromText(path); - } - } catch (const std::system_error &err) { - print_what(err, path); - return 0; - } catch (const std::exception &e) { - print_what(e, path); - return 0; - } - size_t pos = path.find_last_of("/"); - auto dir = path.substr(0, pos); - - // file has to contain at least one mesh - if (sceneObjects.meshes.size() == 0) return 0; - - fx::gltf::Accessor posAccessor; - std::vector<VertexAttribute> vertexAttributes; - std::vector<Material> materials; - std::vector<Texture> textures; - std::vector<Sampler> samplers; - std::vector<Mesh> meshes; - std::vector<VertexGroup> vertexGroups; - int groupCount = 0; - - Mesh mesh = {}; - - for(int i = 0; i < sceneObjects.meshes.size(); i++){ - std::vector<int> vertexGroupsIndices; - fx::gltf::Mesh const &objectMesh = sceneObjects.meshes[i]; - - for(int j = 0; j < objectMesh.primitives.size(); j++){ - fx::gltf::Primitive const &objectPrimitive = objectMesh.primitives[j]; - vertexAttributes.clear(); - vertexAttributes.reserve(objectPrimitive.attributes.size()); - - for (auto const & attrib : objectPrimitive.attributes) { - - fx::gltf::Accessor accessor = sceneObjects.accessors[attrib.second]; - VertexAttribute attribute; - - if (attrib.first == "POSITION") { - attribute.type = PrimitiveType::POSITION; - posAccessor = accessor; - } else if (attrib.first == "NORMAL") { - attribute.type = PrimitiveType::NORMAL; - } else if (attrib.first == "TEXCOORD_0") { - attribute.type = PrimitiveType::TEXCOORD_0; - } - else if (attrib.first == "TEXCOORD_1") { - attribute.type = PrimitiveType::TEXCOORD_1; - } else if (attrib.first == "TANGENT") { - attribute.type = PrimitiveType::TANGENT; - } else { - return 0; - } - - attribute.offset = sceneObjects.bufferViews[accessor.bufferView].byteOffset; - attribute.length = sceneObjects.bufferViews[accessor.bufferView].byteLength; - attribute.stride = sceneObjects.bufferViews[accessor.bufferView].byteStride; - attribute.componentType = static_cast<ComponentType>(accessor.componentType); - - if (convertTypeToInt(accessor.type) != 10) { - attribute.componentCount = convertTypeToInt(accessor.type); - } else { - return 0; - } - - vertexAttributes.push_back(attribute); - } - - IndexType indexType; - std::vector<uint8_t> indexBufferData = {}; - if (objectPrimitive.indices >= 0){ // if there is no index buffer, -1 is returned from fx-gltf - const fx::gltf::Accessor &indexAccessor = sceneObjects.accessors[objectPrimitive.indices]; - const fx::gltf::BufferView &indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView]; - const fx::gltf::Buffer &indexBuffer = sceneObjects.buffers[indexBufferView.buffer]; - - indexBufferData.resize(indexBufferView.byteLength); - { - const size_t off = indexBufferView.byteOffset; - const void *const ptr = ((char*)indexBuffer.data.data()) + off; - if (!memcpy(indexBufferData.data(), ptr, indexBufferView.byteLength)) { - vkcv_log(LogLevel::ERROR, "Copying index buffer data"); - return 0; - } - } - - indexType = getIndexType(indexAccessor.componentType); - if (indexType == IndexType::UNDEFINED){ - vkcv_log(LogLevel::ERROR, "Index Type undefined."); - return 0; - } - } - - const fx::gltf::BufferView& vertexBufferView = sceneObjects.bufferViews[posAccessor.bufferView]; - const fx::gltf::Buffer& vertexBuffer = sceneObjects.buffers[vertexBufferView.buffer]; - - // only copy relevant part of vertex data - uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max(); - uint32_t relevantBufferEnd = 0; - for (const auto &attribute : vertexAttributes) { - relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset); - const uint32_t attributeEnd = attribute.offset + attribute.length; - relevantBufferEnd = std::max(relevantBufferEnd, attributeEnd); // TODO: need to incorporate stride? - } - const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset; - - // FIXME: This only works when all vertex attributes are in one buffer - std::vector<uint8_t> vertexBufferData; - vertexBufferData.resize(relevantBufferSize); - { - const void *const ptr = ((char*)vertexBuffer.data.data()) + relevantBufferOffset; - if (!memcpy(vertexBufferData.data(), ptr, relevantBufferSize)) { - vkcv_log(LogLevel::ERROR, "Copying vertex buffer data"); - return 0; - } - } - - // make vertex attributes relative to copied section - for (auto &attribute : vertexAttributes) { - attribute.offset -= relevantBufferOffset; - } - - const size_t numVertexGroups = objectMesh.primitives.size(); - vertexGroups.reserve(numVertexGroups); - - vertexGroups.push_back({ - static_cast<PrimitiveMode>(objectPrimitive.mode), - sceneObjects.accessors[objectPrimitive.indices].count, - posAccessor.count, - {indexType, indexBufferData}, - {vertexBufferData, vertexAttributes}, - {posAccessor.min[0], posAccessor.min[1], posAccessor.min[2]}, - {posAccessor.max[0], posAccessor.max[1], posAccessor.max[2]}, - static_cast<uint8_t>(objectPrimitive.material) - }); - - vertexGroupsIndices.push_back(groupCount); - groupCount++; - } - - mesh.name = sceneObjects.meshes[i].name; - mesh.vertexGroups = vertexGroupsIndices; - meshes.push_back(mesh); - } - - for(int m = 0; m < sceneObjects.nodes.size(); m++) { - meshes[sceneObjects.nodes[m].mesh].modelMatrix = computeModelMatrix(sceneObjects.nodes[m].translation, - sceneObjects.nodes[m].scale, - sceneObjects.nodes[m].rotation, - sceneObjects.nodes[m].matrix); - } - - if (sceneObjects.textures.size() > 0){ - textures.reserve(sceneObjects.textures.size()); - - for(int k = 0; k < sceneObjects.textures.size(); k++){ - const fx::gltf::Texture &tex = sceneObjects.textures[k]; - const fx::gltf::Image &img = sceneObjects.images[tex.source]; - std::string img_uri = dir + "/" + img.uri; - int w, h, c; - uint8_t *data = stbi_load(img_uri.c_str(), &w, &h, &c, 4); - c = 4; // FIXME hardcoded to always have RGBA channel layout - if (!data) { - vkcv_log(LogLevel::ERROR, "Loading texture image data.") - return 0; - } - const size_t byteLen = w * h * c; - - std::vector<uint8_t> imgdata; - imgdata.resize(byteLen); - if (!memcpy(imgdata.data(), data, byteLen)) { - vkcv_log(LogLevel::ERROR, "Copying texture image data") - free(data); - return 0; - } - free(data); + /** + * This function fills the array of vertex attributes of a VertexGroup (usually + * part of a vkcv::asset::Mesh) object based on the description of attributes + * for a fx::gltf::Primitive. + * + * @param src The description of attribute objects from the fx-gltf library + * @param gltf The main glTF document + * @param dst The array of vertex attributes stored in an asset::Mesh object + * @return ASSET_ERROR when at least one VertexAttribute could not be + * constructed properly, otherwise ASSET_SUCCESS + */ + static int loadVertexAttributes(const fx::gltf::Attributes &src, + const std::vector<fx::gltf::Accessor> &accessors, + const std::vector<fx::gltf::BufferView> &bufferViews, + std::vector<VertexAttribute> &dst) { + for (const auto &attrib : src) { + VertexAttribute att; + + if (attrib.first == "POSITION") { + att.type = PrimitiveType::POSITION; + } else if (attrib.first == "NORMAL") { + att.type = PrimitiveType::NORMAL; + } else if (attrib.first == "TANGENT") { + att.type = PrimitiveType::TANGENT; + } else if (attrib.first == "TEXCOORD_0") { + att.type = PrimitiveType::TEXCOORD_0; + } else if (attrib.first == "TEXCOORD_1") { + att.type = PrimitiveType::TEXCOORD_1; + } else if (attrib.first == "COLOR_0") { + att.type = PrimitiveType::COLOR_0; + } else if (attrib.first == "COLOR_1") { + att.type = PrimitiveType::COLOR_1; + } else if (attrib.first == "JOINTS_0") { + att.type = PrimitiveType::JOINTS_0; + } else if (attrib.first == "WEIGHTS_0") { + att.type = PrimitiveType::WEIGHTS_0; + } else { + att.type = PrimitiveType::UNDEFINED; + } + + if (att.type != PrimitiveType::UNDEFINED) { + const fx::gltf::Accessor &accessor = accessors[attrib.second]; + const fx::gltf::BufferView &buf = bufferViews[accessor.bufferView]; + + att.offset = buf.byteOffset; + att.length = buf.byteLength; + att.stride = buf.byteStride; + att.componentType = static_cast<ComponentType>(accessor.componentType); + att.componentCount = getComponentCount(accessor.type); + + /* Assume tightly packed stride as not explicitly provided */ + if (att.stride == 0) { + att.stride = att.componentCount * getComponentSize(att.componentType); + } + } + + if ((att.type == PrimitiveType::UNDEFINED) || + (att.componentCount == 0)) { + return ASSET_ERROR; + } + + dst.push_back(att); + } + + return ASSET_SUCCESS; + } - textures.push_back({ - 0, - static_cast<uint8_t>(c), - static_cast<uint16_t>(w), - static_cast<uint16_t>(h), - imgdata - }); + /** + * This function calculates the modelMatrix out of the data given in the gltf file. + * It also checks, whether a modelMatrix was given. + * + * @param translation possible translation vector (default 0,0,0) + * @param scale possible scale vector (default 1,1,1) + * @param rotation possible rotation, given in quaternion (default 0,0,0,1) + * @param matrix possible modelmatrix (default identity) + * @return model Matrix as an array of floats + */ + static std::array<float, 16> calculateModelMatrix(const std::array<float, 3>& translation, + const std::array<float, 3>& scale, + const std::array<float, 4>& rotation, + const std::array<float, 16>& matrix){ + std::array<float, 16> modelMatrix = { + 1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1 + }; + + if (matrix != modelMatrix){ + return matrix; + } else { + // translation + modelMatrix[3] = translation[0]; + modelMatrix[7] = translation[1]; + modelMatrix[11] = translation[2]; + + // rotation and scale + auto a = rotation[0]; + auto q1 = rotation[1]; + auto q2 = rotation[2]; + auto q3 = rotation[3]; + + modelMatrix[0] = (2 * (a * a + q1 * q1) - 1) * scale[0]; + modelMatrix[1] = (2 * (q1 * q2 - a * q3)) * scale[1]; + modelMatrix[2] = (2 * (q1 * q3 + a * q2)) * scale[2]; + + modelMatrix[4] = (2 * (q1 * q2 + a * q3)) * scale[0]; + modelMatrix[5] = (2 * (a * a + q2 * q2) - 1) * scale[1]; + modelMatrix[6] = (2 * (q2 * q3 - a * q1)) * scale[2]; + + modelMatrix[8] = (2 * (q1 * q3 - a * q2)) * scale[0]; + modelMatrix[9] = (2 * (q2 * q3 + a * q1)) * scale[1]; + modelMatrix[10] = (2 * (a * a + q3 * q3) - 1) * scale[2]; + + // flip y, because GLTF uses y up, but vulkan -y up + modelMatrix[5] *= -1; + + return modelMatrix; + } + } - } - } + bool Material::hasTexture(const PBRTextureTarget target) const { + return textureMask & bitflag(target); + } - if (sceneObjects.materials.size() > 0){ - materials.reserve(sceneObjects.materials.size()); + /** + * This function translates a given fx-gltf-sampler-wrapping-mode-enum to its vulkan sampler-adress-mode counterpart. + * @param mode: wrapping mode of a sampler given as fx-gltf-enum + * @return int vulkan-enum representing the same wrapping mode + */ + static int translateSamplerMode(const fx::gltf::Sampler::WrappingMode mode) { + switch (mode) { + case fx::gltf::Sampler::WrappingMode::ClampToEdge: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case fx::gltf::Sampler::WrappingMode::MirroredRepeat: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case fx::gltf::Sampler::WrappingMode::Repeat: + default: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + } + } - for (int l = 0; l < sceneObjects.materials.size(); l++){ - fx::gltf::Material material = sceneObjects.materials[l]; - // TODO I think we shouldn't set the index for a texture target if - // it isn't defined. So we need to test first if there is a normal - // texture before assigning material.normalTexture.index. - // About the bitmask: If a normal texture is there, modify the - // materials textureMask like this: - // mat.textureMask |= bitflag(asset::normal); - materials.push_back({ - 0, - material.pbrMetallicRoughness.baseColorTexture.index, - material.pbrMetallicRoughness.metallicRoughnessTexture.index, - material.normalTexture.index, - material.occlusionTexture.index, - material.emissiveTexture.index, - { - material.pbrMetallicRoughness.baseColorFactor[0], - material.pbrMetallicRoughness.baseColorFactor[1], - material.pbrMetallicRoughness.baseColorFactor[2], - material.pbrMetallicRoughness.baseColorFactor[3] - }, - material.pbrMetallicRoughness.metallicFactor, - material.pbrMetallicRoughness.roughnessFactor, - material.normalTexture.scale, - material.occlusionTexture.strength, - { - material.emissiveFactor[0], - material.emissiveFactor[1], - material.emissiveFactor[2] - } + /** + * If the glTF doesn't define samplers, we use the defaults defined by fx-gltf. + * The following are details about the glTF/OpenGL to Vulkan translation. + * magFilter (VkFilter?): + * GL_NEAREST -> VK_FILTER_NEAREST + * GL_LINEAR -> VK_FILTER_LINEAR + * minFilter (VkFilter?): + * mipmapMode (VkSamplerMipmapMode?): + * Vulkans minFilter and mipmapMode combined correspond to OpenGLs + * GL_minFilter_MIPMAP_mipmapMode: + * GL_NEAREST_MIPMAP_NEAREST: + * minFilter=VK_FILTER_NEAREST + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST + * GL_LINEAR_MIPMAP_NEAREST: + * minFilter=VK_FILTER_LINEAR + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST + * GL_NEAREST_MIPMAP_LINEAR: + * minFilter=VK_FILTER_NEAREST + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR + * GL_LINEAR_MIPMAP_LINEAR: + * minFilter=VK_FILTER_LINEAR + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_LINEAR + * The modes of GL_LINEAR and GL_NEAREST have to be emulated using + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST with specific minLOD and maxLOD: + * GL_LINEAR: + * minFilter=VK_FILTER_LINEAR + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST + * minLOD=0, maxLOD=0.25 + * GL_NEAREST: + * minFilter=VK_FILTER_NEAREST + * mipmapMode=VK_SAMPLER_MIPMAP_MODE_NEAREST + * minLOD=0, maxLOD=0.25 + * Setting maxLOD=0 causes magnification to always be performed (using + * the defined magFilter), this may be valid if the min- and magFilter + * are equal, otherwise it won't be the expected behaviour from OpenGL + * and glTF; instead using maxLod=0.25 allows the minFilter to be + * performed while still always rounding to the base level. + * With other modes, minLOD and maxLOD default to: + * minLOD=0 + * maxLOD=VK_LOD_CLAMP_NONE + * wrapping: + * gltf has wrapS, wrapT with {clampToEdge, MirroredRepeat, Repeat} while + * Vulkan has addressModeU, addressModeV, addressModeW with values + * VK_SAMPLER_ADDRESS_MODE_{REPEAT,MIRRORED_REPEAT,CLAMP_TO_EDGE, + * CAMP_TO_BORDER,MIRROR_CLAMP_TO_EDGE} + * Translation from glTF to Vulkan is straight forward for the 3 existing + * modes, default is repeat, the other modes aren't available. + */ + static vkcv::asset::Sampler loadSampler(const fx::gltf::Sampler &src) { + Sampler dst; + + dst.minLOD = 0; + dst.maxLOD = VK_LOD_CLAMP_NONE; + + switch (src.minFilter) { + case fx::gltf::Sampler::MinFilter::None: + case fx::gltf::Sampler::MinFilter::Nearest: + dst.minFilter = VK_FILTER_NEAREST; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + dst.maxLOD = 0.25; + break; + case fx::gltf::Sampler::MinFilter::Linear: + dst.minFilter = VK_FILTER_LINEAR; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + dst.maxLOD = 0.25; + break; + case fx::gltf::Sampler::MinFilter::NearestMipMapNearest: + dst.minFilter = VK_FILTER_NEAREST; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + break; + case fx::gltf::Sampler::MinFilter::LinearMipMapNearest: + dst.minFilter = VK_FILTER_LINEAR; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + break; + case fx::gltf::Sampler::MinFilter::NearestMipMapLinear: + dst.minFilter = VK_FILTER_NEAREST; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + break; + case fx::gltf::Sampler::MinFilter::LinearMipMapLinear: + dst.minFilter = VK_FILTER_LINEAR; + dst.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + break; + default: + break; + } + + switch (src.magFilter) { + case fx::gltf::Sampler::MagFilter::None: + case fx::gltf::Sampler::MagFilter::Nearest: + dst.magFilter = VK_FILTER_NEAREST; + break; + case fx::gltf::Sampler::MagFilter::Linear: + dst.magFilter = VK_FILTER_LINEAR; + break; + default: + break; + } + + dst.addressModeU = translateSamplerMode(src.wrapS); + dst.addressModeV = translateSamplerMode(src.wrapT); + + // There is no information about wrapping for a third axis in glTF and + // we have to hardcode this value. + dst.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + + return dst; + } - }); - } - } + /** + * Initializes vertex groups of a Mesh, including copying the data to + * index- and vertex-buffers. + */ + static int loadVertexGroups(const fx::gltf::Mesh &objectMesh, + const fx::gltf::Document &sceneObjects, + Scene &scene, Mesh &mesh) { + mesh.vertexGroups.reserve(objectMesh.primitives.size()); + + for (const auto &objectPrimitive : objectMesh.primitives) { + VertexGroup vertexGroup; + + vertexGroup.vertexBuffer.attributes.reserve( + objectPrimitive.attributes.size() + ); + + if (ASSET_SUCCESS != loadVertexAttributes( + objectPrimitive.attributes, + sceneObjects.accessors, + sceneObjects.bufferViews, + vertexGroup.vertexBuffer.attributes)) { + vkcv_log(LogLevel::ERROR, "Failed to get vertex attributes of '%s'", + mesh.name.c_str()); + return ASSET_ERROR; + } + + // The accessor for the position attribute is used for + // 1) getting the vertex buffer view which is only needed to get + // the vertex buffer + // 2) getting the vertex count for the VertexGroup + // 3) getting the min/max of the bounding box for the VertexGroup + fx::gltf::Accessor posAccessor; + bool noPosition = true; + + for (auto const& attrib : objectPrimitive.attributes) { + if (attrib.first == "POSITION") { + posAccessor = sceneObjects.accessors[attrib.second]; + noPosition = false; + break; + } + } + + if (noPosition) { + vkcv_log(LogLevel::ERROR, "Position attribute not found from '%s'", + mesh.name.c_str()); + return ASSET_ERROR; + } + + const fx::gltf::Accessor& indexAccessor = sceneObjects.accessors[objectPrimitive.indices]; + + int indexBufferURI; + if (objectPrimitive.indices >= 0) { // if there is no index buffer, -1 is returned from fx-gltf + const fx::gltf::BufferView& indexBufferView = sceneObjects.bufferViews[indexAccessor.bufferView]; + const fx::gltf::Buffer& indexBuffer = sceneObjects.buffers[indexBufferView.buffer]; + + // Because the buffers are already preloaded into the memory by the gltf-library, + // it makes no sense to load them later on manually again into memory. + vertexGroup.indexBuffer.data.resize(indexBufferView.byteLength); + memcpy(vertexGroup.indexBuffer.data.data(), + indexBuffer.data.data() + indexBufferView.byteOffset, + indexBufferView.byteLength); + } else { + indexBufferURI = -1; + } + + vertexGroup.indexBuffer.type = getIndexType(indexAccessor.componentType); + + if (IndexType::UNDEFINED == vertexGroup.indexBuffer.type) { + vkcv_log(LogLevel::ERROR, "Index Type undefined or not supported."); + return ASSET_ERROR; + } + + if (posAccessor.bufferView >= sceneObjects.bufferViews.size()) { + vkcv_log(LogLevel::ERROR, "Access to bufferView out of bounds: %lu", + posAccessor.bufferView); + return ASSET_ERROR; + } + const fx::gltf::BufferView& vertexBufferView = sceneObjects.bufferViews[posAccessor.bufferView]; + if (vertexBufferView.buffer >= sceneObjects.buffers.size()) { + vkcv_log(LogLevel::ERROR, "Access to buffer out of bounds: %lu", + vertexBufferView.buffer); + return ASSET_ERROR; + } + const fx::gltf::Buffer& vertexBuffer = sceneObjects.buffers[vertexBufferView.buffer]; + + // only copy relevant part of vertex data + uint32_t relevantBufferOffset = std::numeric_limits<uint32_t>::max(); + uint32_t relevantBufferEnd = 0; + + for (const auto& attribute : vertexGroup.vertexBuffer.attributes) { + relevantBufferOffset = std::min(attribute.offset, relevantBufferOffset); + relevantBufferEnd = std::max(relevantBufferEnd, attribute.offset + attribute.length); + } + + const uint32_t relevantBufferSize = relevantBufferEnd - relevantBufferOffset; + + vertexGroup.vertexBuffer.data.resize(relevantBufferSize); + memcpy(vertexGroup.vertexBuffer.data.data(), + vertexBuffer.data.data() + relevantBufferOffset, + relevantBufferSize); + + // make vertex attributes relative to copied section + for (auto& attribute : vertexGroup.vertexBuffer.attributes) { + attribute.offset -= relevantBufferOffset; + } + + vertexGroup.mode = static_cast<PrimitiveMode>(objectPrimitive.mode); + vertexGroup.numIndices = sceneObjects.accessors[objectPrimitive.indices].count; + vertexGroup.numVertices = posAccessor.count; + + memcpy(&(vertexGroup.min), posAccessor.min.data(), sizeof(vertexGroup.min)); + memcpy(&(vertexGroup.max), posAccessor.max.data(), sizeof(vertexGroup.max)); + + vertexGroup.materialIndex = static_cast<uint8_t>(objectPrimitive.material); + + mesh.vertexGroups.push_back(static_cast<int>(scene.vertexGroups.size())); + scene.vertexGroups.push_back(vertexGroup); + } + + return ASSET_SUCCESS; + } - scene = { - meshes, - vertexGroups, - materials, - textures, - samplers - }; + /** + * Returns an integer with specific bits set corresponding to the + * textures that appear in the given material. This mask is used in the + * vkcv::asset::Material struct and can be tested via the hasTexture + * method. + */ + static uint16_t generateTextureMask(fx::gltf::Material &material) { + uint16_t textureMask = 0; + + if (material.pbrMetallicRoughness.baseColorTexture.index >= 0) { + textureMask |= bitflag(asset::PBRTextureTarget::baseColor); + } + if (material.pbrMetallicRoughness.metallicRoughnessTexture.index >= 0) { + textureMask |= bitflag(asset::PBRTextureTarget::metalRough); + } + if (material.normalTexture.index >= 0) { + textureMask |= bitflag(asset::PBRTextureTarget::normal); + } + if (material.occlusionTexture.index >= 0) { + textureMask |= bitflag(asset::PBRTextureTarget::occlusion); + } + if (material.emissiveTexture.index >= 0) { + textureMask |= bitflag(asset::PBRTextureTarget::emissive); + } + + return textureMask; + } - return 1; -} + int probeScene(const std::filesystem::path& path, Scene& scene) { + fx::gltf::Document sceneObjects; + + try { + if (path.extension() == ".glb") { + sceneObjects = fx::gltf::LoadFromBinary(path.string()); + } else { + sceneObjects = fx::gltf::LoadFromText(path.string()); + } + } catch (const std::system_error& err) { + recurseExceptionPrint(err, path.string()); + return ASSET_ERROR; + } catch (const std::exception& e) { + recurseExceptionPrint(e, path.string()); + return ASSET_ERROR; + } + + const auto directory = path.parent_path(); + + scene.meshes.clear(); + scene.vertexGroups.clear(); + scene.materials.clear(); + scene.textures.clear(); + scene.samplers.clear(); + + // file has to contain at least one mesh + if (sceneObjects.meshes.empty()) { + vkcv_log(LogLevel::ERROR, "No meshes found! (%s)", path.c_str()); + return ASSET_ERROR; + } else { + scene.meshes.reserve(sceneObjects.meshes.size()); + + for (size_t i = 0; i < sceneObjects.meshes.size(); i++) { + Mesh mesh; + mesh.name = sceneObjects.meshes[i].name; + + if (loadVertexGroups(sceneObjects.meshes[i], sceneObjects, scene, mesh) != ASSET_SUCCESS) { + vkcv_log(LogLevel::ERROR, "Failed to load vertex groups of '%s'! (%s)", + mesh.name.c_str(), path.c_str()); + return ASSET_ERROR; + } + + scene.meshes.push_back(mesh); + } + + // This only works if the node has a mesh and it only loads the meshes and ignores cameras and lights + for (const auto& node : sceneObjects.nodes) { + if ((node.mesh >= 0) && (node.mesh < scene.meshes.size())) { + scene.meshes[node.mesh].modelMatrix = calculateModelMatrix( + node.translation, + node.scale, + node.rotation, + node.matrix + ); + } + } + } + + if (sceneObjects.samplers.empty()) { + vkcv_log(LogLevel::WARNING, "No samplers found! (%s)", path.c_str()); + } else { + scene.samplers.reserve(sceneObjects.samplers.size()); + + for (const auto &samplerObject : sceneObjects.samplers) { + scene.samplers.push_back(loadSampler(samplerObject)); + } + } + + if (sceneObjects.textures.empty()) { + vkcv_log(LogLevel::WARNING, "No textures found! (%s)", path.c_str()); + } else { + scene.textures.reserve(sceneObjects.textures.size()); + + for (const auto& textureObject : sceneObjects.textures) { + Texture texture; + + if (textureObject.sampler < 0) { + texture.sampler = -1; + } else + if (static_cast<size_t>(textureObject.sampler) >= scene.samplers.size()) { + vkcv_log(LogLevel::ERROR, "Sampler of texture '%s' missing (%s) %d", + textureObject.name.c_str(), path.c_str()); + return ASSET_ERROR; + } else { + texture.sampler = textureObject.sampler; + } + + if ((textureObject.source < 0) || + (static_cast<size_t>(textureObject.source) >= sceneObjects.images.size())) { + vkcv_log(LogLevel::ERROR, "Failed to load texture '%s' (%s)", + textureObject.name.c_str(), path.c_str()); + return ASSET_ERROR; + } + + const auto& image = sceneObjects.images[textureObject.source]; + + if (image.uri.empty()) { + const fx::gltf::BufferView bufferView = sceneObjects.bufferViews[image.bufferView]; + + texture.path.clear(); + texture.data.resize(bufferView.byteLength); + memcpy(texture.data.data(), + sceneObjects.buffers[bufferView.buffer].data.data() + bufferView.byteOffset, + bufferView.byteLength); + } else { + texture.path = directory / image.uri; + } + + scene.textures.push_back(texture); + } + } + + if (sceneObjects.materials.empty()) { + vkcv_log(LogLevel::WARNING, "No materials found! (%s)", path.c_str()); + } else { + scene.materials.reserve(sceneObjects.materials.size()); + + for (auto material : sceneObjects.materials) { + scene.materials.push_back({ + generateTextureMask(material), + material.pbrMetallicRoughness.baseColorTexture.index, + material.pbrMetallicRoughness.metallicRoughnessTexture.index, + material.normalTexture.index, + material.occlusionTexture.index, + material.emissiveTexture.index, + { + material.pbrMetallicRoughness.baseColorFactor[0], + material.pbrMetallicRoughness.baseColorFactor[1], + material.pbrMetallicRoughness.baseColorFactor[2], + material.pbrMetallicRoughness.baseColorFactor[3] + }, + material.pbrMetallicRoughness.metallicFactor, + material.pbrMetallicRoughness.roughnessFactor, + material.normalTexture.scale, + material.occlusionTexture.strength, + { + material.emissiveFactor[0], + material.emissiveFactor[1], + material.emissiveFactor[2] + } + }); + } + } + + return ASSET_SUCCESS; + } + + /** + * Loads and decodes the textures data based on the textures file path. + * The path member is the only one that has to be initialized before + * calling this function, the others (width, height, channels, data) + * are set by this function and the sampler is of no concern here. + */ + static int loadTextureData(Texture& texture) { + if ((texture.width > 0) && (texture.height > 0) && (texture.channels > 0) && + (!texture.data.empty())) { + return ASSET_SUCCESS; // Texture data was loaded already! + } + + uint8_t* data; + + if (texture.path.empty()) { + data = stbi_load_from_memory( + reinterpret_cast<uint8_t*>(texture.data.data()), + static_cast<int>(texture.data.size()), + &texture.width, + &texture.height, + &texture.channels, 4 + ); + } else { + data = stbi_load( + texture.path.string().c_str(), + &texture.width, + &texture.height, + &texture.channels, 4 + ); + } + + if (!data) { + vkcv_log(LogLevel::ERROR, "Texture could not be loaded from '%s'", + texture.path.c_str()); + + texture.width = 0; + texture.height = 0; + texture.channels = 0; + return ASSET_ERROR; + } + + texture.data.resize(texture.width * texture.height * 4); + memcpy(texture.data.data(), data, texture.data.size()); + stbi_image_free(data); + + return ASSET_SUCCESS; + } -TextureData loadTexture(const std::filesystem::path& path) { - TextureData texture; - - uint8_t* data = stbi_load(path.string().c_str(), &texture.width, &texture.height, &texture.componentCount, 4); - - if (!data) { - vkcv_log(LogLevel::ERROR, "Texture could not be loaded from '%s'", path.c_str()); - - texture.width = 0; - texture.height = 0; - texture.componentCount = 0; - return texture; - } - - texture.data.resize(texture.width * texture.height * 4); - memcpy(texture.data.data(), data, texture.data.size()); - return texture; -} + int loadMesh(Scene &scene, int index) { + if ((index < 0) || (static_cast<size_t>(index) >= scene.meshes.size())) { + vkcv_log(LogLevel::ERROR, "Mesh index out of range: %d", index); + return ASSET_ERROR; + } + + const Mesh &mesh = scene.meshes[index]; + + for (const auto& vg : mesh.vertexGroups) { + const VertexGroup &vertexGroup = scene.vertexGroups[vg]; + const Material& material = scene.materials[vertexGroup.materialIndex]; + + if (material.hasTexture(PBRTextureTarget::baseColor)) { + const int result = loadTextureData(scene.textures[material.baseColor]); + if (ASSET_SUCCESS != result) { + vkcv_log(LogLevel::ERROR, "Failed loading baseColor texture of mesh '%s'", + mesh.name.c_str()) + return result; + } + } + + if (material.hasTexture(PBRTextureTarget::metalRough)) { + const int result = loadTextureData(scene.textures[material.metalRough]); + if (ASSET_SUCCESS != result) { + vkcv_log(LogLevel::ERROR, "Failed loading metalRough texture of mesh '%s'", + mesh.name.c_str()) + return result; + } + } + + if (material.hasTexture(PBRTextureTarget::normal)) { + const int result = loadTextureData(scene.textures[material.normal]); + if (ASSET_SUCCESS != result) { + vkcv_log(LogLevel::ERROR, "Failed loading normal texture of mesh '%s'", + mesh.name.c_str()) + return result; + } + } + + if (material.hasTexture(PBRTextureTarget::occlusion)) { + const int result = loadTextureData(scene.textures[material.occlusion]); + if (ASSET_SUCCESS != result) { + vkcv_log(LogLevel::ERROR, "Failed loading occlusion texture of mesh '%s'", + mesh.name.c_str()) + return result; + } + } + + if (material.hasTexture(PBRTextureTarget::emissive)) { + const int result = loadTextureData(scene.textures[material.emissive]); + if (ASSET_SUCCESS != result) { + vkcv_log(LogLevel::ERROR, "Failed loading emissive texture of mesh '%s'", + mesh.name.c_str()) + return result; + } + } + } + + return ASSET_SUCCESS; + } + + int loadScene(const std::filesystem::path &path, Scene &scene) { + int result = probeScene(path, scene); + + if (result != ASSET_SUCCESS) { + vkcv_log(LogLevel::ERROR, "Loading scene failed '%s'", + path.c_str()); + return result; + } + + for (size_t i = 0; i < scene.meshes.size(); i++) { + result = loadMesh(scene, static_cast<int>(i)); + + if (result != ASSET_SUCCESS) { + vkcv_log(LogLevel::ERROR, "Loading mesh with index %d failed '%s'", + static_cast<int>(i), path.c_str()); + return result; + } + } + + return ASSET_SUCCESS; + } + + Texture loadTexture(const std::filesystem::path& path) { + Texture texture; + texture.path = path; + texture.sampler = -1; + if (loadTextureData(texture) != ASSET_SUCCESS) { + texture.path.clear(); + texture.w = texture.h = texture.channels = 0; + texture.data.clear(); + } + return texture; + } } diff --git a/modules/camera/config/GLM.cmake b/modules/camera/config/GLM.cmake index efd6444451100b912aa0b5b4a532dc8f448b0b40..f256ccade8c4f44a89744bb7875371324cf2369d 100644 --- a/modules/camera/config/GLM.cmake +++ b/modules/camera/config/GLM.cmake @@ -4,18 +4,20 @@ find_package(glm QUIET) if (glm_FOUND) list(APPEND vkcv_camera_includes ${GLM_INCLUDE_DIRS}) list(APPEND vkcv_camera_libraries glm) - - list(APPEND vkcv_camera_definitions GLM_DEPTH_ZERO_TO_ONE) - list(APPEND vkcv_camera_definitions GLM_FORCE_LEFT_HANDED) else() if (EXISTS "${vkcv_camera_lib_path}/glm") add_subdirectory(${vkcv_camera_lib}/glm) - + + list(APPEND vkcv_camera_includes ${vkcv_camera_lib_path}/glm) list(APPEND vkcv_camera_libraries glm) - - list(APPEND vkcv_camera_definitions GLM_DEPTH_ZERO_TO_ONE) - list(APPEND vkcv_camera_definitions GLM_FORCE_LEFT_HANDED) else() message(WARNING "GLM is required..! Update the submodules!") endif () endif () + +list(APPEND vkcv_camera_definitions GLM_DEPTH_ZERO_TO_ONE) +list(APPEND vkcv_camera_definitions GLM_FORCE_LEFT_HANDED) + +if ((WIN32) AND (${CMAKE_SIZEOF_VOID_P} MATCHES 4)) + list(APPEND vkcv_camera_definitions GLM_ENABLE_EXPERIMENTAL) +endif() diff --git a/modules/camera/include/vkcv/camera/Camera.hpp b/modules/camera/include/vkcv/camera/Camera.hpp index 9d85df7dce6d043630fd9d39287cace8530dbd6a..8a8c5df5d74cf1402bd8810172657ba77ddb2d56 100644 --- a/modules/camera/include/vkcv/camera/Camera.hpp +++ b/modules/camera/include/vkcv/camera/Camera.hpp @@ -3,6 +3,8 @@ #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_access.hpp> +#include <glm/vec3.hpp> +#include <glm/mat4x4.hpp> namespace vkcv::camera { @@ -20,9 +22,6 @@ namespace vkcv::camera { glm::vec3 m_up; glm::vec3 m_position; glm::vec3 m_center; - - float m_pitch; - float m_yaw; /** * @brief Sets the view matrix of the camera to @p view @@ -75,7 +74,7 @@ namespace vkcv::camera { * @brief Gets the current projection of the camera * @return The current projection matrix */ - glm::mat4 getProjection() const; + const glm::mat4& getProjection() const; /** * @brief Gets the model-view-projection matrix of the camera with y-axis-correction applied @@ -156,6 +155,20 @@ namespace vkcv::camera { * @param[in] center The new center point. */ void setCenter(const glm::vec3& center); + + /** + * @brief Gets the angles of the camera. + * @param[out] pitch The pitch value in radians + * @param[out] yaw The yaw value in radians + */ + void getAngles(float& pitch, float& yaw); + + /** + * @brief Sets the angles of the camera. + * @param pitch The new pitch value in radians + * @param yaw The new yaw value in radians + */ + void setAngles(float pitch, float yaw); /** * @brief Gets the pitch value of the camera in degrees. diff --git a/modules/camera/include/vkcv/camera/PilotCameraController.hpp b/modules/camera/include/vkcv/camera/PilotCameraController.hpp index 2b64cdc0dd3045714aba7b3b7c6241af2337c706..67388818a59b66775598e9d4257fa4c36646332a 100644 --- a/modules/camera/include/vkcv/camera/PilotCameraController.hpp +++ b/modules/camera/include/vkcv/camera/PilotCameraController.hpp @@ -29,42 +29,6 @@ namespace vkcv::camera { float m_fov_min; float m_fov_max; - /** - * @brief Indicates forward movement of the camera depending on the performed @p action. - * @param[in] action The performed action. - */ - void moveForward(int action); - - /** - * @brief Indicates backward movement of the camera depending on the performed @p action. - * @param[in] action The performed action. - */ - void moveBackward(int action); - - /** - * @brief Indicates left movement of the camera depending on the performed @p action. - * @param[in] action The performed action. - */ - void moveLeft(int action); - - /** - * @brief Indicates right movement of the camera depending on the performed @p action. - * @param[in] action The performed action. - */ - void moveRight(int action); - - /** - * @brief Indicates upward movement of the camera depending on the performed @p action. - * @param[in] action The performed action. - */ - void moveUpward(int action); - - /** - * @brief Indicates downward movement of the camera depending on the performed @p action. - * @param[in] action The performed action. - */ - void moveDownward(int action); - public: /** diff --git a/modules/camera/include/vkcv/camera/TrackballCameraController.hpp b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp index 4166bda9f6cb62e4c8f1b650557b00c6ec94b2a1..20336f7a10644c73e451c5106f37545e84eb27f7 100644 --- a/modules/camera/include/vkcv/camera/TrackballCameraController.hpp +++ b/modules/camera/include/vkcv/camera/TrackballCameraController.hpp @@ -14,6 +14,8 @@ namespace vkcv::camera { float m_cameraSpeed; float m_scrollSensitivity; float m_radius; + float m_pitch; + float m_yaw; /** * @brief Updates the current radius of @p camera in respect to the @p offset. diff --git a/modules/camera/src/vkcv/camera/Camera.cpp b/modules/camera/src/vkcv/camera/Camera.cpp index 3541b1a5bc1253c6b0f2b044d757341855a5e900..87d09aa9a6e3e7dc80d5de9a95f3e1e3b72e9205 100644 --- a/modules/camera/src/vkcv/camera/Camera.cpp +++ b/modules/camera/src/vkcv/camera/Camera.cpp @@ -10,8 +10,6 @@ namespace vkcv::camera { glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f) ); - - setFront(glm::normalize(m_center - m_position)); } Camera::~Camera() = default; @@ -44,20 +42,20 @@ namespace vkcv::camera { 0.0f, 0.0f, 0.0f, 1.0f ); - glm::mat4 Camera::getProjection() const { - return y_correction * m_projection; + const glm::mat4& Camera::getProjection() const { + return m_projection; } void Camera::setProjection(const glm::mat4& projection) { - m_projection = glm::inverse(y_correction) * projection; + m_projection = y_correction * projection; } glm::mat4 Camera::getMVP() const { - return y_correction * m_projection * m_view; + return m_projection * m_view; } float Camera::getFov() const { - const float tanHalfFovy = 1.0f / m_projection[1][1]; + const float tanHalfFovy = -1.0f / m_projection[1][1]; float halfFovy = std::atan(tanHalfFovy); if (halfFovy < 0) { @@ -73,7 +71,7 @@ namespace vkcv::camera { float Camera::getRatio() const { const float aspectProduct = 1.0f / m_projection[0][0]; - const float tanHalfFovy = 1.0f / m_projection[1][1]; + const float tanHalfFovy = -1.0f / m_projection[1][1]; return aspectProduct / tanHalfFovy; } @@ -93,16 +91,11 @@ namespace vkcv::camera { } glm::vec3 Camera::getFront() const { - glm::vec3 direction; - direction.x = std::sin(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch)); - direction.y = std::sin(glm::radians(m_pitch)); - direction.z = std::cos(glm::radians(m_yaw)) * std::cos(glm::radians(m_pitch)); - return glm::normalize(direction); + return glm::normalize(m_center - m_position); } void Camera::setFront(const glm::vec3 &front) { - m_pitch = std::atan2(front.y, std::sqrt(front.x * front.x + front.z * front.z)); - m_yaw = std::atan2(front.x, front.z); + setCenter(m_position + front); } const glm::vec3& Camera::getPosition() const { @@ -128,21 +121,47 @@ namespace vkcv::camera { void Camera::setUp(const glm::vec3 &up) { lookAt(m_position, m_center, up); } - - float Camera::getPitch() const { - return m_pitch; + + void Camera::getAngles(float& pitch, float& yaw) { + const auto front = getFront(); + + pitch = std::atan2(front[1], std::sqrt( + front[0] * front[0] + front[2] * front[2] + )); + + yaw = std::atan2(front[0], front[2]); + } + + void Camera::setAngles(float pitch, float yaw) { + float cosPitch = std::cos(pitch); + + setFront(glm::vec3( + std::sin(yaw) * cosPitch, + std::sin(pitch), + std::cos(yaw) * cosPitch + )); + } + + float Camera::getPitch() const { + const auto front = getFront(); + + return glm::degrees(std::atan2(front[1], std::sqrt( + front[0] * front[0] + front[2] * front[2] + ))); } void Camera::setPitch(float pitch) { - m_pitch = pitch; + setAngles(glm::radians(pitch), glm::radians(getYaw())); } float Camera::getYaw() const { - return m_yaw; + const auto front = getFront(); + + return glm::degrees(std::atan2(front[0], front[2])); } void Camera::setYaw(float yaw) { - m_yaw = yaw; + setAngles(glm::radians(getPitch()), glm::radians(yaw)); } } \ No newline at end of file diff --git a/modules/camera/src/vkcv/camera/CameraManager.cpp b/modules/camera/src/vkcv/camera/CameraManager.cpp index f129f3a248325957cb56470e2547a0146bc7c971..c8aa4f7e0e493a2aaf5bfd6d93768e169cd255b9 100644 --- a/modules/camera/src/vkcv/camera/CameraManager.cpp +++ b/modules/camera/src/vkcv/camera/CameraManager.cpp @@ -52,8 +52,8 @@ namespace vkcv::camera { } void CameraManager::mouseMoveCallback(double x, double y){ - auto xoffset = static_cast<float>(x - m_lastX); - auto yoffset = static_cast<float>(y - m_lastY); + auto xoffset = static_cast<float>(x - m_lastX) / m_window.getWidth(); + auto yoffset = static_cast<float>(y - m_lastY) / m_window.getHeight(); m_lastX = x; m_lastY = y; getActiveController().mouseMoveCallback(xoffset, yoffset, getActiveCamera()); diff --git a/modules/camera/src/vkcv/camera/PilotCameraController.cpp b/modules/camera/src/vkcv/camera/PilotCameraController.cpp index 5460858ab48d81252787b3c0141dd72982faca7d..1c7bb12679e57c9221465452f2fc41a539b6b2a0 100644 --- a/modules/camera/src/vkcv/camera/PilotCameraController.cpp +++ b/modules/camera/src/vkcv/camera/PilotCameraController.cpp @@ -50,12 +50,11 @@ namespace vkcv::camera { } // handle yaw rotation - float yaw = camera.getYaw() + static_cast<float>(xOffset); - yaw += 360.0f * (yaw < -180.0f) - 360.0f * (yaw > 180.0f); + float yaw = camera.getYaw() + static_cast<float>(xOffset) * 90.0f * m_cameraSpeed; camera.setYaw(yaw); // handle pitch rotation - float pitch = camera.getPitch() - static_cast<float>(yOffset); + float pitch = camera.getPitch() - static_cast<float>(yOffset) * 90.0f * m_cameraSpeed; pitch = glm::clamp(pitch, -89.0f, 89.0f); camera.setPitch(pitch); } @@ -83,22 +82,22 @@ namespace vkcv::camera { void PilotCameraController::keyCallback(int key, int scancode, int action, int mods, Camera &camera) { switch (key) { case GLFW_KEY_W: - moveForward(action); + m_forward = static_cast<bool>(action); break; case GLFW_KEY_S: - moveBackward(action); + m_backward = static_cast<bool>(action); break; case GLFW_KEY_A: - moveLeft(action); + m_left = static_cast<bool>(action); break; case GLFW_KEY_D: - moveRight(action); + m_right = static_cast<bool>(action); break; case GLFW_KEY_E: - moveUpward(action); + m_upward = static_cast<bool>(action); break; case GLFW_KEY_Q: - moveDownward(action); + m_downward = static_cast<bool>(action); break; default: break; @@ -110,31 +109,25 @@ namespace vkcv::camera { } void PilotCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) { - if(!m_rotationActive){ - return; - } - - float sensitivity = 0.05f; - xoffset *= sensitivity; - yoffset *= sensitivity; + xoffset *= static_cast<float>(m_rotationActive); + yoffset *= static_cast<float>(m_rotationActive); panView(xoffset , yoffset, camera); } void PilotCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) { - if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == false && action == GLFW_PRESS){ - m_rotationActive = true; - } - else if(button == GLFW_MOUSE_BUTTON_2 && m_rotationActive == true && action == GLFW_RELEASE){ - m_rotationActive = false; - } + if (button == GLFW_MOUSE_BUTTON_2) { + if (m_rotationActive != (action == GLFW_PRESS)) { + m_rotationActive = (action == GLFW_PRESS); + } + } } void PilotCameraController::gamepadCallback(int gamepadIndex, Camera &camera, double frametime) { GLFWgamepadstate gamepadState; glfwGetGamepadState(gamepadIndex, &gamepadState); - float sensitivity = 100.0f; + float sensitivity = 1.0f; double threshold = 0.1; // handle rotations @@ -163,29 +156,4 @@ namespace vkcv::camera { * -copysign(1.0, stickLeftX); } - - void PilotCameraController::moveForward(int action){ - m_forward = static_cast<bool>(action); - } - - void PilotCameraController::moveBackward(int action){ - m_backward = static_cast<bool>(action); - } - - void PilotCameraController::moveLeft(int action){ - m_left = static_cast<bool>(action); - } - - void PilotCameraController::moveRight(int action){ - m_right = static_cast<bool>(action); - } - - void PilotCameraController::moveUpward(int action){ - m_upward = static_cast<bool>(action); - } - - void PilotCameraController::moveDownward(int action){ - m_downward = static_cast<bool>(action); - } - } \ No newline at end of file diff --git a/modules/camera/src/vkcv/camera/TrackballCameraController.cpp b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp index cdd66cdb7fdd650d5112fe7bb4738f1fcded7783..8de2beb87d8f29415db611bfe0d17c5efd57a2a3 100644 --- a/modules/camera/src/vkcv/camera/TrackballCameraController.cpp +++ b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp @@ -8,6 +8,8 @@ namespace vkcv::camera { m_radius = 3.0f; m_cameraSpeed = 2.5f; m_scrollSensitivity = 0.2f; + m_pitch = 0.0f; + m_yaw = 0.0f; } void TrackballCameraController::setRadius(const float radius) { @@ -21,14 +23,10 @@ namespace vkcv::camera { } // handle yaw rotation - float yaw = camera.getYaw() + static_cast<float>(xOffset) * m_cameraSpeed; - yaw += 360.0f * (yaw < 0.0f) - 360.0f * (yaw > 360.0f); - camera.setYaw(yaw); + m_yaw = m_yaw + static_cast<float>(xOffset) * 90.0f * m_cameraSpeed; // handle pitch rotation - float pitch = camera.getPitch() + static_cast<float>(yOffset) * m_cameraSpeed; - pitch += 360.0f * (pitch < 0.0f) - 360.0f * (pitch > 360.0f); - camera.setPitch(pitch); + m_pitch = m_pitch + static_cast<float>(yOffset) * 90.0f * m_cameraSpeed; } void TrackballCameraController::updateRadius(double offset, Camera &camera) { @@ -44,14 +42,11 @@ namespace vkcv::camera { } void TrackballCameraController::updateCamera(double deltaTime, Camera &camera) { - float yaw = camera.getYaw(); - float pitch = camera.getPitch(); - const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f); const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f); - const glm::mat4 rotationY = glm::rotate(glm::mat4(1.0f), glm::radians(yaw), yAxis); - const glm::mat4 rotationX = glm::rotate(rotationY, -glm::radians(pitch), xAxis); + const glm::mat4 rotationY = glm::rotate(glm::mat4(1.0f), glm::radians(m_yaw), yAxis); + const glm::mat4 rotationX = glm::rotate(rotationY, -glm::radians(m_pitch), xAxis); const glm::vec3 translation = glm::vec3( rotationX * glm::vec4(0.0f, 0.0f, m_radius, 0.0f) ); @@ -72,15 +67,10 @@ namespace vkcv::camera { } void TrackballCameraController::mouseMoveCallback(double xoffset, double yoffset, Camera &camera) { - if(!m_rotationActive){ - return; - } - - float sensitivity = 0.025f; - xoffset *= sensitivity; - yoffset *= sensitivity; + xoffset *= static_cast<float>(m_rotationActive); + yoffset *= static_cast<float>(m_rotationActive); - panView(xoffset , yoffset, camera); + panView(xoffset, yoffset, camera); } void TrackballCameraController::mouseButtonCallback(int button, int action, int mods, Camera &camera) { @@ -96,7 +86,7 @@ namespace vkcv::camera { GLFWgamepadstate gamepadState; glfwGetGamepadState(gamepadIndex, &gamepadState); - float sensitivity = 100.0f; + float sensitivity = 1.0f; double threshold = 0.1; // handle rotations diff --git a/modules/material/CMakeLists.txt b/modules/material/CMakeLists.txt index d5b654cc6d00ce77d93b4666f48b7a5097e674b3..ed3804531d36f9850bbb5d334e4fed9b43d92434 100644 --- a/modules/material/CMakeLists.txt +++ b/modules/material/CMakeLists.txt @@ -12,8 +12,6 @@ set(vkcv_material_include ${PROJECT_SOURCE_DIR}/include) set(vkcv_material_sources ${vkcv_material_include}/vkcv/material/Material.hpp ${vkcv_material_source}/vkcv/material/Material.cpp - ${vkcv_material_include}/vkcv/material/PBRMaterial.hpp - ${vkcv_material_source}/vkcv/material/PBRMaterial.cpp ) # adding source files to the module diff --git a/modules/material/include/vkcv/material/Material.hpp b/modules/material/include/vkcv/material/Material.hpp index 00b492072fa4ef8b7b41f70202d515ee4ac828fa..9b54d99828eca3738fed9ff1c4078ca9f87eaefa 100644 --- a/modules/material/include/vkcv/material/Material.hpp +++ b/modules/material/include/vkcv/material/Material.hpp @@ -1,14 +1,70 @@ #pragma once + +#include <vector> + +#include <vkcv/Core.hpp> #include <vkcv/Handles.hpp> namespace vkcv::material { - + + enum class MaterialType { + PBR_MATERIAL = 1, + + UNKNOWN = 0 + }; + class Material { private: + struct Texture { + ImageHandle m_Image; + SamplerHandle m_Sampler; + std::vector<float> m_Factors; + }; + + MaterialType m_Type; + DescriptorSetHandle m_DescriptorSet; + std::vector<Texture> m_Textures; + public: - const DescriptorSetHandle m_DescriptorSetHandle; - protected: - Material(const DescriptorSetHandle& setHandle); + Material(); + ~Material() = default; + + Material(const Material& other) = default; + Material(Material&& other) = default; + + Material& operator=(const Material& other) = default; + Material& operator=(Material&& other) = default; + + [[nodiscard]] + MaterialType getType() const; + + [[nodiscard]] + const DescriptorSetHandle& getDescriptorSet() const; + + explicit operator bool() const; + + bool operator!() const; + + static const std::vector<DescriptorBinding>& getDescriptorBindings(MaterialType type); + + static Material createPBR(Core &core, + const ImageHandle &colorImg, + const SamplerHandle &colorSmp, + const ImageHandle &normalImg, + const SamplerHandle &normalSmp, + const ImageHandle &metRoughImg, + const SamplerHandle &metRoughSmp, + const ImageHandle &occlusionImg, + const SamplerHandle &occlusionSmp, + const ImageHandle &emissiveImg, + const SamplerHandle &emissiveSmp, + const float baseColorFactor [4], + float metallicFactor, + float roughnessFactor, + float normalScale, + float occlusionStrength, + const float emissiveFactor [3]); + }; } diff --git a/modules/material/include/vkcv/material/PBRMaterial.hpp b/modules/material/include/vkcv/material/PBRMaterial.hpp deleted file mode 100644 index 09a5214b0e748a09ef8caefe5bf2b1a69ecbd8e1..0000000000000000000000000000000000000000 --- a/modules/material/include/vkcv/material/PBRMaterial.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -#include <vector> - -#include <vkcv/DescriptorConfig.hpp> -#include <vkcv/Core.hpp> - - -#include "Material.hpp" - -namespace vkcv::material -{ - class PBRMaterial : Material - { - private: - struct vec3 { - float x, y, z; - }; - struct vec4 { - float x, y, z, a; - }; - PBRMaterial(const ImageHandle& colorImg, - const SamplerHandle& colorSmp, - const ImageHandle& normalImg, - const SamplerHandle& normalSmp, - const ImageHandle& metRoughImg, - const SamplerHandle& metRoughSmp, - const ImageHandle& occlusionImg, - const SamplerHandle& occlusionSmp, - const ImageHandle& emissiveImg, - const SamplerHandle& emissiveSmp, - const DescriptorSetHandle& setHandle, - vec4 baseColorFactor, - float metallicFactor, - float roughnessFactor, - float normalScale, - float occlusionStrength, - vec3 emissiveFactor) noexcept; - - - public: - PBRMaterial() = delete; - - const ImageHandle m_ColorTexture; - const SamplerHandle m_ColorSampler; - - const ImageHandle m_NormalTexture; - const SamplerHandle m_NormalSampler; - - const ImageHandle m_MetRoughTexture; - const SamplerHandle m_MetRoughSampler; - - const ImageHandle m_OcclusionTexture; - const SamplerHandle m_OcclusionSampler; - - const ImageHandle m_EmissiveTexture; - const SamplerHandle m_EmissiveSampler; - - // - const vec4 m_BaseColorFactor; - const float m_MetallicFactor; - const float m_RoughnessFactor; - const float m_NormalScale; - const float m_OcclusionStrength; - const vec3 m_EmissiveFactor; - - /* - * Returns the material's necessary descriptor bindings which serves as its descriptor layout - * The binding is in the following order: - * 0 - diffuse texture - * 1 - diffuse sampler - * 2 - normal texture - * 3 - normal sampler - * 4 - metallic roughness texture - * 5 - metallic roughness sampler - * 6 - occlusion texture - * 7 - occlusion sampler - * 8 - emissive texture - * 9 - emissive sampler - */ - static std::vector<DescriptorBinding> getDescriptorBindings() noexcept; - - static PBRMaterial create( - vkcv::Core* core, - ImageHandle &colorImg, - SamplerHandle &colorSmp, - ImageHandle &normalImg, - SamplerHandle &normalSmp, - ImageHandle &metRoughImg, - SamplerHandle &metRoughSmp, - ImageHandle &occlusionImg, - SamplerHandle &occlusionSmp, - ImageHandle &emissiveImg, - SamplerHandle &emissiveSmp, - vec4 baseColorFactor, - float metallicFactor, - float roughnessFactor, - float normalScale, - float occlusionStrength, - vec3 emissiveFactor); - - }; -} \ No newline at end of file diff --git a/modules/material/src/vkcv/material/Material.cpp b/modules/material/src/vkcv/material/Material.cpp index 9168bcfbf924e9868ceaaff74aef5d3c6b99739c..409db0b9dd83f91b6a2afbb48d74933ab1a483fc 100644 --- a/modules/material/src/vkcv/material/Material.cpp +++ b/modules/material/src/vkcv/material/Material.cpp @@ -2,11 +2,185 @@ #include "vkcv/material/Material.hpp" namespace vkcv::material { + + Material::Material() { + m_Type = MaterialType::UNKNOWN; + } - //TODO - - Material::Material(const DescriptorSetHandle& setHandle) : m_DescriptorSetHandle(setHandle) + MaterialType Material::getType() const { + return m_Type; + } + + const DescriptorSetHandle & Material::getDescriptorSet() const { + return m_DescriptorSet; + } + + Material::operator bool() const { + return (m_Type != MaterialType::UNKNOWN); + } + + bool Material::operator!() const { + return (m_Type == MaterialType::UNKNOWN); + } + + const std::vector<DescriptorBinding>& Material::getDescriptorBindings(MaterialType type) { + static std::vector<DescriptorBinding> pbr_bindings; + static std::vector<DescriptorBinding> default_bindings; + + switch (type) { + case MaterialType::PBR_MATERIAL: + if (pbr_bindings.empty()) { + pbr_bindings.emplace_back(0, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT); + pbr_bindings.emplace_back(1, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT); + pbr_bindings.emplace_back(2, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT); + pbr_bindings.emplace_back(3, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT); + pbr_bindings.emplace_back(4, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT); + pbr_bindings.emplace_back(5, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT); + pbr_bindings.emplace_back(6, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT); + pbr_bindings.emplace_back(7, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT); + pbr_bindings.emplace_back(8, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT); + pbr_bindings.emplace_back(9, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT); + } + + return pbr_bindings; + default: + return default_bindings; + } + } + + static void fillImage(Image& image, float data [4]) { + std::vector<float> vec (image.getWidth() * image.getHeight() * image.getDepth() * 4); + + for (size_t i = 0; i < vec.size(); i++) { + vec[i] = data[i % 4]; + } + + image.fill(data); + } + + Material Material::createPBR(Core &core, + const ImageHandle &colorImg, const SamplerHandle &colorSmp, + const ImageHandle &normalImg, const SamplerHandle &normalSmp, + const ImageHandle &metRoughImg, const SamplerHandle &metRoughSmp, + const ImageHandle &occlusionImg, const SamplerHandle &occlusionSmp, + const ImageHandle &emissiveImg, const SamplerHandle &emissiveSmp, + const float baseColorFactor [4], + float metallicFactor, + float roughnessFactor, + float normalScale, + float occlusionStrength, + const float emissiveFactor [3]) { + ImageHandle images [5] = { + colorImg, normalImg, metRoughImg, occlusionImg, emissiveImg + }; + + SamplerHandle samplers [5] = { + colorSmp, normalSmp, metRoughSmp, occlusionSmp, emissiveSmp + }; + + if (!colorImg) { + vkcv::Image defaultColor = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2); + float colorData [4] = { 228, 51, 255, 1 }; + fillImage(defaultColor, colorData); + images[0] = defaultColor.getHandle(); + } + + if (!normalImg) { + vkcv::Image defaultNormal = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2); + float normalData [4] = { 0, 0, 1, 0 }; + fillImage(defaultNormal, normalData); + images[1] = defaultNormal.getHandle(); + } + + if (!metRoughImg) { + vkcv::Image defaultRough = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2); + float roughData [4] = { 228, 51, 255, 1 }; + fillImage(defaultRough, roughData); + images[2] = defaultRough.getHandle(); + } + + if (!occlusionImg) { + vkcv::Image defaultOcclusion = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2); + float occlusionData [4] = { 228, 51, 255, 1 }; + fillImage(defaultOcclusion, occlusionData); + images[3] = defaultOcclusion.getHandle(); + } + + if (!emissiveImg) { + vkcv::Image defaultEmissive = core.createImage(vk::Format::eR8G8B8A8Srgb, 2, 2); + float emissiveData [4] = { 0, 0, 0, 1 }; + fillImage(defaultEmissive, emissiveData); + images[4] = defaultEmissive.getHandle(); + } + + if (!colorSmp) { + samplers[0] = core.createSampler( + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerMipmapMode::LINEAR, + vkcv::SamplerAddressMode::REPEAT + ); + } + + if (!normalSmp) { + samplers[1] = core.createSampler( + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerMipmapMode::LINEAR, + vkcv::SamplerAddressMode::REPEAT + ); + } + + if (!metRoughSmp) { + samplers[2] = core.createSampler( + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerMipmapMode::LINEAR, + vkcv::SamplerAddressMode::REPEAT + ); + } + + if (!occlusionSmp) { + samplers[3] = core.createSampler( + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerMipmapMode::LINEAR, + vkcv::SamplerAddressMode::REPEAT + ); + } + + if (!emissiveSmp) { + samplers[4] = core.createSampler( + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerMipmapMode::LINEAR, + vkcv::SamplerAddressMode::REPEAT + ); + } + + Material material; + material.m_Type = MaterialType::PBR_MATERIAL; + + const auto& bindings = getDescriptorBindings(material.m_Type); + material.m_DescriptorSet = core.createDescriptorSet(bindings);; + + material.m_Textures.reserve(bindings.size()); + material.m_Textures.push_back({ images[0], samplers[0], std::vector<float>(baseColorFactor, baseColorFactor+4) }); + material.m_Textures.push_back({ images[1], samplers[1], { normalScale } }); + material.m_Textures.push_back({ images[2], samplers[2], { metallicFactor, roughnessFactor } }); + material.m_Textures.push_back({ images[3], samplers[3], { occlusionStrength } }); + material.m_Textures.push_back({ images[4], samplers[4], std::vector<float>(emissiveFactor, emissiveFactor+3) }); + + vkcv::DescriptorWrites setWrites; + + for (size_t i = 0; i < material.m_Textures.size(); i++) { + setWrites.sampledImageWrites.emplace_back(i * 2, material.m_Textures[i].m_Image); + setWrites.samplerWrites.emplace_back(i * 2 + 1, material.m_Textures[i].m_Sampler); + } + + core.writeDescriptorSet(material.m_DescriptorSet, setWrites); + return material; } } diff --git a/modules/material/src/vkcv/material/PBRMaterial.cpp b/modules/material/src/vkcv/material/PBRMaterial.cpp deleted file mode 100644 index d27e755c06a39e369d22efc997a0b411d067c132..0000000000000000000000000000000000000000 --- a/modules/material/src/vkcv/material/PBRMaterial.cpp +++ /dev/null @@ -1,194 +0,0 @@ -#include "vkcv/material/PBRMaterial.hpp" - - -namespace vkcv::material -{ - PBRMaterial::PBRMaterial( - const ImageHandle& colorImg, - const SamplerHandle& colorSmp, - const ImageHandle& normalImg, - const SamplerHandle& normalSmp, - const ImageHandle& metRoughImg, - const SamplerHandle& metRoughSmp, - const ImageHandle& occlusionImg, - const SamplerHandle& occlusionSmp, - const ImageHandle& emissiveImg, - const SamplerHandle& emissiveSmp, - const DescriptorSetHandle& setHandle, - vec4 baseColorFactor, - float metallicFactor, - float roughnessFactor, - float normalScale, - float occlusionStrength, - vec3 emissiveFactor) noexcept : - m_ColorTexture(colorImg), - m_ColorSampler(colorSmp), - m_NormalTexture(normalImg), - m_NormalSampler(normalSmp), - m_MetRoughTexture(metRoughImg), - m_MetRoughSampler(metRoughSmp), - m_OcclusionTexture(occlusionImg), - m_OcclusionSampler(occlusionSmp), - m_EmissiveTexture(emissiveImg), - m_EmissiveSampler(emissiveSmp), - Material(setHandle), - m_BaseColorFactor(baseColorFactor), - m_MetallicFactor(metallicFactor), - m_RoughnessFactor(roughnessFactor), - m_NormalScale(normalScale), - m_OcclusionStrength(occlusionStrength), - m_EmissiveFactor(emissiveFactor) - { - } - - std::vector<DescriptorBinding> PBRMaterial::getDescriptorBindings() noexcept - { - static std::vector<DescriptorBinding> bindings; - - if (bindings.empty()) { - bindings.emplace_back(0, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT); - bindings.emplace_back(1, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT); - bindings.emplace_back(2, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT); - bindings.emplace_back(3, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT); - bindings.emplace_back(4, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT); - bindings.emplace_back(5, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT); - bindings.emplace_back(6, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT); - bindings.emplace_back(7, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT); - bindings.emplace_back(8, DescriptorType::IMAGE_SAMPLED, 1, ShaderStage::FRAGMENT); - bindings.emplace_back(9, DescriptorType::SAMPLER, 1, ShaderStage::FRAGMENT); - } - - return bindings; - } - - PBRMaterial PBRMaterial::create( - vkcv::Core* core, - ImageHandle& colorImg, - SamplerHandle& colorSmp, - ImageHandle& normalImg, - SamplerHandle& normalSmp, - ImageHandle& metRoughImg, - SamplerHandle& metRoughSmp, - ImageHandle& occlusionImg, - SamplerHandle& occlusionSmp, - ImageHandle& emissiveImg, - SamplerHandle& emissiveSmp, - vec4 baseColorFactor, - float metallicFactor, - float roughnessFactor, - float normalScale, - float occlusionStrength, - vec3 emissiveFactor) - { - //Test if Images and samplers valid, if not use default - if (!colorImg) { - vkcv::Image defaultColor = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1); - vec4 colorData{ 228, 51, 255,1 }; - defaultColor.fill(&colorData); - colorImg = defaultColor.getHandle(); - } - if (!normalImg) { - vkcv::Image defaultNormal = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1); - vec4 normalData{ 0, 0, 1,0 }; - defaultNormal.fill(&normalData); - normalImg = defaultNormal.getHandle(); - } - if (!metRoughImg) { - vkcv::Image defaultRough = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1); - vec4 roughData{ 228, 51, 255,1 }; - defaultRough.fill(&roughData); - metRoughImg = defaultRough.getHandle(); - } - if (!occlusionImg) { - vkcv::Image defaultOcclusion = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1); - vec4 occlusionData{ 228, 51, 255,1 }; - defaultOcclusion.fill(&occlusionData); - occlusionImg = defaultOcclusion.getHandle(); - } - if (!emissiveImg) { - vkcv::Image defaultEmissive = core->createImage(vk::Format::eR8G8B8A8Srgb, 1, 1); - vec4 emissiveData{ 0, 0, 0,1 }; - defaultEmissive.fill(&emissiveData); - emissiveImg = defaultEmissive.getHandle(); - } - if (!colorSmp) { - colorSmp = core->createSampler( - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerMipmapMode::LINEAR, - vkcv::SamplerAddressMode::REPEAT - ); - } - if (!normalSmp) { - normalSmp = core->createSampler( - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerMipmapMode::LINEAR, - vkcv::SamplerAddressMode::REPEAT - ); - } - if (!metRoughSmp) { - metRoughSmp = core->createSampler( - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerMipmapMode::LINEAR, - vkcv::SamplerAddressMode::REPEAT - ); - } - if (!occlusionSmp) { - occlusionSmp = core->createSampler( - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerMipmapMode::LINEAR, - vkcv::SamplerAddressMode::REPEAT - ); - } - if (!emissiveSmp) { - emissiveSmp = core->createSampler( - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerMipmapMode::LINEAR, - vkcv::SamplerAddressMode::REPEAT - ); - } - - - - //create descriptorset - vkcv::DescriptorSetHandle descriptorSetHandle = core->createDescriptorSet(getDescriptorBindings()); - //writes - vkcv::DescriptorWrites setWrites; - setWrites.sampledImageWrites = { - vkcv::SampledImageDescriptorWrite(0, colorImg), - vkcv::SampledImageDescriptorWrite(2, normalImg), - vkcv::SampledImageDescriptorWrite(4, metRoughImg), - vkcv::SampledImageDescriptorWrite(6, occlusionImg), - vkcv::SampledImageDescriptorWrite(8, emissiveImg) }; - setWrites.samplerWrites = { - vkcv::SamplerDescriptorWrite(1, colorSmp), - vkcv::SamplerDescriptorWrite(3, normalSmp), - vkcv::SamplerDescriptorWrite(5, metRoughSmp), - vkcv::SamplerDescriptorWrite(7, occlusionSmp), - vkcv::SamplerDescriptorWrite(9, emissiveSmp) }; - core->writeDescriptorSet(descriptorSetHandle, setWrites); - - return PBRMaterial( - colorImg, - colorSmp, - normalImg, - normalSmp, - metRoughImg, - metRoughSmp, - occlusionImg, - occlusionSmp, - emissiveImg, - emissiveSmp, - descriptorSetHandle, - baseColorFactor, - metallicFactor, - roughnessFactor, - normalScale, - occlusionStrength, - emissiveFactor); - } -} \ No newline at end of file diff --git a/modules/meshlet/CMakeLists.txt b/modules/meshlet/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..d576466d3d125d3a19640088a9b5725ac7a46b97 --- /dev/null +++ b/modules/meshlet/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.16) +project(vkcv_meshlet) + +# setting c++ standard for the module +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(vkcv_meshlet_source ${PROJECT_SOURCE_DIR}/src) +set(vkcv_meshlet_include ${PROJECT_SOURCE_DIR}/include) + +# Add source and header files to the module +set(vkcv_meshlet_sources + ${vkcv_meshlet_include}/vkcv/meshlet/Meshlet.hpp + ${vkcv_meshlet_source}/vkcv/meshlet/Meshlet.cpp + + ${vkcv_meshlet_include}/vkcv/meshlet/Tipsify.hpp + ${vkcv_meshlet_source}/vkcv/meshlet/Tipsify.cpp + + ${vkcv_meshlet_include}/vkcv/meshlet/Forsyth.hpp + ${vkcv_meshlet_source}/vkcv/meshlet/Forsyth.cpp) + +# adding source files to the module +add_library(vkcv_meshlet STATIC ${vkcv_meshlet_sources}) + + +# link the required libraries to the module +target_link_libraries(vkcv_meshlet vkcv ${vkcv_libraries}) + +# including headers of dependencies and the VkCV framework +target_include_directories(vkcv_meshlet SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include}) + +# add the own include directory for public headers +target_include_directories(vkcv_meshlet BEFORE PUBLIC ${vkcv_meshlet_include}) + +# linking with libraries from all dependencies and the VkCV framework +target_link_libraries(vkcv_meshlet vkcv vkcv_asset_loader vkcv_camera) diff --git a/modules/meshlet/include/vkcv/meshlet/Forsyth.hpp b/modules/meshlet/include/vkcv/meshlet/Forsyth.hpp new file mode 100644 index 0000000000000000000000000000000000000000..43dc9a3b6bb81ea915268de7a7b53b18efd27638 --- /dev/null +++ b/modules/meshlet/include/vkcv/meshlet/Forsyth.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "Meshlet.hpp" + +namespace vkcv::meshlet +{ + /** + * Reorders the index buffer, simulating a LRU cache, so that vertices are grouped together in close triangle patches + * @param idxBuf current IndexBuffer + * @param vertexCount of the mesh + * @return new reordered index buffer to replace the input index buffer + * References: + * https://tomforsyth1000.github.io/papers/fast_vert_cache_opt.html + * https://www.martin.st/thesis/efficient_triangle_reordering.pdf + * https://github.com/vivkin/forsyth/blob/master/forsyth.h + */ + VertexCacheReorderResult forsythReorder(const std::vector<uint32_t> &idxBuf, const size_t vertexCount); +} diff --git a/modules/meshlet/include/vkcv/meshlet/Meshlet.hpp b/modules/meshlet/include/vkcv/meshlet/Meshlet.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9900dffaf28c85753d367ba79bbdf5c19a2cf479 --- /dev/null +++ b/modules/meshlet/include/vkcv/meshlet/Meshlet.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include <vector> +#include <map> +#include <glm/glm.hpp> +#include <vkcv/asset/asset_loader.hpp> + +namespace vkcv::meshlet { + + struct Vertex { + glm::vec3 position; + float padding0; + glm::vec3 normal; + float padding1; + }; + + struct Meshlet { + uint32_t vertexOffset; + uint32_t vertexCount; + uint32_t indexOffset; + uint32_t indexCount; + glm::vec3 meanPosition; + float boundingSphereRadius; + }; + + struct VertexCacheReorderResult { + /** + * @param indexBuffer new indexBuffer + * @param skippedIndices indices that have a spacial break + */ + VertexCacheReorderResult(const std::vector<uint32_t> indexBuffer, const std::vector<uint32_t> skippedIndices) + :indexBuffer(indexBuffer), skippedIndices(skippedIndices) {} + + std::vector<uint32_t> indexBuffer; + std::vector<uint32_t> skippedIndices; + }; + + struct MeshShaderModelData { + std::vector<Vertex> vertices; + std::vector<uint32_t> localIndices; + std::vector<Meshlet> meshlets; + }; + + std::vector<Vertex> convertToVertices( + const std::vector<uint8_t>& vertexData, + const uint64_t vertexCount, + const vkcv::asset::VertexAttribute& positionAttribute, + const vkcv::asset::VertexAttribute& normalAttribute); + + MeshShaderModelData createMeshShaderModelData( + const std::vector<Vertex>& inVertices, + const std::vector<uint32_t>& inIndices, + const std::vector<uint32_t>& deadEndIndices = {}); + + std::vector<uint32_t> assetLoaderIndicesTo32BitIndices( + const std::vector<uint8_t>& indexData, + vkcv::asset::IndexType indexType); + +} \ No newline at end of file diff --git a/modules/meshlet/include/vkcv/meshlet/Tipsify.hpp b/modules/meshlet/include/vkcv/meshlet/Tipsify.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6fb4b37d9c17c82642c3b5e7667c3e8acc50b8c0 --- /dev/null +++ b/modules/meshlet/include/vkcv/meshlet/Tipsify.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "Meshlet.hpp" +#include <algorithm> +#include <iostream> + +namespace vkcv::meshlet { + /** + * reorders the IndexBuffer, so all usages of vertices to triangle are as close as possible + * @param indexBuffer32Bit current IndexBuffer + * @param vertexCount of the mesh + * @param cacheSize of the priority cache <br> + * Recommended: 20. Keep the value between 5 and 50 <br> + * low: more random and patchy<br> + * high: closer vertices have higher chance -> leads to sinuous lines + * @return new IndexBuffer that replaces the input IndexBuffer, and the indices that are skipped + * + * https://gfx.cs.princeton.edu/pubs/Sander_2007_%3ETR/tipsy.pdf + * https://www.martin.st/thesis/efficient_triangle_reordering.pdf + */ + VertexCacheReorderResult tipsifyMesh(const std::vector<uint32_t> &indexBuffer32Bit, + const int vertexCount, const unsigned int cacheSize = 20); +} \ No newline at end of file diff --git a/modules/meshlet/src/vkcv/meshlet/Forsyth.cpp b/modules/meshlet/src/vkcv/meshlet/Forsyth.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fd0f160d65b8db81102f9eb6a9d60cf735999d44 --- /dev/null +++ b/modules/meshlet/src/vkcv/meshlet/Forsyth.cpp @@ -0,0 +1,317 @@ +#include "vkcv/meshlet/Forsyth.hpp" +#include <vkcv/Logger.hpp> +#include <array> +#include <cmath> + +namespace vkcv::meshlet +{ + /* + * CACHE AND VALENCE + * SIZE AND SCORE CONSTANTS + * CHANGE AS NEEDED + */ + + // set these to adjust performance and result quality + const size_t VERTEX_CACHE_SIZE = 8; + const size_t CACHE_FUNCTION_LENGTH = 32; + + // score function constants + const float CACHE_DECAY_POWER = 1.5f; + const float LAST_TRI_SCORE = 0.75f; + + const float VALENCE_BOOST_SCALE = 2.0f; + const float VALENCE_BOOST_POWER = 0.5f; + + // sizes for precalculated tables + // make sure that cache score is always >= vertex_cache_size + const size_t CACHE_SCORE_TABLE_SIZE = 32; + const size_t VALENCE_SCORE_TABLE_SIZE = 32; + + // precalculated tables + std::array<float, CACHE_SCORE_TABLE_SIZE> cachePositionScore = {}; + std::array<float, VALENCE_SCORE_TABLE_SIZE> valenceScore = {}; + + // function to populate the cache position and valence score tables + void initScoreTables() + { + for(size_t i = 0; i < CACHE_SCORE_TABLE_SIZE; i++) + { + float score = 0.0f; + if (i < 3) + { + score = LAST_TRI_SCORE; + } + else + { + const float scaler = 1.0f / static_cast<float>(CACHE_FUNCTION_LENGTH - 3); + score = 1.0f - (i - 3) * scaler; + score = std::pow(score, CACHE_DECAY_POWER); + } + cachePositionScore[i] = score; + } + + for(size_t i = 0; i < VALENCE_SCORE_TABLE_SIZE; i++) + { + const float valenceBoost = std::pow(i, -VALENCE_BOOST_POWER); + const float score = VALENCE_BOOST_SCALE * valenceBoost; + + valenceScore[i] = score; + } + } + + /** + * Return the vertex' score, depending on its current active triangle count and cache position + * Add a valence boost to score, if active triangles are below VALENCE_SCORE_TABLE_SIZE + * @param numActiveTris the active triangles on this vertex + * @param cachePos the vertex' position in the cache + * @return vertex' score + */ + float findVertexScore(uint32_t numActiveTris, int32_t cachePos) + { + if(numActiveTris == 0) + return 0.0f; + + float score = 0.0f; + + if (cachePos >= 0) + score = cachePositionScore[cachePos]; + + if (numActiveTris < VALENCE_SCORE_TABLE_SIZE) + score += valenceScore[numActiveTris]; + + return score; + } + + VertexCacheReorderResult forsythReorder(const std::vector<uint32_t> &idxBuf, const size_t vertexCount) + { + std::vector<uint32_t> skippedIndices; + + initScoreTables(); + + // get the total triangle count from the index buffer + const size_t triangleCount = idxBuf.size() / 3; + + // per-vertex active triangle count + std::vector<uint8_t> numActiveTris(vertexCount, 0); + // iterate over indices, count total occurrences of each vertex + for(const auto index : idxBuf) + { + if(numActiveTris[index] == UINT8_MAX) + { + vkcv_log(LogLevel::ERROR, "Unsupported mesh."); + vkcv_log(LogLevel::ERROR, "Vertex shared by too many triangles."); + return VertexCacheReorderResult({}, {}); + } + + numActiveTris[index]++; + } + + + // allocate remaining vectors + /** + * offsets: contains the vertices' offset into the triangleIndices vector + * Offset itself is the sum of triangles required by the previous vertices + * + * lastScore: the vertices' most recent calculated score + * + * cacheTag: the vertices' most recent cache score + * + * triangleAdded: boolean flags to denote whether a triangle has been processed or not + * + * triangleScore: total score of the three vertices making up the triangle + * + * triangleIndices: indices for the triangles + */ + std::vector<uint32_t> offsets(vertexCount, 0); + std::vector<float> lastScore(vertexCount, 0.0f); + std::vector<int8_t> cacheTag(vertexCount, -1); + + std::vector<bool> triangleAdded(triangleCount, false); + std::vector<float> triangleScore(triangleCount, 0.0f); + + std::vector<int32_t> triangleIndices(idxBuf.size(), 0); + + + // sum the number of active triangles for all previous vertices + // null the number of active triangles afterwards for recalculation in second loop + uint32_t sum = 0; + for(size_t i = 0; i < vertexCount; i++) + { + offsets[i] = sum; + sum += numActiveTris[i]; + numActiveTris[i] = 0; + } + // create the triangle indices, using the newly calculated offsets, and increment numActiveTris + // every vertex should be referenced by a triangle index now + for(size_t i = 0; i < triangleCount; i++) + { + for(size_t j = 0; j < 3; j++) + { + uint32_t v = idxBuf[3 * i + j]; + triangleIndices[offsets[v] + numActiveTris[v]] = static_cast<int32_t>(i); + numActiveTris[v]++; + } + } + + // calculate and initialize the triangle score, by summing the vertices' score + for (size_t i = 0; i < vertexCount; i++) + { + lastScore[i] = findVertexScore(numActiveTris[i], static_cast<int32_t>(cacheTag[i])); + + for(size_t j = 0; j < numActiveTris[i]; j++) + { + triangleScore[triangleIndices[offsets[i] + j]] += lastScore[i]; + } + } + + // find best triangle to start reordering with + int32_t bestTriangle = -1; + float bestScore = -1.0f; + for(size_t i = 0; i < triangleCount; i++) + { + if(triangleScore[i] > bestScore) + { + bestScore = triangleScore[i]; + bestTriangle = static_cast<int32_t>(i); + } + } + + // allocate output triangles + std::vector<int32_t> outTriangles(triangleCount, 0); + uint32_t outPos = 0; + + // initialize cache (with -1) + std::array<int32_t, VERTEX_CACHE_SIZE + 3> cache = {}; + for(auto &element : cache) + { + element = -1; + } + + uint32_t scanPos = 0; + + // begin reordering routine + // output the currently best triangle, as long as there are triangles left to output + while(bestTriangle >= 0) + { + // mark best triangle as added + triangleAdded[bestTriangle] = true; + // output this triangle + outTriangles[outPos++] = bestTriangle; + + // push best triangle's vertices into the cache + for(size_t i = 0; i < 3; i++) + { + uint32_t v = idxBuf[3 * bestTriangle + i]; + + // get vertex' cache position, if its -1, set its position to the end + int8_t endPos = cacheTag[v]; + if(endPos < 0) + endPos = static_cast<int8_t>(VERTEX_CACHE_SIZE + i); + + // shift vertices' cache entries forward by one + for(int8_t j = endPos; j > i; j--) + { + cache[j] = cache[j - 1]; + + // if cache slot is valid vertex, + // update the vertex cache tag accordingly + if (cache[j] >= 0) + cacheTag[cache[j]]++; + } + + // insert current vertex into its new target slot + cache[i] = static_cast<int32_t>(v); + cacheTag[v] = static_cast<int8_t>(i); + + // find current triangle in the list of active triangles + // remove it by moving the last triangle into the slot the current triangle is holding. + for (size_t j = 0; j < numActiveTris[v]; j++) + { + if(triangleIndices[offsets[v] + j] == bestTriangle) + { + triangleIndices[offsets[v] + j] = triangleIndices[offsets[v] + numActiveTris[v] - 1]; + break; + } + } + // shorten the list + numActiveTris[v]--; + } + + // update scores of all triangles in cache + for (size_t i = 0; i < cache.size(); i++) + { + int32_t v = cache[i]; + if (v < 0) + break; + + // this vertex has been pushed outside of the actual cache + if(i >= VERTEX_CACHE_SIZE) + { + cacheTag[v] = -1; + cache[i] = -1; + } + + float newScore = findVertexScore(numActiveTris[v], cacheTag[v]); + float diff = newScore - lastScore[v]; + + for(size_t j = 0; j < numActiveTris[v]; j++) + { + triangleScore[triangleIndices[offsets[v] + j]] += diff; + } + lastScore[v] = newScore; + } + + // find best triangle reference by vertices in cache + bestTriangle = -1; + bestScore = -1.0f; + for(size_t i = 0; i < VERTEX_CACHE_SIZE; i++) + { + if (cache[i] < 0) + break; + + int32_t v = cache[i]; + for(size_t j = 0; j < numActiveTris[v]; j++) + { + int32_t t = triangleIndices[offsets[v] + j]; + if(triangleScore[t] > bestScore) + { + bestTriangle = t; + bestScore = triangleScore[t]; + } + } + } + + // if no triangle was found at all, continue scanning whole list of triangles + if (bestTriangle < 0) + { + for(; scanPos < triangleCount; scanPos++) + { + if(!triangleAdded[scanPos]) + { + bestTriangle = scanPos; + + skippedIndices.push_back(3 * outPos); + + break; + } + } + } + } + + + // convert triangle index array into full triangle list + std::vector<uint32_t> outIndices(idxBuf.size(), 0); + outPos = 0; + for(size_t i = 0; i < triangleCount; i++) + { + int32_t t = outTriangles[i]; + for(size_t j = 0; j < 3; j++) + { + int32_t v = idxBuf[3 * t + j]; + outIndices[outPos++] = static_cast<uint32_t>(v); + } + } + + return VertexCacheReorderResult(outIndices, skippedIndices); + } +} \ No newline at end of file diff --git a/modules/meshlet/src/vkcv/meshlet/Meshlet.cpp b/modules/meshlet/src/vkcv/meshlet/Meshlet.cpp new file mode 100644 index 0000000000000000000000000000000000000000..abcad7207ed5a6f80cb292ab2f7e855d3b4c7797 --- /dev/null +++ b/modules/meshlet/src/vkcv/meshlet/Meshlet.cpp @@ -0,0 +1,167 @@ + +#include "vkcv/meshlet/Meshlet.hpp" +#include <vkcv/Logger.hpp> +#include <cassert> +#include <iostream> + +namespace vkcv::meshlet { + +std::vector<vkcv::meshlet::Vertex> convertToVertices( + const std::vector<uint8_t>& vertexData, + const uint64_t vertexCount, + const vkcv::asset::VertexAttribute& positionAttribute, + const vkcv::asset::VertexAttribute& normalAttribute) { + + assert(positionAttribute.type == vkcv::asset::PrimitiveType::POSITION); + assert(normalAttribute.type == vkcv::asset::PrimitiveType::NORMAL); + + std::vector<vkcv::meshlet::Vertex> vertices; + vertices.reserve(vertexCount); + + const size_t positionStepSize = positionAttribute.stride == 0 ? sizeof(glm::vec3) : positionAttribute.stride; + const size_t normalStepSize = normalAttribute.stride == 0 ? sizeof(glm::vec3) : normalAttribute.stride; + + for (int i = 0; i < vertexCount; i++) { + Vertex v; + + const size_t positionOffset = positionAttribute.offset + positionStepSize * i; + const size_t normalOffset = normalAttribute.offset + normalStepSize * i; + + v.position = *reinterpret_cast<const glm::vec3*>(&(vertexData[positionOffset])); + v.normal = *reinterpret_cast<const glm::vec3*>(&(vertexData[normalOffset])); + vertices.push_back(v); + } + return vertices; +} + +MeshShaderModelData createMeshShaderModelData( + const std::vector<Vertex>& inVertices, + const std::vector<uint32_t>& inIndices, + const std::vector<uint32_t>& deadEndIndices) { + + MeshShaderModelData data; + size_t currentIndex = 0; + + const size_t maxVerticesPerMeshlet = 64; + const size_t maxIndicesPerMeshlet = 126 * 3; + + bool indicesAreLeft = true; + + size_t deadEndIndicesIndex = 0; + + while (indicesAreLeft) { + Meshlet meshlet; + + meshlet.indexCount = 0; + meshlet.vertexCount = 0; + + meshlet.indexOffset = data.localIndices.size(); + meshlet.vertexOffset = data.vertices.size(); + + std::map<uint32_t, uint32_t> globalToLocalIndexMap; + std::vector<uint32_t> globalIndicesOrdered; + + while (true) { + + if (deadEndIndicesIndex < deadEndIndices.size()) { + const uint32_t deadEndIndex = deadEndIndices[deadEndIndicesIndex]; + if (deadEndIndex == currentIndex) { + deadEndIndicesIndex++; + break; + } + } + + indicesAreLeft = currentIndex + 1 <= inIndices.size(); + if (!indicesAreLeft) { + break; + } + + bool enoughSpaceForIndices = meshlet.indexCount + 3 < maxIndicesPerMeshlet; + if (!enoughSpaceForIndices) { + break; + } + + size_t vertexCountToAdd = 0; + for (int i = 0; i < 3; i++) { + const uint32_t globalIndex = inIndices[currentIndex + i]; + const bool containsVertex = globalToLocalIndexMap.find(globalIndex) != globalToLocalIndexMap.end(); + if (!containsVertex) { + vertexCountToAdd++; + } + } + + bool enoughSpaceForVertices = meshlet.vertexCount + vertexCountToAdd < maxVerticesPerMeshlet; + if (!enoughSpaceForVertices) { + break; + } + + for (int i = 0; i < 3; i++) { + const uint32_t globalIndex = inIndices[currentIndex + i]; + + uint32_t localIndex; + const bool indexAlreadyExists = globalToLocalIndexMap.find(globalIndex) != globalToLocalIndexMap.end(); + if (indexAlreadyExists) { + localIndex = globalToLocalIndexMap[globalIndex]; + } + else { + localIndex = globalToLocalIndexMap.size(); + globalToLocalIndexMap[globalIndex] = localIndex; + globalIndicesOrdered.push_back(globalIndex); + } + + data.localIndices.push_back(localIndex); + } + + meshlet.indexCount += 3; + currentIndex += 3; + meshlet.vertexCount += vertexCountToAdd; + } + + for (const uint32_t globalIndex : globalIndicesOrdered) { + const Vertex v = inVertices[globalIndex]; + data.vertices.push_back(v); + } + + // compute mean position + meshlet.meanPosition = glm::vec3(0); + const uint32_t meshletLastVertexIndex = meshlet.vertexOffset + meshlet.vertexCount; + + for (uint32_t vertexIndex = meshlet.vertexOffset; vertexIndex < meshletLastVertexIndex; vertexIndex++) { + const Vertex& v = data.vertices[vertexIndex]; + meshlet.meanPosition += v.position; + } + meshlet.meanPosition /= meshlet.vertexCount; + + // compute bounding sphere radius + meshlet.boundingSphereRadius = 0.f; + for (uint32_t vertexIndex = meshlet.vertexOffset; vertexIndex < meshletLastVertexIndex; vertexIndex++) { + const Vertex& v = data.vertices[vertexIndex]; + const float d = glm::distance(v.position, meshlet.meanPosition); + meshlet.boundingSphereRadius = glm::max(meshlet.boundingSphereRadius, d); + } + + data.meshlets.push_back(meshlet); + } + + return data; +} + +std::vector<uint32_t> assetLoaderIndicesTo32BitIndices(const std::vector<uint8_t>& indexData, vkcv::asset::IndexType indexType) { + std::vector<uint32_t> indices; + if (indexType == vkcv::asset::IndexType::UINT16) { + for (int i = 0; i < indexData.size(); i += 2) { + const uint16_t index16Bit = *reinterpret_cast<const uint16_t *>(&(indexData[i])); + const uint32_t index32Bit = static_cast<uint32_t>(index16Bit); + indices.push_back(index32Bit); + } + } else if (indexType == vkcv::asset::IndexType::UINT32) { + for (int i = 0; i < indexData.size(); i += 4) { + const uint32_t index32Bit = *reinterpret_cast<const uint32_t *>(&(indexData[i])); + indices.push_back(index32Bit); + } + } else { + vkcv_log(vkcv::LogLevel::ERROR, "Unsupported index type"); + } + return indices; +} +} \ No newline at end of file diff --git a/modules/meshlet/src/vkcv/meshlet/Tipsify.cpp b/modules/meshlet/src/vkcv/meshlet/Tipsify.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c5762100bc37eccbe3e4f6b4c94e5f0e580c53c7 --- /dev/null +++ b/modules/meshlet/src/vkcv/meshlet/Tipsify.cpp @@ -0,0 +1,288 @@ + +#include <vkcv/Logger.hpp> +#include "vkcv/meshlet/Tipsify.hpp" +#include <iostream> + +namespace vkcv::meshlet { + + const int maxUsedVertices = 128; + + /** + * modulo operation with maxUsedVertices + * @param number for modulo operation + * @return number between 0 and maxUsedVertices - 1 + */ + int mod( int number ){ + return (number + maxUsedVertices) % maxUsedVertices; + } + + /** + * searches for the next VertexIndex that was used before or returns any vertexIndex if no used was found + * @param livingTriangles + * @param usedVerticeStack + * @param usedVerticeCount + * @param usedVerticeOffset + * @param vertexCount + * @param lowestLivingVertexIndex + * @param currentTriangleIndex + * @param skippedIndices + * @return a VertexIndex to be used as fanningVertexIndex + */ + int skipDeadEnd( + const std::vector<uint8_t> &livingTriangles, + const std::vector<uint32_t> &usedVerticeStack, + int &usedVerticeCount, + int &usedVerticeOffset, + int vertexCount, + int &lowestLivingVertexIndex, + int ¤tTriangleIndex, + std::vector<uint32_t> &skippedIndices) { + + // returns the latest vertex used that has a living triangle + while (mod(usedVerticeCount) != usedVerticeOffset) { + // iterate from the latest to the oldest. + maxUsedVertices to always make it a positive number in the range 0 to maxUsedVertices -1 + int nextVertex = usedVerticeStack[mod(--usedVerticeCount)]; + + if (livingTriangles[nextVertex] > 0) { + return nextVertex; + } + } + // returns any vertexIndex since no last used has a living triangle + while (lowestLivingVertexIndex + 1 < vertexCount) { + lowestLivingVertexIndex++; + if (livingTriangles[lowestLivingVertexIndex] > 0) { + // add index of the vertex to skippedIndices + skippedIndices.push_back(static_cast<uint32_t>(currentTriangleIndex * 3)); + return lowestLivingVertexIndex; + } + } + return -1; + } + + /** + * searches for the best next candidate as a fanningVertexIndex + * @param vertexCount + * @param lowestLivingVertexIndex + * @param cacheSize + * @param possibleCandidates + * @param numPossibleCandidates + * @param lastTimestampCache + * @param currentTimeStamp + * @param livingTriangles + * @param usedVerticeStack + * @param usedVerticeCount + * @param usedVerticeOffset + * @param currentTriangleIndex + * @param skippedIndices + * @return a VertexIndex to be used as fanningVertexIndex + */ + int getNextVertexIndex(int vertexCount, + int &lowestLivingVertexIndex, + int cacheSize, + const std::vector<uint32_t> &possibleCandidates, + int numPossibleCandidates, + const std::vector<uint32_t> &lastTimestampCache, + int currentTimeStamp, + const std::vector<uint8_t> &livingTriangles, + const std::vector<uint32_t> &usedVerticeStack, + int &usedVerticeCount, + int &usedVerticeOffset, + int ¤tTriangleIndex, + std::vector<uint32_t> &skippedIndices) { + int nextVertexIndex = -1; + int maxPriority = -1; + // calculates the next possibleCandidates that is recently used + for (int j = 0; j < numPossibleCandidates; j++) { + int vertexIndex = possibleCandidates[j]; + + // the candidate needs to be not fanned out yet + if (livingTriangles[vertexIndex] > 0) { + int priority = -1; + + // prioritizes recent used vertices, but tries not to pick one that has many triangles -> fills holes better + if ( currentTimeStamp - lastTimestampCache[vertexIndex] + 2 * livingTriangles[vertexIndex] <= + cacheSize) { + priority = currentTimeStamp - lastTimestampCache[vertexIndex]; + } + // select the vertexIndex with the highest priority + if (priority > maxPriority) { + maxPriority = priority; + nextVertexIndex = vertexIndex; + } + } + } + + // if no candidate is alive, try and find another one + if (nextVertexIndex == -1) { + nextVertexIndex = skipDeadEnd( + livingTriangles, + usedVerticeStack, + usedVerticeCount, + usedVerticeOffset, + vertexCount, + lowestLivingVertexIndex, + currentTriangleIndex, + skippedIndices); + } + return nextVertexIndex; + } + + VertexCacheReorderResult tipsifyMesh( + const std::vector<uint32_t> &indexBuffer32Bit, + const int vertexCount, + const unsigned int cacheSize) { + + if (indexBuffer32Bit.empty() || vertexCount <= 0) { + vkcv_log(LogLevel::ERROR, "Invalid Input."); + return VertexCacheReorderResult(indexBuffer32Bit , {}); + } + int triangleCount = indexBuffer32Bit.size() / 3; + + // dynamic array for vertexOccurrence + std::vector<uint8_t> vertexOccurrence(vertexCount, 0); + // count the occurrence of a vertex in all among all triangles + for (size_t i = 0; i < triangleCount * 3; i++) { + vertexOccurrence[indexBuffer32Bit[i]]++; + } + + int sum = 0; + std::vector<uint32_t> offsetVertexOccurrence(vertexCount + 1, 0); + // highest offset for later iteration + int maxOffset = 0; + // calculate the offset of each vertex from the start + for (int i = 0; i < vertexCount; i++) { + offsetVertexOccurrence[i] = sum; + sum += vertexOccurrence[i]; + + if (vertexOccurrence[i] > maxOffset) { + maxOffset = vertexOccurrence[i]; + } + // reset for reuse + vertexOccurrence[i] = 0; + } + offsetVertexOccurrence[vertexCount] = sum; + + // vertexIndexToTriangle = which vertex belongs to which triangle + std::vector<uint32_t> vertexIndexToTriangle(3 * triangleCount, 0); + // vertexOccurrence functions as number of usages in all triangles + // lowestLivingVertexIndex = number of a triangle + for (int i = 0; i < triangleCount; i++) { + // get the pointer to the first vertex of the triangle + // this allows us to iterate over the indexBuffer with the first vertex of the triangle as start + const uint32_t *vertexIndexOfTriangle = &indexBuffer32Bit[i * 3]; + + vertexIndexToTriangle[offsetVertexOccurrence[vertexIndexOfTriangle[0]] + vertexOccurrence[vertexIndexOfTriangle[0]]] = i; + vertexOccurrence[vertexIndexOfTriangle[0]]++; + + vertexIndexToTriangle[offsetVertexOccurrence[vertexIndexOfTriangle[1]] + vertexOccurrence[vertexIndexOfTriangle[1]]] = i; + vertexOccurrence[vertexIndexOfTriangle[1]]++; + + vertexIndexToTriangle[offsetVertexOccurrence[vertexIndexOfTriangle[2]] + vertexOccurrence[vertexIndexOfTriangle[2]]] = i; + vertexOccurrence[vertexIndexOfTriangle[2]]++; + } + + // counts if a triangle still uses this vertex + std::vector<uint8_t> livingVertices = vertexOccurrence; + std::vector<uint32_t> lastTimestampCache(vertexCount, 0); + + // stack of already used vertices, if it'currentTimeStamp full it will write to 0 again + std::vector<uint32_t> usedVerticeStack(maxUsedVertices, 0); + + //currently used vertices + int usedVerticeCount = 0; + // offset if maxUsedVertices was reached and it loops back to 0 + int usedVerticeOffset = 0; + + // saves if a triangle was emitted (used in the IndexBuffer) + std::vector<bool> isEmittedTriangles(triangleCount, false); + + // reordered Triangles that get rewritten to the new IndexBuffer + std::vector<uint32_t> reorderedTriangleIndexBuffer(triangleCount, 0); + + // offset to the latest not used triangleIndex + int triangleOutputOffset = 0; + // vertexIndex to fan out from (fanning VertexIndex) + int currentVertexIndex = 0; + int currentTimeStamp = cacheSize + 1; + int lowestLivingVertexIndex = 0; + + std::vector<uint32_t> possibleCandidates(3 * maxOffset); + + int currentTriangleIndex = 0; + // list of vertex indices where a deadEnd was reached + // useful to know where the mesh is potentially not contiguous + std::vector<uint32_t> skippedIndices; + + // run while not all indices are fanned out, -1 equals all are fanned out + while (currentVertexIndex >= 0) { + // number of possible candidates for a fanning VertexIndex + int numPossibleCandidates = 0; + // offset of currentVertexIndex and the next VertexIndex + int startOffset = offsetVertexOccurrence[currentVertexIndex]; + int endOffset = offsetVertexOccurrence[currentVertexIndex + 1]; + // iterates over every triangle of currentVertexIndex + for (int offset = startOffset; offset < endOffset; offset++) { + int triangleIndex = vertexIndexToTriangle[offset]; + + // checks if the triangle is already emitted + if (!isEmittedTriangles[triangleIndex]) { + + // get the pointer to the first vertex of the triangle + // this allows us to iterate over the indexBuffer with the first vertex of the triangle as start + const uint32_t *vertexIndexOfTriangle = &indexBuffer32Bit[3 * triangleIndex]; + + currentTriangleIndex++; + + // save emitted vertexIndexOfTriangle to reorderedTriangleIndexBuffer and set it to emitted + reorderedTriangleIndexBuffer[triangleOutputOffset++] = triangleIndex; + isEmittedTriangles[triangleIndex] = true; + + // save all vertexIndices of the triangle to reuse as soon as possible + for (int j = 0; j < 3; j++) { + int vertexIndex = vertexIndexOfTriangle[j]; + + //save vertexIndex to reuseStack + usedVerticeStack[mod(usedVerticeCount++)] = vertexIndex; + + // after looping back increase the start, so it only overrides the oldest vertexIndex + if ((mod(usedVerticeCount)) == + (mod(usedVerticeOffset))) { + usedVerticeOffset = mod(usedVerticeOffset + 1); + } + // add vertex to next possibleCandidates as fanning vertex + possibleCandidates[numPossibleCandidates++] = vertexIndex; + + // remove one occurrence of the vertex, since the triangle is used + livingVertices[vertexIndex]--; + + // writes the timestamp (number of iteration) of the last usage, if it wasn't used within the last cacheSize iterations + if (currentTimeStamp - lastTimestampCache[vertexIndex] > cacheSize) { + lastTimestampCache[vertexIndex] = currentTimeStamp; + currentTimeStamp++; + } + } + } + } + + // search for the next vertexIndex to fan out + currentVertexIndex = getNextVertexIndex( + vertexCount, lowestLivingVertexIndex, cacheSize, possibleCandidates, numPossibleCandidates, lastTimestampCache, currentTimeStamp, + livingVertices, usedVerticeStack, usedVerticeCount, usedVerticeOffset, currentTriangleIndex, skippedIndices); + } + + std::vector<uint32_t> reorderedIndexBuffer(3 * triangleCount); + + triangleOutputOffset = 0; + // rewriting the TriangleIndexBuffer to the new IndexBuffer + for (int i = 0; i < triangleCount; i++) { + int triangleIndex = reorderedTriangleIndexBuffer[i]; + // rewriting the triangle index to vertices + for (int j = 0; j < 3; j++) { + int vertexIndex = indexBuffer32Bit[(3 * triangleIndex) + j]; + reorderedIndexBuffer[triangleOutputOffset++] = vertexIndex; + } + } + + return VertexCacheReorderResult(reorderedIndexBuffer, skippedIndices); + } +} \ No newline at end of file diff --git a/modules/scene/CMakeLists.txt b/modules/scene/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..5edf9a29ad929b3c07b79d4f1ffcb7f1cf2fcd99 --- /dev/null +++ b/modules/scene/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.16) +project(vkcv_scene) + +# setting c++ standard for the module +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(vkcv_scene_source ${PROJECT_SOURCE_DIR}/src) +set(vkcv_scene_include ${PROJECT_SOURCE_DIR}/include) + +# Add source and header files to the module +set(vkcv_scene_sources + ${vkcv_scene_include}/vkcv/scene/Bounds.hpp + ${vkcv_scene_source}/vkcv/scene/Bounds.cpp + + ${vkcv_scene_include}/vkcv/scene/Frustum.hpp + ${vkcv_scene_source}/vkcv/scene/Frustum.cpp + + ${vkcv_scene_include}/vkcv/scene/MeshPart.hpp + ${vkcv_scene_source}/vkcv/scene/MeshPart.cpp + + ${vkcv_scene_include}/vkcv/scene/Mesh.hpp + ${vkcv_scene_source}/vkcv/scene/Mesh.cpp + + ${vkcv_scene_include}/vkcv/scene/Node.hpp + ${vkcv_scene_source}/vkcv/scene/Node.cpp + + ${vkcv_scene_include}/vkcv/scene/Scene.hpp + ${vkcv_scene_source}/vkcv/scene/Scene.cpp +) + +# adding source files to the module +add_library(vkcv_scene STATIC ${vkcv_scene_sources}) + +# link the required libraries to the module +target_link_libraries(vkcv_scene vkcv) + +# including headers of dependencies and the VkCV framework +target_include_directories(vkcv_scene SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_material_include} ${vkcv_camera_include}) + +# add the own include directory for public headers +target_include_directories(vkcv_scene BEFORE PUBLIC ${vkcv_scene_include}) + +# linking with libraries from all dependencies and the VkCV framework +target_link_libraries(vkcv_scene vkcv vkcv_asset_loader vkcv_material vkcv_camera) diff --git a/modules/scene/include/vkcv/scene/Bounds.hpp b/modules/scene/include/vkcv/scene/Bounds.hpp new file mode 100644 index 0000000000000000000000000000000000000000..07cdf88828d786982b0fe8e7919d543557794c42 --- /dev/null +++ b/modules/scene/include/vkcv/scene/Bounds.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include <array> +#include <iostream> +#include <glm/vec3.hpp> + +namespace vkcv::scene { + + class Bounds { + private: + glm::vec3 m_min; + glm::vec3 m_max; + + public: + Bounds(); + Bounds(const glm::vec3& min, const glm::vec3& max); + ~Bounds() = default; + + Bounds(const Bounds& other) = default; + Bounds(Bounds&& other) = default; + + Bounds& operator=(const Bounds& other) = default; + Bounds& operator=(Bounds&& other) = default; + + void setMin(const glm::vec3& min); + + [[nodiscard]] + const glm::vec3& getMin() const; + + void setMax(const glm::vec3& max); + + [[nodiscard]] + const glm::vec3& getMax() const; + + void setCenter(const glm::vec3& center); + + [[nodiscard]] + glm::vec3 getCenter() const; + + void setSize(const glm::vec3& size); + + [[nodiscard]] + glm::vec3 getSize() const; + + [[nodiscard]] + std::array<glm::vec3, 8> getCorners() const; + + void extend(const glm::vec3& point); + + [[nodiscard]] + bool contains(const glm::vec3& point) const; + + [[nodiscard]] + bool contains(const Bounds& other) const; + + [[nodiscard]] + bool intersects(const Bounds& other) const; + + [[nodiscard]] + explicit operator bool() const; + + [[nodiscard]] + bool operator!() const; + + }; + + std::ostream& operator << (std::ostream& out, const Bounds& bounds); + +} diff --git a/modules/scene/include/vkcv/scene/Frustum.hpp b/modules/scene/include/vkcv/scene/Frustum.hpp new file mode 100644 index 0000000000000000000000000000000000000000..de3917575a9aed32459e6403fab1d6d8fe131b0a --- /dev/null +++ b/modules/scene/include/vkcv/scene/Frustum.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include <glm/mat4x4.hpp> +#include "vkcv/scene/Bounds.hpp" + +namespace vkcv::scene { + + Bounds transformBounds(const glm::mat4& transform, const Bounds& bounds, bool* negative_w = nullptr); + + bool checkFrustum(const glm::mat4& transform, const Bounds& bounds); + +} diff --git a/modules/scene/include/vkcv/scene/Mesh.hpp b/modules/scene/include/vkcv/scene/Mesh.hpp new file mode 100644 index 0000000000000000000000000000000000000000..bc82af4bfabed5e8bfc286bc53cd7b89791726fc --- /dev/null +++ b/modules/scene/include/vkcv/scene/Mesh.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include <glm/mat4x4.hpp> + +#include <vkcv/camera/Camera.hpp> + +#include "MeshPart.hpp" + +namespace vkcv::scene { + + typedef typename event_function<const glm::mat4&, const glm::mat4&, PushConstants&, vkcv::DrawcallInfo&>::type RecordMeshDrawcallFunction; + + class Node; + + class Mesh { + friend class Node; + + private: + Scene& m_scene; + std::vector<MeshPart> m_parts; + std::vector<DrawcallInfo> m_drawcalls; + glm::mat4 m_transform; + Bounds m_bounds; + + explicit Mesh(Scene& scene); + + void load(const asset::Scene& scene, + const asset::Mesh& mesh); + + void recordDrawcalls(const glm::mat4& viewProjection, + PushConstants& pushConstants, + std::vector<DrawcallInfo>& drawcalls, + const RecordMeshDrawcallFunction& record); + + [[nodiscard]] + size_t getDrawcallCount() const; + + public: + ~Mesh(); + + Mesh(const Mesh& other) = default; + Mesh(Mesh&& other) = default; + + Mesh& operator=(const Mesh& other); + Mesh& operator=(Mesh&& other) noexcept; + + [[nodiscard]] + const Bounds& getBounds() const; + + }; + +} diff --git a/modules/scene/include/vkcv/scene/MeshPart.hpp b/modules/scene/include/vkcv/scene/MeshPart.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0d3467c6b57fcece69eb6f0c609c604fb99907d2 --- /dev/null +++ b/modules/scene/include/vkcv/scene/MeshPart.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include <vector> + +#include <vkcv/Buffer.hpp> +#include <vkcv/asset/asset_loader.hpp> +#include <vkcv/material/Material.hpp> + +#include "Bounds.hpp" + +namespace vkcv::scene { + + class Scene; + class Mesh; + + class MeshPart { + friend class Mesh; + + private: + Scene& m_scene; + BufferHandle m_vertices; + std::vector<VertexBufferBinding> m_vertexBindings; + BufferHandle m_indices; + size_t m_indexCount; + Bounds m_bounds; + size_t m_materialIndex; + + explicit MeshPart(Scene& scene); + + void load(const asset::Scene& scene, + const asset::VertexGroup& vertexGroup, + std::vector<DrawcallInfo>& drawcalls); + + public: + ~MeshPart(); + + MeshPart(const MeshPart& other); + MeshPart(MeshPart&& other); + + MeshPart& operator=(const MeshPart& other); + MeshPart& operator=(MeshPart&& other) noexcept; + + [[nodiscard]] + const material::Material& getMaterial() const; + + [[nodiscard]] + const Bounds& getBounds() const; + + explicit operator bool() const; + bool operator!() const; + + }; + +} diff --git a/modules/scene/include/vkcv/scene/Node.hpp b/modules/scene/include/vkcv/scene/Node.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1fcca5b9cbecf1064070d7737d008d2b108371db --- /dev/null +++ b/modules/scene/include/vkcv/scene/Node.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include <vector> + +#include <vkcv/camera/Camera.hpp> + +#include "Bounds.hpp" +#include "Mesh.hpp" + +namespace vkcv::scene { + + class Scene; + + class Node { + friend class Scene; + + private: + Scene& m_scene; + + std::vector<Mesh> m_meshes; + std::vector<Node> m_nodes; + Bounds m_bounds; + + explicit Node(Scene& scene); + + void addMesh(const Mesh& mesh); + + void loadMesh(const asset::Scene& asset_scene, const asset::Mesh& asset_mesh); + + void recordDrawcalls(const glm::mat4& viewProjection, + PushConstants& pushConstants, + std::vector<DrawcallInfo>& drawcalls, + const RecordMeshDrawcallFunction& record); + + void splitMeshesToSubNodes(size_t maxMeshesPerNode); + + [[nodiscard]] + size_t getDrawcallCount() const; + + size_t addNode(); + + Node& getNode(size_t index); + + [[nodiscard]] + const Node& getNode(size_t index) const; + + public: + ~Node(); + + Node(const Node& other) = default; + Node(Node&& other) = default; + + Node& operator=(const Node& other); + Node& operator=(Node&& other) noexcept; + + [[nodiscard]] + const Bounds& getBounds() const; + + }; + +} diff --git a/modules/scene/include/vkcv/scene/Scene.hpp b/modules/scene/include/vkcv/scene/Scene.hpp new file mode 100644 index 0000000000000000000000000000000000000000..429c0bcf729f9afb7dd76cdd58c54931862e1a4a --- /dev/null +++ b/modules/scene/include/vkcv/scene/Scene.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include <filesystem> +#include <mutex> + +#include <vkcv/Core.hpp> +#include <vkcv/Event.hpp> +#include <vkcv/camera/Camera.hpp> +#include <vkcv/material/Material.hpp> + +#include "Node.hpp" + +namespace vkcv::scene { + + class Scene { + friend class MeshPart; + + private: + struct Material { + size_t m_usages; + material::Material m_data; + }; + + Core* m_core; + + std::vector<Material> m_materials; + std::vector<Node> m_nodes; + + explicit Scene(Core* core); + + size_t addNode(); + + Node& getNode(size_t index); + + const Node& getNode(size_t index) const; + + void increaseMaterialUsage(size_t index); + + void decreaseMaterialUsage(size_t index); + + void loadMaterial(size_t index, const asset::Scene& scene, + const asset::Material& material); + + public: + ~Scene(); + + Scene(const Scene& other); + Scene(Scene&& other) noexcept; + + Scene& operator=(const Scene& other); + Scene& operator=(Scene&& other) noexcept; + + size_t getMaterialCount() const; + + [[nodiscard]] + const material::Material& getMaterial(size_t index) const; + + void recordDrawcalls(CommandStreamHandle &cmdStream, + const camera::Camera &camera, + const PassHandle &pass, + const PipelineHandle &pipeline, + size_t pushConstantsSizePerDrawcall, + const RecordMeshDrawcallFunction &record, + const std::vector<ImageHandle> &renderTargets); + + static Scene create(Core& core); + + static Scene load(Core& core, const std::filesystem::path &path); + + }; + +} \ No newline at end of file diff --git a/modules/scene/src/vkcv/scene/Bounds.cpp b/modules/scene/src/vkcv/scene/Bounds.cpp new file mode 100644 index 0000000000000000000000000000000000000000..731d81e928deae4c27f5c857de5b94dc3180888b --- /dev/null +++ b/modules/scene/src/vkcv/scene/Bounds.cpp @@ -0,0 +1,126 @@ + +#include "vkcv/scene/Bounds.hpp" + +namespace vkcv::scene { + + Bounds::Bounds() : + m_min(glm::vec3(0)), + m_max(glm::vec3(0)) {} + + Bounds::Bounds(const glm::vec3 &min, const glm::vec3 &max) : + m_min(min), + m_max(max) + {} + + void Bounds::setMin(const glm::vec3 &min) { + m_min = min; + } + + const glm::vec3 & Bounds::getMin() const { + return m_min; + } + + void Bounds::setMax(const glm::vec3 &max) { + m_max = max; + } + + const glm::vec3 & Bounds::getMax() const { + return m_max; + } + + void Bounds::setCenter(const glm::vec3 ¢er) { + const glm::vec3 size = getSize(); + m_min = center - size / 2.0f; + m_max = center + size / 2.0f; + } + + glm::vec3 Bounds::getCenter() const { + return (m_min + m_max) / 2.0f; + } + + void Bounds::setSize(const glm::vec3 &size) { + const glm::vec3 center = getCenter(); + m_min = center - size / 2.0f; + m_max = center + size / 2.0f; + } + + glm::vec3 Bounds::getSize() const { + return (m_max - m_min); + } + + std::array<glm::vec3, 8> Bounds::getCorners() const { + return { + m_min, + glm::vec3(m_min[0], m_min[1], m_max[2]), + glm::vec3(m_min[0], m_max[1], m_min[2]), + glm::vec3(m_min[0], m_max[1], m_max[2]), + glm::vec3(m_max[0], m_min[1], m_min[2]), + glm::vec3(m_max[0], m_min[1], m_max[2]), + glm::vec3(m_max[0], m_max[1], m_min[2]), + m_max + }; + } + + void Bounds::extend(const glm::vec3 &point) { + m_min = glm::vec3( + std::min(m_min[0], point[0]), + std::min(m_min[1], point[1]), + std::min(m_min[2], point[2]) + ); + + m_max = glm::vec3( + std::max(m_max[0], point[0]), + std::max(m_max[1], point[1]), + std::max(m_max[2], point[2]) + ); + } + + bool Bounds::contains(const glm::vec3 &point) const { + return ( + (point[0] >= m_min[0]) && (point[0] <= m_max[0]) && + (point[1] >= m_min[1]) && (point[1] <= m_max[1]) && + (point[2] >= m_min[2]) && (point[2] <= m_max[2]) + ); + } + + bool Bounds::contains(const Bounds &other) const { + return ( + (other.m_min[0] >= m_min[0]) && (other.m_max[0] <= m_max[0]) && + (other.m_min[1] >= m_min[1]) && (other.m_max[1] <= m_max[1]) && + (other.m_min[2] >= m_min[2]) && (other.m_max[2] <= m_max[2]) + ); + } + + bool Bounds::intersects(const Bounds &other) const { + return ( + (other.m_max[0] >= m_min[0]) && (other.m_min[0] <= m_max[0]) && + (other.m_max[1] >= m_min[1]) && (other.m_min[1] <= m_max[1]) && + (other.m_max[2] >= m_min[2]) && (other.m_min[2] <= m_max[2]) + ); + } + + Bounds::operator bool() const { + return ( + (m_min[0] <= m_max[0]) && + (m_min[1] <= m_max[1]) && + (m_min[2] <= m_max[2]) + ); + } + + bool Bounds::operator!() const { + return ( + (m_min[0] > m_max[0]) || + (m_min[1] > m_max[1]) || + (m_min[2] > m_max[2]) + ); + } + + std::ostream& operator << (std::ostream& out, const Bounds& bounds) { + const auto& min = bounds.getMin(); + const auto& max = bounds.getMax(); + + return out << "[Bounds: (" << min[0] << ", " << min[1] << ", " << min[2] << ") (" + << max[0] << ", " << max[1] << ", " << max[2] << ") ]"; + } + +} diff --git a/modules/scene/src/vkcv/scene/Frustum.cpp b/modules/scene/src/vkcv/scene/Frustum.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1f63eb1d07002d24add81872627777048642dcdb --- /dev/null +++ b/modules/scene/src/vkcv/scene/Frustum.cpp @@ -0,0 +1,73 @@ + +#include "vkcv/scene/Frustum.hpp" + +namespace vkcv::scene { + + static glm::vec3 transformPoint(const glm::mat4& transform, const glm::vec3& point, bool* negative_w) { + const glm::vec4 position = transform * glm::vec4(point, 1.0f); + + + /* + * We divide by the absolute of the 4th coorditnate because + * clipping is weird and points have to move to the other + * side of the camera. + * + * We also need to collect if the 4th coordinate was negative + * to know if all corners are behind the camera. So these can + * be culled as well + */ + if (negative_w) { + const float perspective = std::abs(position[3]); + + *negative_w &= (position[3] < 0.0f); + + return glm::vec3( + position[0] / perspective, + position[1] / perspective, + position[2] / perspective + ); + } else { + return glm::vec3( + position[0], + position[1], + position[2] + ); + } + } + + Bounds transformBounds(const glm::mat4& transform, const Bounds& bounds, bool* negative_w) { + const auto corners = bounds.getCorners(); + + if (negative_w) { + *negative_w = true; + } + + auto projected = transformPoint(transform, corners[0], negative_w); + + Bounds result (projected, projected); + + for (size_t j = 1; j < corners.size(); j++) { + projected = transformPoint(transform, corners[j], negative_w); + result.extend(projected); + } + + return result; + } + + bool checkFrustum(const glm::mat4& transform, const Bounds& bounds) { + static Bounds frustum ( + glm::vec3(-1.0f, -1.0f, -0.0f), + glm::vec3(+1.0f, +1.0f, +1.0f) + ); + + bool negative_w; + auto box = transformBounds(transform, bounds, &negative_w); + + if (negative_w) { + return false; + } else { + return box.intersects(frustum); + } + } + +} diff --git a/modules/scene/src/vkcv/scene/Mesh.cpp b/modules/scene/src/vkcv/scene/Mesh.cpp new file mode 100644 index 0000000000000000000000000000000000000000..af02aedbd71ba4bdfcc30aa7fdcd82796af904f1 --- /dev/null +++ b/modules/scene/src/vkcv/scene/Mesh.cpp @@ -0,0 +1,132 @@ + +#include "vkcv/scene/Mesh.hpp" +#include "vkcv/scene/Scene.hpp" +#include "vkcv/scene/Frustum.hpp" + +namespace vkcv::scene { + + Mesh::Mesh(Scene& scene) : + m_scene(scene) {} + + static glm::mat4 arrayTo4x4Matrix(const std::array<float,16>& array){ + glm::mat4 matrix; + + for (int i = 0; i < 4; i++){ + for (int j = 0; j < 4; j++){ + matrix[i][j] = array[j * 4 + i]; + } + } + + return matrix; + } + + void Mesh::load(const asset::Scene &scene, const asset::Mesh &mesh) { + m_parts.clear(); + m_drawcalls.clear(); + + m_transform = arrayTo4x4Matrix(mesh.modelMatrix); + + for (const auto& vertexGroupIndex : mesh.vertexGroups) { + if ((vertexGroupIndex < 0) || (vertexGroupIndex >= scene.vertexGroups.size())) { + continue; + } + + MeshPart part (m_scene); + part.load(scene, scene.vertexGroups[vertexGroupIndex], m_drawcalls); + + if (!part) { + continue; + } + + auto bounds = transformBounds(m_transform, part.getBounds()); + + if (m_parts.empty()) { + m_bounds = bounds; + } else { + m_bounds.extend(bounds.getMin()); + m_bounds.extend(bounds.getMax()); + } + + m_parts.push_back(part); + } + } + + Mesh::~Mesh() { + m_drawcalls.clear(); + m_parts.clear(); + } + + Mesh &Mesh::operator=(const Mesh &other) { + if (&other == this) { + return *this; + } + + m_parts.resize(other.m_parts.size(), MeshPart(m_scene)); + + for (size_t i = 0; i < m_parts.size(); i++) { + m_parts[i] = other.m_parts[i]; + } + + m_drawcalls = std::vector<DrawcallInfo>(other.m_drawcalls); + m_transform = other.m_transform; + m_bounds = other.m_bounds; + + return *this; + } + + Mesh &Mesh::operator=(Mesh &&other) noexcept { + m_parts.resize(other.m_parts.size(), MeshPart(m_scene)); + + for (size_t i = 0; i < m_parts.size(); i++) { + m_parts[i] = std::move(other.m_parts[i]); + } + + m_drawcalls = std::move(other.m_drawcalls); + m_transform = other.m_transform; + m_bounds = other.m_bounds; + + return *this; + } + + void Mesh::recordDrawcalls(const glm::mat4& viewProjection, + PushConstants& pushConstants, + std::vector<DrawcallInfo>& drawcalls, + const RecordMeshDrawcallFunction& record) { + const glm::mat4 transform = viewProjection * m_transform; + + if (!checkFrustum(viewProjection, m_bounds)) { + return; + } + + if (m_drawcalls.size() == 1) { + drawcalls.push_back(m_drawcalls[0]); + + if (record) { + record(transform, m_transform, pushConstants, drawcalls.back()); + } + } else { + for (size_t i = 0; i < m_parts.size(); i++) { + const MeshPart& part = m_parts[i]; + + if (!checkFrustum(transform, part.getBounds())) { + continue; + } + + drawcalls.push_back(m_drawcalls[i]); + + if (record) { + record(transform, m_transform, pushConstants, drawcalls.back()); + } + } + } + } + + size_t Mesh::getDrawcallCount() const { + return m_drawcalls.size(); + } + + const Bounds& Mesh::getBounds() const { + return m_bounds; + } + +} diff --git a/modules/scene/src/vkcv/scene/MeshPart.cpp b/modules/scene/src/vkcv/scene/MeshPart.cpp new file mode 100644 index 0000000000000000000000000000000000000000..46e79897719d5422151ec31837a41f7e58324a71 --- /dev/null +++ b/modules/scene/src/vkcv/scene/MeshPart.cpp @@ -0,0 +1,158 @@ + +#include "vkcv/scene/MeshPart.hpp" +#include "vkcv/scene/Scene.hpp" + +namespace vkcv::scene { + + MeshPart::MeshPart(Scene& scene) : + m_scene(scene), + m_vertices(), + m_vertexBindings(), + m_indices(), + m_indexCount(0), + m_bounds(), + m_materialIndex(std::numeric_limits<size_t>::max()) {} + + void MeshPart::load(const asset::Scene& scene, + const asset::VertexGroup &vertexGroup, + std::vector<DrawcallInfo>& drawcalls) { + Core& core = *(m_scene.m_core); + + auto vertexBuffer = core.createBuffer<uint8_t>( + BufferType::VERTEX, vertexGroup.vertexBuffer.data.size() + ); + + vertexBuffer.fill(vertexGroup.vertexBuffer.data); + m_vertices = vertexBuffer.getHandle(); + + auto attributes = vertexGroup.vertexBuffer.attributes; + + std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) { + return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type); + }); + + for (const auto& attribute : attributes) { + m_vertexBindings.emplace_back(attribute.offset, vertexBuffer.getVulkanHandle()); + } + + auto indexBuffer = core.createBuffer<uint8_t>( + BufferType::INDEX, vertexGroup.indexBuffer.data.size() + ); + + indexBuffer.fill(vertexGroup.indexBuffer.data); + m_indices = indexBuffer.getHandle(); + m_indexCount = vertexGroup.numIndices; + + m_bounds.setMin(glm::vec3( + vertexGroup.min.x, + vertexGroup.min.y, + vertexGroup.min.z + )); + + m_bounds.setMax(glm::vec3( + vertexGroup.max.x, + vertexGroup.max.y, + vertexGroup.max.z + )); + + if ((vertexGroup.materialIndex >= 0) && + (vertexGroup.materialIndex < scene.materials.size())) { + m_materialIndex = vertexGroup.materialIndex; + + if (!getMaterial()) { + m_scene.loadMaterial(m_materialIndex, scene, scene.materials[vertexGroup.materialIndex]); + } + + m_scene.increaseMaterialUsage(m_materialIndex); + } else { + m_materialIndex = std::numeric_limits<size_t>::max(); + } + + if (*this) { + const auto& material = getMaterial(); + const auto& descriptorSet = core.getDescriptorSet(material.getDescriptorSet()); + + drawcalls.push_back(DrawcallInfo( + vkcv::Mesh(m_vertexBindings, indexBuffer.getVulkanHandle(), m_indexCount), + { DescriptorSetUsage(0, descriptorSet.vulkanHandle) } + )); + } + } + + MeshPart::~MeshPart() { + m_scene.decreaseMaterialUsage(m_materialIndex); + } + + MeshPart::MeshPart(const MeshPart &other) : + m_scene(other.m_scene), + m_vertices(other.m_vertices), + m_vertexBindings(other.m_vertexBindings), + m_indices(other.m_indices), + m_indexCount(other.m_indexCount), + m_bounds(other.m_bounds), + m_materialIndex(other.m_materialIndex) { + m_scene.increaseMaterialUsage(m_materialIndex); + } + + MeshPart::MeshPart(MeshPart &&other) : + m_scene(other.m_scene), + m_vertices(other.m_vertices), + m_vertexBindings(other.m_vertexBindings), + m_indices(other.m_indices), + m_indexCount(other.m_indexCount), + m_bounds(other.m_bounds), + m_materialIndex(other.m_materialIndex) { + m_scene.increaseMaterialUsage(m_materialIndex); + } + + MeshPart &MeshPart::operator=(const MeshPart &other) { + if (&other == this) { + return *this; + } + + m_vertices = other.m_vertices; + m_vertexBindings = other.m_vertexBindings; + m_indices = other.m_indices; + m_indexCount = other.m_indexCount; + m_bounds = other.m_bounds; + m_materialIndex = other.m_materialIndex; + + return *this; + } + + MeshPart &MeshPart::operator=(MeshPart &&other) noexcept { + m_vertices = other.m_vertices; + m_vertexBindings = other.m_vertexBindings; + m_indices = other.m_indices; + m_indexCount = other.m_indexCount; + m_bounds = other.m_bounds; + m_materialIndex = other.m_materialIndex; + + return *this; + } + + const material::Material & MeshPart::getMaterial() const { + return m_scene.getMaterial(m_materialIndex); + } + + MeshPart::operator bool() const { + return ( + (getMaterial()) && + (m_vertices) && + (m_indices) + ); + } + + bool MeshPart::operator!() const { + return ( + (!getMaterial()) || + (!m_vertices) || + (!m_indices) + ); + } + + const Bounds &MeshPart::getBounds() const { + return m_bounds; + } + +} diff --git a/modules/scene/src/vkcv/scene/Node.cpp b/modules/scene/src/vkcv/scene/Node.cpp new file mode 100644 index 0000000000000000000000000000000000000000..24f62d18e160c7d80f82384829a2130737737ba9 --- /dev/null +++ b/modules/scene/src/vkcv/scene/Node.cpp @@ -0,0 +1,189 @@ + +#include "vkcv/scene/Node.hpp" +#include "vkcv/scene/Scene.hpp" +#include "vkcv/scene/Frustum.hpp" + +#include <algorithm> + +namespace vkcv::scene { + + Node::Node(Scene& scene) : + m_scene(scene), + m_meshes(), + m_nodes(), + m_bounds() {} + + Node::~Node() { + m_nodes.clear(); + m_meshes.clear(); + } + + Node &Node::operator=(const Node &other) { + if (&other == this) { + return *this; + } + + m_meshes.resize(other.m_meshes.size(), Mesh(m_scene)); + + for (size_t i = 0; i < m_meshes.size(); i++) { + m_meshes[i] = other.m_meshes[i]; + } + + m_nodes.resize(other.m_nodes.size(), Node(m_scene)); + + for (size_t i = 0; i < m_nodes.size(); i++) { + m_nodes[i] = other.m_nodes[i]; + } + + m_bounds = other.m_bounds; + + return *this; + } + + Node &Node::operator=(Node &&other) noexcept { + m_meshes.resize(other.m_meshes.size(), Mesh(m_scene)); + + for (size_t i = 0; i < m_meshes.size(); i++) { + m_meshes[i] = std::move(other.m_meshes[i]); + } + + m_nodes.resize(other.m_nodes.size(), Node(m_scene)); + + for (size_t i = 0; i < m_nodes.size(); i++) { + m_nodes[i] = std::move(other.m_nodes[i]); + } + + m_bounds = other.m_bounds; + + return *this; + } + + void Node::addMesh(const Mesh& mesh) { + if (m_meshes.empty()) { + m_bounds = mesh.getBounds(); + } else { + m_bounds.extend(mesh.getBounds().getMin()); + m_bounds.extend(mesh.getBounds().getMax()); + } + + m_meshes.push_back(mesh); + } + + void Node::loadMesh(const asset::Scene &asset_scene, const asset::Mesh &asset_mesh) { + Mesh mesh (m_scene); + mesh.load(asset_scene, asset_mesh); + addMesh(mesh); + } + + size_t Node::addNode() { + const Node node (m_scene); + const size_t index = m_nodes.size(); + m_nodes.push_back(node); + return index; + } + + Node& Node::getNode(size_t index) { + return m_nodes[index]; + } + + const Node& Node::getNode(size_t index) const { + return m_nodes[index]; + } + + void Node::recordDrawcalls(const glm::mat4& viewProjection, + PushConstants& pushConstants, + std::vector<DrawcallInfo>& drawcalls, + const RecordMeshDrawcallFunction& record) { + if (!checkFrustum(viewProjection, m_bounds)) { + return; + } + + for (auto& mesh : m_meshes) { + mesh.recordDrawcalls(viewProjection, pushConstants, drawcalls, record); + } + + for (auto& node : m_nodes) { + node.recordDrawcalls(viewProjection, pushConstants, drawcalls, record); + } + } + + void Node::splitMeshesToSubNodes(size_t maxMeshesPerNode) { + if (m_meshes.size() <= maxMeshesPerNode) { + return; + } + + const auto split = m_bounds.getCenter(); + int axis = 0; + + const auto size = m_bounds.getSize(); + + if (size[1] > size[0]) { + if (size[2] > size[1]) { + axis = 2; + } else { + axis = 1; + } + } else + if (size[2] > size[0]) { + axis = 2; + } + + std::vector<size_t> left_meshes; + std::vector<size_t> right_meshes; + + for (size_t i = 0; i < m_meshes.size(); i++) { + const auto& bounds = m_meshes[i].getBounds(); + + if (bounds.getMax()[axis] <= split[axis]) { + left_meshes.push_back(i); + } else + if (bounds.getMin()[axis] >= split[axis]) { + right_meshes.push_back(i); + } + } + + if ((left_meshes.empty()) || (right_meshes.empty())) { + return; + } + + const size_t left = addNode(); + const size_t right = addNode(); + + for (size_t i : left_meshes) { + getNode(left).addMesh(m_meshes[i]); + } + + for (size_t i : right_meshes) { + getNode(right).addMesh(m_meshes[i]); + left_meshes.push_back(i); + } + + std::sort(left_meshes.begin(), left_meshes.end(), std::greater()); + + for (size_t i : left_meshes) { + m_meshes.erase(m_meshes.begin() + static_cast<long>(i)); + } + + getNode(left).splitMeshesToSubNodes(maxMeshesPerNode); + getNode(right).splitMeshesToSubNodes(maxMeshesPerNode); + } + + size_t Node::getDrawcallCount() const { + size_t count = 0; + + for (auto& mesh : m_meshes) { + count += mesh.getDrawcallCount(); + } + + for (auto& node : m_nodes) { + count += node.getDrawcallCount(); + } + + return count; + } + + const Bounds& Node::getBounds() const { + return m_bounds; + } + +} diff --git a/modules/scene/src/vkcv/scene/Scene.cpp b/modules/scene/src/vkcv/scene/Scene.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d6fa2a40a494ef57386e52a306e962a460c66dd6 --- /dev/null +++ b/modules/scene/src/vkcv/scene/Scene.cpp @@ -0,0 +1,273 @@ + +#include "vkcv/scene/Scene.hpp" + +#include <vkcv/Logger.hpp> +#include <vkcv/asset/asset_loader.hpp> + +namespace vkcv::scene { + + Scene::Scene(Core* core) : + m_core(core), + m_materials(), + m_nodes() {} + + Scene::~Scene() { + m_nodes.clear(); + m_materials.clear(); + } + + Scene::Scene(const Scene &other) : + m_core(other.m_core), + m_materials(other.m_materials), + m_nodes() { + m_nodes.resize(other.m_nodes.size(), Node(*this)); + + for (size_t i = 0; i < m_nodes.size(); i++) { + m_nodes[i] = other.m_nodes[i]; + } + } + + Scene::Scene(Scene &&other) noexcept : + m_core(other.m_core), + m_materials(other.m_materials), + m_nodes() { + m_nodes.resize(other.m_nodes.size(), Node(*this)); + + for (size_t i = 0; i < m_nodes.size(); i++) { + m_nodes[i] = std::move(other.m_nodes[i]); + } + } + + Scene &Scene::operator=(const Scene &other) { + if (&other == this) { + return *this; + } + + m_core = other.m_core; + m_materials = std::vector<Material>(other.m_materials); + + m_nodes.resize(other.m_nodes.size(), Node(*this)); + + for (size_t i = 0; i < m_nodes.size(); i++) { + m_nodes[i] = other.m_nodes[i]; + } + + return *this; + } + + Scene &Scene::operator=(Scene &&other) noexcept { + m_core = other.m_core; + m_materials = std::move(other.m_materials); + + m_nodes.resize(other.m_nodes.size(), Node(*this)); + + for (size_t i = 0; i < m_nodes.size(); i++) { + m_nodes[i] = std::move(other.m_nodes[i]); + } + + return *this; + } + + size_t Scene::addNode() { + const Node node (*this); + const size_t index = m_nodes.size(); + m_nodes.push_back(node); + return index; + } + + Node& Scene::getNode(size_t index) { + return m_nodes[index]; + } + + const Node& Scene::getNode(size_t index) const { + return m_nodes[index]; + } + + void Scene::increaseMaterialUsage(size_t index) { + if (index < m_materials.size()) { + m_materials[index].m_usages++; + } + } + + void Scene::decreaseMaterialUsage(size_t index) { + if ((index < m_materials.size()) && (m_materials[index].m_usages > 0)) { + m_materials[index].m_usages--; + } + } + + size_t Scene::getMaterialCount() const { + return m_materials.size(); + } + + const material::Material & Scene::getMaterial(size_t index) const { + static material::Material noMaterial; + + if (index >= m_materials.size()) { + return noMaterial; + } + + return m_materials[index].m_data; + } + + void Scene::recordDrawcalls(CommandStreamHandle &cmdStream, + const camera::Camera &camera, + const PassHandle &pass, + const PipelineHandle &pipeline, + size_t pushConstantsSizePerDrawcall, + const RecordMeshDrawcallFunction &record, + const std::vector<ImageHandle> &renderTargets) { + PushConstants pushConstants (pushConstantsSizePerDrawcall); + std::vector<DrawcallInfo> drawcalls; + size_t count = 0; + + const glm::mat4 viewProjection = camera.getMVP(); + + for (auto& node : m_nodes) { + count += node.getDrawcallCount(); + node.recordDrawcalls(viewProjection, pushConstants, drawcalls, record); + } + + vkcv_log(LogLevel::RAW_INFO, "Frustum culling: %lu / %lu", drawcalls.size(), count); + + m_core->recordDrawcallsToCmdStream( + cmdStream, + pass, + pipeline, + pushConstants, + drawcalls, + renderTargets + ); + } + + Scene Scene::create(Core& core) { + return Scene(&core); + } + + static void loadImage(Core& core, const asset::Scene& asset_scene, + const asset::Texture& asset_texture, + const vk::Format& format, + ImageHandle& image, SamplerHandle& sampler) { + asset::Sampler* asset_sampler = nullptr; + + if ((asset_texture.sampler >= 0) && (asset_texture.sampler < asset_scene.samplers.size())) { + //asset_sampler = &(asset_scene.samplers[asset_texture.sampler]); // TODO + } + + Image img = core.createImage(format, asset_texture.w, asset_texture.h); + img.fill(asset_texture.data.data()); + image = img.getHandle(); + + if (asset_sampler) { + //sampler = core.createSampler(asset_sampler) // TODO + } else { + sampler = core.createSampler( + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerMipmapMode::LINEAR, + vkcv::SamplerAddressMode::REPEAT + ); + } + } + + void Scene::loadMaterial(size_t index, const asset::Scene& scene, + const asset::Material& material) { + if (index >= m_materials.size()) { + return; + } + + ImageHandle diffuseImg; + SamplerHandle diffuseSmp; + + if ((material.baseColor >= 0) && (material.baseColor < scene.textures.size())) { + loadImage(*m_core, scene, scene.textures[material.baseColor], vk::Format::eR8G8B8A8Srgb, + diffuseImg,diffuseSmp); + } + + ImageHandle normalImg; + SamplerHandle normalSmp; + + if ((material.baseColor >= 0) && (material.baseColor < scene.textures.size())) { + loadImage(*m_core, scene, scene.textures[material.baseColor], vk::Format::eR8G8B8A8Srgb, + diffuseImg,diffuseSmp); + } + + ImageHandle metalRoughImg; + SamplerHandle metalRoughSmp; + + if ((material.baseColor >= 0) && (material.baseColor < scene.textures.size())) { + loadImage(*m_core, scene, scene.textures[material.baseColor], vk::Format::eR8G8B8A8Srgb, + diffuseImg,diffuseSmp); + } + + ImageHandle occlusionImg; + SamplerHandle occlusionSmp; + + if ((material.baseColor >= 0) && (material.baseColor < scene.textures.size())) { + loadImage(*m_core, scene, scene.textures[material.baseColor], vk::Format::eR8G8B8A8Srgb, + diffuseImg,diffuseSmp); + } + + ImageHandle emissionImg; + SamplerHandle emissionSmp; + + if ((material.baseColor >= 0) && (material.baseColor < scene.textures.size())) { + loadImage(*m_core, scene, scene.textures[material.baseColor], vk::Format::eR8G8B8A8Srgb, + diffuseImg,diffuseSmp); + } + + const float colorFactors [4] = { + material.baseColorFactor.r, + material.baseColorFactor.g, + material.baseColorFactor.b, + material.baseColorFactor.a + }; + + const float emissionFactors[4] = { + material.emissiveFactor.r, + material.emissiveFactor.g, + material.emissiveFactor.b + }; + + m_materials[index].m_data = material::Material::createPBR( + *m_core, + diffuseImg, diffuseSmp, + normalImg, normalSmp, + metalRoughImg, metalRoughSmp, + occlusionImg, occlusionSmp, + emissionImg, emissionSmp, + colorFactors, + material.normalScale, + material.metallicFactor, + material.roughnessFactor, + material.occlusionStrength, + emissionFactors + ); + } + + Scene Scene::load(Core& core, const std::filesystem::path &path) { + asset::Scene asset_scene; + + if (!asset::loadScene(path.string(), asset_scene)) { + vkcv_log(LogLevel::ERROR, "Scene could not be loaded (%s)", path.c_str()); + return create(core); + } + + Scene scene = create(core); + + for (const auto& material : asset_scene.materials) { + scene.m_materials.push_back({ + 0, material::Material() + }); + } + + const size_t root = scene.addNode(); + + for (const auto& mesh : asset_scene.meshes) { + scene.getNode(root).loadMesh(asset_scene, mesh); + } + + scene.getNode(root).splitMeshesToSubNodes(128); + return scene; + } + +} diff --git a/modules/shader_compiler/CMakeLists.txt b/modules/shader_compiler/CMakeLists.txt index 62e00d5fdc181535fa4f1c981e772e268a116f20..11c1e460575709dd9c9c16fdd02b6b923cc33045 100644 --- a/modules/shader_compiler/CMakeLists.txt +++ b/modules/shader_compiler/CMakeLists.txt @@ -10,6 +10,9 @@ set(vkcv_shader_compiler_include ${PROJECT_SOURCE_DIR}/include) # Add source and header files to the module set(vkcv_shader_compiler_sources + ${vkcv_shader_compiler_include}/vkcv/shader/Compiler.hpp + ${vkcv_shader_compiler_source}/vkcv/shader/Compiler.cpp + ${vkcv_shader_compiler_include}/vkcv/shader/GLSLCompiler.hpp ${vkcv_shader_compiler_source}/vkcv/shader/GLSLCompiler.cpp ) diff --git a/modules/shader_compiler/include/vkcv/shader/Compiler.hpp b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp index d7b7af7178531aea358cecbc8b86a29527173014..5b119ca5c68f997bacfbea6c60d5c965f9a7a54e 100644 --- a/modules/shader_compiler/include/vkcv/shader/Compiler.hpp +++ b/modules/shader_compiler/include/vkcv/shader/Compiler.hpp @@ -1,6 +1,11 @@ #pragma once +#include <filesystem> +#include <string> +#include <unordered_map> + #include <vkcv/Event.hpp> +#include <vkcv/ShaderStage.hpp> namespace vkcv::shader { @@ -8,10 +13,21 @@ namespace vkcv::shader { class Compiler { private: + protected: + std::unordered_map<std::string, std::string> m_defines; + public: + virtual bool compileSource(ShaderStage shaderStage, const char* shaderSource, + const ShaderCompiledFunction& compiled, + const std::filesystem::path& includePath) = 0; + virtual void compile(ShaderStage shaderStage, const std::filesystem::path& shaderPath, - const ShaderCompiledFunction& compiled, bool update = false) = 0; + const ShaderCompiledFunction& compiled, + const std::filesystem::path& includePath, bool update) = 0; + + std::string getDefine(const std::string& name) const; + void setDefine(const std::string& name, const std::string& value); }; } diff --git a/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp index 7105d93a0c3e153bf3abe1d624d0c13c6f09ac6d..eca84def118625e21df1c645cfc71b6bcddf7393 100644 --- a/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp +++ b/modules/shader_compiler/include/vkcv/shader/GLSLCompiler.hpp @@ -7,7 +7,7 @@ namespace vkcv::shader { - class GLSLCompiler { + class GLSLCompiler : public Compiler { private: public: GLSLCompiler(); @@ -20,8 +20,13 @@ namespace vkcv::shader { GLSLCompiler& operator=(const GLSLCompiler& other); GLSLCompiler& operator=(GLSLCompiler&& other) = default; + bool compileSource(ShaderStage shaderStage, const char* shaderSource, + const ShaderCompiledFunction& compiled, + const std::filesystem::path& includePath); + void compile(ShaderStage shaderStage, const std::filesystem::path& shaderPath, - const ShaderCompiledFunction& compiled, bool update = false); + const ShaderCompiledFunction& compiled, + const std::filesystem::path& includePath = "", bool update = false) override; }; diff --git a/modules/shader_compiler/src/vkcv/shader/Compiler.cpp b/modules/shader_compiler/src/vkcv/shader/Compiler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f5ec0435ca8b82dc5f328921f43a39338d1be456 --- /dev/null +++ b/modules/shader_compiler/src/vkcv/shader/Compiler.cpp @@ -0,0 +1,14 @@ + +#include "vkcv/shader/Compiler.hpp" + +namespace vkcv::shader { + + std::string Compiler::getDefine(const std::string &name) const { + return m_defines.at(name); + } + + void Compiler::setDefine(const std::string &name, const std::string &value) { + m_defines[name] = value; + } + +} diff --git a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp index ec358188b8e871da6f4d62ffd397f32bfb795ee2..16067aebedfda8793a0096803ba5344275bcbbcd 100644 --- a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp +++ b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp @@ -2,16 +2,18 @@ #include "vkcv/shader/GLSLCompiler.hpp" #include <fstream> +#include <sstream> #include <glslang/SPIRV/GlslangToSpv.h> #include <glslang/StandAlone/DirStackFileIncluder.h> +#include <vkcv/File.hpp> #include <vkcv/Logger.hpp> namespace vkcv::shader { static uint32_t s_CompilerCount = 0; - GLSLCompiler::GLSLCompiler() { + GLSLCompiler::GLSLCompiler() : Compiler() { if (s_CompilerCount == 0) { glslang::InitializeProcess(); } @@ -19,7 +21,7 @@ namespace vkcv::shader { s_CompilerCount++; } - GLSLCompiler::GLSLCompiler(const GLSLCompiler &other) { + GLSLCompiler::GLSLCompiler(const GLSLCompiler &other) : Compiler(other) { s_CompilerCount++; } @@ -50,6 +52,10 @@ namespace vkcv::shader { return EShLangFragment; case ShaderStage::COMPUTE: return EShLangCompute; + case ShaderStage::TASK: + return EShLangTaskNV; + case ShaderStage::MESH: + return EShLangMeshNV; default: return EShLangCount; } @@ -197,22 +203,45 @@ namespace vkcv::shader { return true; } - void GLSLCompiler::compile(ShaderStage shaderStage, const std::filesystem::path &shaderPath, - const ShaderCompiledFunction& compiled, bool update) { + bool GLSLCompiler::compileSource(ShaderStage shaderStage, const char* shaderSource, + const ShaderCompiledFunction &compiled, + const std::filesystem::path& includePath) { const EShLanguage language = findShaderLanguage(shaderStage); if (language == EShLangCount) { - vkcv_log(LogLevel::ERROR, "Shader stage not supported (%s)", shaderPath.string().c_str()); - return; + vkcv_log(LogLevel::ERROR, "Shader stage not supported"); + return false; } - const std::vector<char> code = readShaderCode(shaderPath); - glslang::TShader shader (language); glslang::TProgram program; + std::string source (shaderSource); + + if (!m_defines.empty()) { + std::ostringstream defines; + for (const auto& define : m_defines) { + defines << "#define " << define.first << " " << define.second << std::endl; + } + + size_t pos = source.find("#version") + 8; + if (pos >= source.length()) { + pos = 0; + } + + const size_t epos = source.find_last_of("#extension", pos) + 10; + if (epos < source.length()) { + pos = epos; + } + + const auto defines_str = defines.str(); + + pos = source.find('\n', pos) + 1; + source = source.insert(pos, defines_str); + } + const char *shaderStrings [1]; - shaderStrings[0] = code.data(); + shaderStrings[0] = source.c_str(); shader.setStrings(shaderStrings, 1); @@ -222,51 +251,53 @@ namespace vkcv::shader { const auto messages = (EShMessages)( EShMsgSpvRules | EShMsgVulkanRules - ); + ); std::string preprocessedGLSL; DirStackFileIncluder includer; - includer.pushExternalLocalDirectory(shaderPath.parent_path().string()); + includer.pushExternalLocalDirectory(includePath.string()); - if (!shader.preprocess(&resources, 100, ENoProfile, false, false, messages, &preprocessedGLSL, includer)) { - vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n} (%s)", - shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str()); - return; + if (!shader.preprocess(&resources, 100, ENoProfile, + false, false, + messages, &preprocessedGLSL, includer)) { + vkcv_log(LogLevel::ERROR, "Shader preprocessing failed {\n%s\n%s\n}", + shader.getInfoLog(), shader.getInfoDebugLog()); + return false; } const char* preprocessedCString = preprocessedGLSL.c_str(); shader.setStrings(&preprocessedCString, 1); if (!shader.parse(&resources, 100, false, messages)) { - vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n} (%s)", - shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str()); - return; + vkcv_log(LogLevel::ERROR, "Shader parsing failed {\n%s\n%s\n}", + shader.getInfoLog(), shader.getInfoDebugLog()); + return false; } program.addShader(&shader); if (!program.link(messages)) { - vkcv_log(LogLevel::ERROR, "Shader linking failed {\n%s\n%s\n} (%s)", - shader.getInfoLog(), shader.getInfoDebugLog(), shaderPath.string().c_str()); - return; + vkcv_log(LogLevel::ERROR, "Shader linking failed {\n%s\n%s\n}", + shader.getInfoLog(), shader.getInfoDebugLog()); + return false; } const glslang::TIntermediate* intermediate = program.getIntermediate(language); if (!intermediate) { - vkcv_log(LogLevel::ERROR, "No valid intermediate representation (%s)", shaderPath.string().c_str()); - return; + vkcv_log(LogLevel::ERROR, "No valid intermediate representation"); + return false; } std::vector<uint32_t> spirv; glslang::GlslangToSpv(*intermediate, spirv); - const std::filesystem::path tmp_path (std::tmpnam(nullptr)); + const std::filesystem::path tmp_path = generateTemporaryFilePath(); if (!writeSpirvCode(tmp_path, spirv)) { - vkcv_log(LogLevel::ERROR, "Spir-V could not be written to disk (%s)", shaderPath.string().c_str()); - return; + vkcv_log(LogLevel::ERROR, "Spir-V could not be written to disk"); + return false; } if (compiled) { @@ -274,6 +305,24 @@ namespace vkcv::shader { } std::filesystem::remove(tmp_path); + return true; + } + + void GLSLCompiler::compile(ShaderStage shaderStage, const std::filesystem::path &shaderPath, + const ShaderCompiledFunction& compiled, + const std::filesystem::path& includePath, bool update) { + const std::vector<char> code = readShaderCode(shaderPath); + bool result; + + if (!includePath.empty()) { + result = compileSource(shaderStage, code.data(), compiled, includePath); + } else { + result = compileSource(shaderStage, code.data(), compiled, shaderPath.parent_path()); + } + + if (!result) { + vkcv_log(LogLevel::ERROR, "Shader compilation failed: (%s)", shaderPath.string().c_str()); + } if (update) { // TODO: Shader hot compilation during runtime diff --git a/modules/upscaling/CMakeLists.txt b/modules/upscaling/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..0767e5c4d2d60c001ac9d6792efcd623456284a8 --- /dev/null +++ b/modules/upscaling/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.16) +project(vkcv_upscaling) + +# setting c++ standard for the project +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(vkcv_upscaling_source ${PROJECT_SOURCE_DIR}/src) +set(vkcv_upscaling_include ${PROJECT_SOURCE_DIR}/include) + +set(vkcv_upscaling_sources + ${vkcv_upscaling_include}/vkcv/upscaling/Upscaling.hpp + ${vkcv_upscaling_source}/vkcv/upscaling/Upscaling.cpp + + ${vkcv_upscaling_include}/vkcv/upscaling/BilinearUpscaling.hpp + ${vkcv_upscaling_source}/vkcv/upscaling/BilinearUpscaling.cpp + + ${vkcv_upscaling_include}/vkcv/upscaling/FSRUpscaling.hpp + ${vkcv_upscaling_source}/vkcv/upscaling/FSRUpscaling.cpp +) + +# Setup some path variables to load libraries +set(vkcv_upscaling_lib lib) +set(vkcv_upscaling_lib_path ${PROJECT_SOURCE_DIR}/${vkcv_upscaling_lib}) + +# Check and load FidelityFX_FSR +include(config/FidelityFX_FSR.cmake) + +# adding source files to the project +add_library(vkcv_upscaling STATIC ${vkcv_upscaling_sources}) + +# link the required libraries to the module +target_link_libraries(vkcv_upscaling ${vkcv_upscaling_libraries} vkcv vkcv_shader_compiler) + +# including headers of dependencies and the VkCV framework +target_include_directories(vkcv_upscaling SYSTEM BEFORE PRIVATE ${vkcv_upscaling_includes} ${vkcv_include} ${vkcv_shader_compiler_include}) + +# add the own include directory for public headers +target_include_directories(vkcv_upscaling BEFORE PUBLIC ${vkcv_upscaling_include}) diff --git a/modules/upscaling/config/FidelityFX_FSR.cmake b/modules/upscaling/config/FidelityFX_FSR.cmake new file mode 100644 index 0000000000000000000000000000000000000000..cc52b4189f781f534a933feb7b782b6bec333e5a --- /dev/null +++ b/modules/upscaling/config/FidelityFX_FSR.cmake @@ -0,0 +1,18 @@ + +if (EXISTS "${vkcv_upscaling_lib_path}/FidelityFX-FSR") + include_shader(${vkcv_upscaling_lib_path}/FidelityFX-FSR/ffx-fsr/ffx_a.h ${vkcv_upscaling_include} ${vkcv_upscaling_source}) + include_shader(${vkcv_upscaling_lib_path}/FidelityFX-FSR/ffx-fsr/ffx_fsr1.h ${vkcv_upscaling_include} ${vkcv_upscaling_source}) + include_shader(${vkcv_upscaling_lib_path}/FidelityFX-FSR/sample/src/VK/FSR_Pass.glsl ${vkcv_upscaling_include} ${vkcv_upscaling_source}) + + list(APPEND vkcv_upscaling_includes ${vkcv_upscaling_lib}/FidelityFX-FSR/ffx-fsr) + + list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_source}/ffx_a.h.cxx) + list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_source}/ffx_fsr1.h.cxx) + list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_source}/FSR_Pass.glsl.cxx) + + list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_include}/ffx_a.h.hxx) + list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_include}/ffx_fsr1.h.hxx) + list(APPEND vkcv_upscaling_sources ${vkcv_upscaling_include}/FSR_Pass.glsl.hxx) +else() + message(WARNING "FidelityFX-FSR is required..! Update the submodules!") +endif () diff --git a/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp new file mode 100644 index 0000000000000000000000000000000000000000..52124dc8e36bee7ef7c00de6afcf3457296a7623 --- /dev/null +++ b/modules/upscaling/include/vkcv/upscaling/BilinearUpscaling.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "Upscaling.hpp" + +namespace vkcv::upscaling { + + class BilinearUpscaling : public Upscaling { + private: + public: + BilinearUpscaling(Core& core); + + void recordUpscaling(const CommandStreamHandle& cmdStream, + const ImageHandle& input, + const ImageHandle& output) override; + + }; + +} diff --git a/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2a1338b85e3ee60a33215157aaaa15817f2db97f --- /dev/null +++ b/modules/upscaling/include/vkcv/upscaling/FSRUpscaling.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include "Upscaling.hpp" + +#include <vkcv/ShaderProgram.hpp> + +namespace vkcv::upscaling { + + enum class FSRQualityMode : int { + NONE = 0, + ULTRA_QUALITY = 1, + QUALITY = 2, + BALANCED = 3, + PERFORMANCE = 4 + }; + + void getFSRResolution(FSRQualityMode mode, + uint32_t outputWidth, uint32_t outputHeight, + uint32_t &inputWidth, uint32_t &inputHeight); + + float getFSRLodBias(FSRQualityMode mode); + + struct FSRConstants { + uint32_t Const0 [4]; + uint32_t Const1 [4]; + uint32_t Const2 [4]; + uint32_t Const3 [4]; + uint32_t Sample [4]; + }; + + class FSRUpscaling : public Upscaling { + private: + PipelineHandle m_easuPipeline; + PipelineHandle m_rcasPipeline; + + DescriptorSetHandle m_easuDescriptorSet; + DescriptorSetHandle m_rcasDescriptorSet; + + Buffer<FSRConstants> m_easuConstants; + Buffer<FSRConstants> m_rcasConstants; + ImageHandle m_intermediateImage; + SamplerHandle m_sampler; + + bool m_hdr; + + /** + * Sharpness will calculate the rcasAttenuation value + * which should be between 0.0f and 2.0f (default: 0.25f). + * + * rcasAttenuation = (1.0f - sharpness) * 2.0f + * + * So the default value for sharpness should be 0.875f. + * + * Beware that 0.0f or any negative value of sharpness will + * disable the rcas pass completely. + */ + float m_sharpness; + + public: + explicit FSRUpscaling(Core& core); + + void recordUpscaling(const CommandStreamHandle& cmdStream, + const ImageHandle& input, + const ImageHandle& output) override; + + [[nodiscard]] + bool isHdrEnabled() const; + + void setHdrEnabled(bool enabled); + + [[nodiscard]] + float getSharpness() const; + + void setSharpness(float sharpness); + + }; + +} diff --git a/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp b/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c44e878ad78f0a3359599c76f781371505fd3a85 --- /dev/null +++ b/modules/upscaling/include/vkcv/upscaling/Upscaling.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include <vkcv/Core.hpp> +#include <vkcv/Handles.hpp> + +namespace vkcv::upscaling { + + class Upscaling { + protected: + Core& m_core; + + public: + Upscaling(Core& core); + + ~Upscaling() = default; + + virtual void recordUpscaling(const CommandStreamHandle& cmdStream, + const ImageHandle& input, + const ImageHandle& output) = 0; + + }; + +} diff --git a/modules/upscaling/lib/FidelityFX-FSR b/modules/upscaling/lib/FidelityFX-FSR new file mode 160000 index 0000000000000000000000000000000000000000..bcffc8171efb80e265991301a49670ed755088dd --- /dev/null +++ b/modules/upscaling/lib/FidelityFX-FSR @@ -0,0 +1 @@ +Subproject commit bcffc8171efb80e265991301a49670ed755088dd diff --git a/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9c36acf5d050e3f4f19223020357b6c32534a2de --- /dev/null +++ b/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp @@ -0,0 +1,13 @@ + +#include "vkcv/upscaling/BilinearUpscaling.hpp" + +namespace vkcv::upscaling { + + BilinearUpscaling::BilinearUpscaling(Core &core) : Upscaling(core) {} + + void BilinearUpscaling::recordUpscaling(const CommandStreamHandle &cmdStream, const ImageHandle &input, + const ImageHandle &output) { + m_core.recordBlitImage(cmdStream, input, output, SamplerFilterType::LINEAR); + } + +} diff --git a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp new file mode 100644 index 0000000000000000000000000000000000000000..460a6d0b459fe7d1d2a917a62138fea2e5a40908 --- /dev/null +++ b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp @@ -0,0 +1,382 @@ + +#include "vkcv/upscaling/FSRUpscaling.hpp" + +#include <stdint.h> +#include <math.h> + +#define A_CPU 1 +#include <ffx_a.h> +#include <ffx_fsr1.h> + +#include "ffx_a.h.hxx" +#include "ffx_fsr1.h.hxx" +#include "FSR_Pass.glsl.hxx" + +#include <vkcv/File.hpp> +#include <vkcv/Logger.hpp> +#include <vkcv/shader/GLSLCompiler.hpp> + +namespace vkcv::upscaling { + + void getFSRResolution(FSRQualityMode mode, + uint32_t outputWidth, uint32_t outputHeight, + uint32_t &inputWidth, uint32_t &inputHeight) { + float scale; + + switch (mode) { + case FSRQualityMode::ULTRA_QUALITY: + scale = 1.3f; + break; + case FSRQualityMode::QUALITY: + scale = 1.5f; + break; + case FSRQualityMode::BALANCED: + scale = 1.7f; + break; + case FSRQualityMode::PERFORMANCE: + scale = 2.0f; + break; + default: + scale = 1.0f; + break; + } + + inputWidth = static_cast<uint32_t>( + std::round(static_cast<float>(outputWidth) / scale) + ); + + inputHeight = static_cast<uint32_t>( + std::round(static_cast<float>(outputHeight) / scale) + ); + } + + float getFSRLodBias(FSRQualityMode mode) { + switch (mode) { + case FSRQualityMode::ULTRA_QUALITY: + return -0.38f; + case FSRQualityMode::QUALITY: + return -0.58f; + case FSRQualityMode::BALANCED: + return -0.79f; + case FSRQualityMode::PERFORMANCE: + return -1.0f; + default: + return 0.0f; + } + } + + static std::vector<DescriptorBinding> getDescriptorBindings() { + return std::vector<DescriptorBinding>({ + DescriptorBinding( + 0, DescriptorType::UNIFORM_BUFFER_DYNAMIC, + 1, ShaderStage::COMPUTE + ), + DescriptorBinding( + 1, DescriptorType::IMAGE_SAMPLED, + 1, ShaderStage::COMPUTE + ), + DescriptorBinding( + 2, DescriptorType::IMAGE_STORAGE, + 1, ShaderStage::COMPUTE + ), + DescriptorBinding( + 3, DescriptorType::SAMPLER, + 1, ShaderStage::COMPUTE + ) + }); + } + + template<typename T> + bool checkFeatures(const vk::BaseInStructure* base, vk::StructureType type, bool (*check)(const T& features)) { + if (base->sType == type) { + return check(*reinterpret_cast<const T*>(base)); + } else + if (base->pNext) { + return checkFeatures<T>(base->pNext, type, check); + } else { + return false; + } + } + + static bool checkFloat16(const vk::PhysicalDeviceFloat16Int8FeaturesKHR& features) { + return features.shaderFloat16; + } + + static bool check16Storage(const vk::PhysicalDevice16BitStorageFeaturesKHR& features) { + return features.storageBuffer16BitAccess; + } + + static bool writeShaderCode(const std::filesystem::path &shaderPath, const std::string& code) { + std::ofstream file (shaderPath.string(), std::ios::out); + + if (!file.is_open()) { + vkcv_log(LogLevel::ERROR, "The file could not be opened (%s)", shaderPath.string().c_str()); + return false; + } + + file.seekp(0); + file.write(code.c_str(), static_cast<std::streamsize>(code.length())); + file.close(); + + return true; + } + + static bool compileFSRShader(vkcv::shader::GLSLCompiler& compiler, + const shader::ShaderCompiledFunction& compiled) { + std::filesystem::path directory = generateTemporaryDirectoryPath(); + + if (!std::filesystem::create_directory(directory)) { + vkcv_log(LogLevel::ERROR, "The directory could not be created (%s)", directory.string().c_str()); + return false; + } + + if (!writeShaderCode(directory / "ffx_a.h", FFX_A_H_SHADER)) { + return false; + } + + if (!writeShaderCode(directory / "ffx_fsr1.h", FFX_FSR1_H_SHADER)) { + return false; + } + + return compiler.compileSource(vkcv::ShaderStage::COMPUTE, + FSR_PASS_GLSL_SHADER.c_str(), + [&directory, &compiled] (vkcv::ShaderStage shaderStage, + const std::filesystem::path& path) { + if (compiled) { + compiled(shaderStage, path); + } + + std::filesystem::remove_all(directory); + }, directory + ); + } + + FSRUpscaling::FSRUpscaling(Core& core) : + Upscaling(core), + m_easuPipeline(), + m_rcasPipeline(), + m_easuDescriptorSet(m_core.createDescriptorSet(getDescriptorBindings())), + m_rcasDescriptorSet(m_core.createDescriptorSet(getDescriptorBindings())), + m_easuConstants(m_core.createBuffer<FSRConstants>( + BufferType::UNIFORM,1, + BufferMemoryType::HOST_VISIBLE + )), + m_rcasConstants(m_core.createBuffer<FSRConstants>( + BufferType::UNIFORM,1, + BufferMemoryType::HOST_VISIBLE + )), + m_intermediateImage(), + m_sampler(m_core.createSampler( + SamplerFilterType::LINEAR, + SamplerFilterType::LINEAR, + SamplerMipmapMode::NEAREST, + SamplerAddressMode::CLAMP_TO_EDGE + )), + m_hdr(false), + m_sharpness(0.875f) { + vkcv::shader::GLSLCompiler easuCompiler; + vkcv::shader::GLSLCompiler rcasCompiler; + + const auto& features = m_core.getContext().getPhysicalDevice().getFeatures2(); + const bool float16Support = ( + checkFeatures<vk::PhysicalDeviceFloat16Int8FeaturesKHR>( + reinterpret_cast<const vk::BaseInStructure*>(&features), + vk::StructureType::ePhysicalDeviceShaderFloat16Int8FeaturesKHR, + checkFloat16 + ) && + checkFeatures<vk::PhysicalDevice16BitStorageFeaturesKHR>( + reinterpret_cast<const vk::BaseInStructure*>(&features), + vk::StructureType::ePhysicalDevice16BitStorageFeaturesKHR, + check16Storage + ) + ) || (true); // check doesn't work because chain is empty + + if (!float16Support) { + easuCompiler.setDefine("SAMPLE_SLOW_FALLBACK", "1"); + rcasCompiler.setDefine("SAMPLE_SLOW_FALLBACK", "1"); + } + + easuCompiler.setDefine("SAMPLE_EASU", "1"); + rcasCompiler.setDefine("SAMPLE_RCAS", "1"); + + { + ShaderProgram program; + compileFSRShader(easuCompiler, [&program](vkcv::ShaderStage shaderStage, + const std::filesystem::path& path) { + program.addShader(shaderStage, path); + }); + + m_easuPipeline = m_core.createComputePipeline(program, { + m_core.getDescriptorSet(m_easuDescriptorSet).layout + }); + + DescriptorWrites writes; + writes.uniformBufferWrites.emplace_back( + 0, m_easuConstants.getHandle(),true + ); + + writes.samplerWrites.emplace_back(3, m_sampler); + + m_core.writeDescriptorSet(m_easuDescriptorSet, writes); + } + + { + ShaderProgram program; + compileFSRShader(rcasCompiler, [&program](vkcv::ShaderStage shaderStage, + const std::filesystem::path& path) { + program.addShader(shaderStage, path); + }); + + m_rcasPipeline = m_core.createComputePipeline(program, { + m_core.getDescriptorSet(m_rcasDescriptorSet).layout + }); + + DescriptorWrites writes; + writes.uniformBufferWrites.emplace_back( + 0, m_rcasConstants.getHandle(),true + ); + + writes.samplerWrites.emplace_back(3, m_sampler); + + m_core.writeDescriptorSet(m_rcasDescriptorSet, writes); + } + } + + void FSRUpscaling::recordUpscaling(const CommandStreamHandle& cmdStream, + const ImageHandle& input, + const ImageHandle& output) { + const uint32_t inputWidth = m_core.getImageWidth(input); + const uint32_t inputHeight = m_core.getImageHeight(input); + + const uint32_t outputWidth = m_core.getImageWidth(output); + const uint32_t outputHeight = m_core.getImageHeight(output); + + if ((!m_intermediateImage) || + (outputWidth != m_core.getImageWidth(m_intermediateImage)) || + (outputHeight != m_core.getImageHeight(m_intermediateImage))) { + m_intermediateImage = m_core.createImage( + m_core.getImageFormat(output), + outputWidth, outputHeight,1, + false, + true + ).getHandle(); + + m_core.prepareImageForStorage(cmdStream, m_intermediateImage); + } + + const bool rcasEnabled = ( + (m_sharpness > +0.0f) && + ((inputWidth < outputWidth) || (inputHeight < outputHeight)) + ); + + { + FSRConstants consts = {}; + + FsrEasuCon( + consts.Const0, consts.Const1, consts.Const2, consts.Const3, + static_cast<AF1>(inputWidth), static_cast<AF1>(inputHeight), + static_cast<AF1>(inputWidth), static_cast<AF1>(inputHeight), + static_cast<AF1>(outputWidth), static_cast<AF1>(outputHeight) + ); + + consts.Sample[0] = (((m_hdr) && (!rcasEnabled)) ? 1 : 0); + + m_easuConstants.fill(&consts); + } + + static const uint32_t threadGroupWorkRegionDim = 16; + + uint32_t dispatch[3]; + dispatch[0] = (outputWidth + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + dispatch[1] = (outputHeight + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + dispatch[2] = 1; + + m_core.recordBufferMemoryBarrier(cmdStream, m_easuConstants.getHandle()); + + if (rcasEnabled) { + { + DescriptorWrites writes; + writes.sampledImageWrites.emplace_back(1, input); + writes.storageImageWrites.emplace_back(2, m_intermediateImage); + + m_core.writeDescriptorSet(m_easuDescriptorSet, writes); + } + { + DescriptorWrites writes; + writes.sampledImageWrites.emplace_back(1, m_intermediateImage); + writes.storageImageWrites.emplace_back(2, output); + + m_core.writeDescriptorSet(m_rcasDescriptorSet, writes); + } + + m_core.recordComputeDispatchToCmdStream( + cmdStream, + m_easuPipeline, + dispatch, + {DescriptorSetUsage(0, m_core.getDescriptorSet( + m_easuDescriptorSet + ).vulkanHandle, { 0 })}, + PushConstants(0) + ); + + { + FSRConstants consts = {}; + + FsrRcasCon(consts.Const0, (1.0f - m_sharpness) * 2.0f); + consts.Sample[0] = (m_hdr ? 1 : 0); + + m_rcasConstants.fill(&consts); + } + + m_core.recordBufferMemoryBarrier(cmdStream, m_rcasConstants.getHandle()); + m_core.prepareImageForSampling(cmdStream, m_intermediateImage); + + m_core.recordComputeDispatchToCmdStream( + cmdStream, + m_rcasPipeline, + dispatch, + {DescriptorSetUsage(0, m_core.getDescriptorSet( + m_rcasDescriptorSet + ).vulkanHandle, { 0 })}, + PushConstants(0) + ); + + m_core.prepareImageForStorage(cmdStream, m_intermediateImage); + } else { + { + DescriptorWrites writes; + writes.sampledImageWrites.emplace_back(1, input); + writes.storageImageWrites.emplace_back(2, output); + + m_core.writeDescriptorSet(m_easuDescriptorSet, writes); + } + + m_core.recordComputeDispatchToCmdStream( + cmdStream, + m_easuPipeline, + dispatch, + {DescriptorSetUsage(0, m_core.getDescriptorSet( + m_easuDescriptorSet + ).vulkanHandle, { 0 })}, + PushConstants(0) + ); + } + } + + bool FSRUpscaling::isHdrEnabled() const { + return m_hdr; + } + + void FSRUpscaling::setHdrEnabled(bool enabled) { + m_hdr = enabled; + } + + float FSRUpscaling::getSharpness() const { + return m_sharpness; + } + + void FSRUpscaling::setSharpness(float sharpness) { + m_sharpness = (sharpness < 0.0f ? 0.0f : (sharpness > 1.0f ? 1.0f : sharpness)); + } + +} diff --git a/modules/upscaling/src/vkcv/upscaling/Upscaling.cpp b/modules/upscaling/src/vkcv/upscaling/Upscaling.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b0c3dee9b1c799c0e1f07b59b03d3ad46bd453ed --- /dev/null +++ b/modules/upscaling/src/vkcv/upscaling/Upscaling.cpp @@ -0,0 +1,8 @@ + +#include "vkcv/upscaling/Upscaling.hpp" + +namespace vkcv::upscaling { + + Upscaling::Upscaling(Core &core) : m_core(core) {} + +} diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index 1c6e3afe2347f6ef8ea8a62be7acbe0ea750497d..8010718447b8e72aed8eab42c8eac3e9591986ee 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -1,8 +1,9 @@ # Add new projects/examples here: -add_subdirectory(bloom) add_subdirectory(first_triangle) add_subdirectory(first_mesh) -add_subdirectory(particle_simulation) add_subdirectory(first_scene) +add_subdirectory(particle_simulation) add_subdirectory(voxelization) +add_subdirectory(mesh_shader) +add_subdirectory(indirect_dispatch) diff --git a/projects/bloom/.gitignore b/projects/bloom/.gitignore deleted file mode 100644 index 3643183e0628e666abab193e1dd1d92c1774ac61..0000000000000000000000000000000000000000 --- a/projects/bloom/.gitignore +++ /dev/null @@ -1 +0,0 @@ -bloom \ No newline at end of file diff --git a/projects/bloom/CMakeLists.txt b/projects/bloom/CMakeLists.txt deleted file mode 100644 index 8171938e7cb430aacce5562af44f628c11c97c54..0000000000000000000000000000000000000000 --- a/projects/bloom/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(bloom) - -# setting c++ standard for the project -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# this should fix the execution path to load local files from the project -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - -# adding source files to the project -add_executable(bloom src/main.cpp) - -target_sources(bloom PRIVATE - src/BloomAndFlares.cpp - src/BloomAndFlares.hpp) - -# this should fix the execution path to load local files from the project (for MSVC) -if(MSVC) - set_target_properties(bloom PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - set_target_properties(bloom PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - - # in addition to setting the output directory, the working directory has to be set - # by default visual studio sets the working directory to the build directory, when using the debugger - set_target_properties(bloom PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) -endif() - -# including headers of dependencies and the VkCV framework -target_include_directories(bloom SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include}) - -# linking with libraries from all dependencies and the VkCV framework -target_link_libraries(bloom vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler) diff --git a/projects/bloom/resources/Sponza/Sponza.bin b/projects/bloom/resources/Sponza/Sponza.bin deleted file mode 100644 index cfedd26ca5a67b6d0a47d44d13a75e14a141717a..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/Sponza.bin +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b809f7a17687dc99e6f41ca1ea32c06eded8779bf34d16f1f565d750b0ffd68 -size 6347696 diff --git a/projects/bloom/resources/Sponza/Sponza.gltf b/projects/bloom/resources/Sponza/Sponza.gltf deleted file mode 100644 index 172ea07e21c94465211c860cd805355704cef230..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/Sponza.gltf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5cc0ecad5c4694088ff820e663619c370421afc1323ac487406e8e9b4735d787 -size 713962 diff --git a/projects/bloom/resources/Sponza/background.png b/projects/bloom/resources/Sponza/background.png deleted file mode 100644 index b64def129da38f4e23d89e21b4af1039008a4327..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/background.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f5b5f900ff8ed83a31750ec8e428b5b91273794ddcbfc4e4b8a6a7e781f8c686 -size 1417666 diff --git a/projects/bloom/resources/Sponza/chain_texture.png b/projects/bloom/resources/Sponza/chain_texture.png deleted file mode 100644 index c1e1768cff78e0614ad707eca8602a4c4edab5e5..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/chain_texture.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d8362cfd472880daeaea37439326a4651d1338680ae69bb2513fc6b17c8de7d4 -size 490895 diff --git a/projects/bloom/resources/Sponza/lion.png b/projects/bloom/resources/Sponza/lion.png deleted file mode 100644 index c49c7f0ed31e762e19284d0d3624fbc47664e56b..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/lion.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9f882f746c3a9cd51a9c6eedc1189b97668721d91a3fe49232036e789912c652 -size 2088728 diff --git a/projects/bloom/resources/Sponza/spnza_bricks_a_diff.png b/projects/bloom/resources/Sponza/spnza_bricks_a_diff.png deleted file mode 100644 index cde4c7a6511e9a5f03c63ad996437fcdba3ce2df..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/spnza_bricks_a_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b94219c2f5f943f3f4715c74e7d1038bf0ab3b3b3216a758eaee67f875df0851 -size 1928829 diff --git a/projects/bloom/resources/Sponza/sponza_arch_diff.png b/projects/bloom/resources/Sponza/sponza_arch_diff.png deleted file mode 100644 index bcd9bda2918d226039f9e2d03902d377b706fab6..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_arch_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c0df2c8a01b2843b1c792b494f7173cdbc4f834840fc2177af3e5d690fceda57 -size 1596151 diff --git a/projects/bloom/resources/Sponza/sponza_ceiling_a_diff.png b/projects/bloom/resources/Sponza/sponza_ceiling_a_diff.png deleted file mode 100644 index 59de631ffac4414cabf69b2dc794c46fc187d6cb..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_ceiling_a_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ab6c187a81aa68f4eba30119e17fce2e4882a9ec320f70c90482dbe9da82b1c6 -size 1872074 diff --git a/projects/bloom/resources/Sponza/sponza_column_a_diff.png b/projects/bloom/resources/Sponza/sponza_column_a_diff.png deleted file mode 100644 index 01a82432d3f9939bbefe850bdb900f1ff9a3f6db..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_column_a_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c291507e2808bb83e160ab4b020689817df273baad3713a9ad19ac15fac6826 -size 1840992 diff --git a/projects/bloom/resources/Sponza/sponza_column_b_diff.png b/projects/bloom/resources/Sponza/sponza_column_b_diff.png deleted file mode 100644 index 10a660cce2a5a9b8997772c746058ce23e7d45d7..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_column_b_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2820b0267c4289c6cedbb42721792a57ef244ec2d0935941011c2a7d3fe88a9b -size 2170433 diff --git a/projects/bloom/resources/Sponza/sponza_column_c_diff.png b/projects/bloom/resources/Sponza/sponza_column_c_diff.png deleted file mode 100644 index bc46fd979044a938d3adca7601689e71504e48bf..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_column_c_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a0bc993ff59865468ef4530798930c7dfefb07482d71db45bc2a520986b27735 -size 2066950 diff --git a/projects/bloom/resources/Sponza/sponza_curtain_blue_diff.png b/projects/bloom/resources/Sponza/sponza_curtain_blue_diff.png deleted file mode 100644 index 384c8c2c051160d530eb3ac8b05c9c60752a2d2b..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_curtain_blue_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b85c6bb3cd5105f48d3812ec8e7a1068521ce69e917300d79e136e19d45422fb -size 9510905 diff --git a/projects/bloom/resources/Sponza/sponza_curtain_diff.png b/projects/bloom/resources/Sponza/sponza_curtain_diff.png deleted file mode 100644 index af842e9f5fe18c1f609875e00899a6770fa4488b..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_curtain_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:563c56bdbbee395a6ef7f0c51c8ac9223c162e517b4cdba0d4654e8de27c98d8 -size 9189263 diff --git a/projects/bloom/resources/Sponza/sponza_curtain_green_diff.png b/projects/bloom/resources/Sponza/sponza_curtain_green_diff.png deleted file mode 100644 index 6c9b6391a199407637fa71033d79fb58b8b4f0d7..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_curtain_green_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:238fe1c7f481388d1c1d578c2da8d411b99e8f0030ab62060a306db333124476 -size 8785458 diff --git a/projects/bloom/resources/Sponza/sponza_details_diff.png b/projects/bloom/resources/Sponza/sponza_details_diff.png deleted file mode 100644 index 12656686362c3e0a297e060491f33bd7351551f9..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_details_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb1223b3bb82f8757e7df25a6891f1239cdd7ec59990340e952fb2d6b7ea570c -size 1522643 diff --git a/projects/bloom/resources/Sponza/sponza_fabric_blue_diff.png b/projects/bloom/resources/Sponza/sponza_fabric_blue_diff.png deleted file mode 100644 index 879d16ef84722a4fc13e83a771778de326e4bc54..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_fabric_blue_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:467d290bf5d4b2a017da140ba9e244ed8a8a9be5418a9ac9bcb4ad572ae2d7ab -size 2229440 diff --git a/projects/bloom/resources/Sponza/sponza_fabric_diff.png b/projects/bloom/resources/Sponza/sponza_fabric_diff.png deleted file mode 100644 index 3311287a219d2148620b87fe428fea071688d051..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_fabric_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1594f59cc2848db26add47361f4e665e3d8afa147760ed915d839fea42b20287 -size 2267382 diff --git a/projects/bloom/resources/Sponza/sponza_fabric_green_diff.png b/projects/bloom/resources/Sponza/sponza_fabric_green_diff.png deleted file mode 100644 index de110f369004388dae4cd5067c63428db3a07834..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_fabric_green_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:902b87faab221173bf370cea7c74cb9060b4d870ac6316b190dafded1cb12993 -size 2258220 diff --git a/projects/bloom/resources/Sponza/sponza_flagpole_diff.png b/projects/bloom/resources/Sponza/sponza_flagpole_diff.png deleted file mode 100644 index 5f6e0812a0df80346318baa3cb50a6888afc58f8..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_flagpole_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bfffb62e770959c725d0f3db6dc7dbdd46a380ec55ef884dab94d44ca017b438 -size 1425673 diff --git a/projects/bloom/resources/Sponza/sponza_floor_a_diff.png b/projects/bloom/resources/Sponza/sponza_floor_a_diff.png deleted file mode 100644 index 788ed764f79ba724f04a2d603076a5b85013e188..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_floor_a_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a16f9230fa91f9f31dfca6216ce205f1ef132d44f3b012fbf6efc0fba69770ab -size 1996838 diff --git a/projects/bloom/resources/Sponza/sponza_roof_diff.png b/projects/bloom/resources/Sponza/sponza_roof_diff.png deleted file mode 100644 index c5b84261fdd1cc776a94b3ce398c7806b895f9a3..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_roof_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7fc412138c20da19f8173e53545e771f4652558dff624d4dc67143e40efe562b -size 2320533 diff --git a/projects/bloom/resources/Sponza/sponza_thorn_diff.png b/projects/bloom/resources/Sponza/sponza_thorn_diff.png deleted file mode 100644 index 7a9142674a7d4a6f94a48c5152cf0300743b597a..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/sponza_thorn_diff.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a73a17c883cd0d0d67cfda2dc4118400a916366c05b9a5ac465f0c8b30fd9c8e -size 635001 diff --git a/projects/bloom/resources/Sponza/vase_dif.png b/projects/bloom/resources/Sponza/vase_dif.png deleted file mode 100644 index 61236a81cb324af8797b05099cd264cefe189e56..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/vase_dif.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53d06f52bf9e59df4cf00237707cca76c4f692bda61a62b06a30d321311d6dd9 -size 1842101 diff --git a/projects/bloom/resources/Sponza/vase_hanging.png b/projects/bloom/resources/Sponza/vase_hanging.png deleted file mode 100644 index 36a3cee71d8213225090c74f8c0dce33b9d44378..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/vase_hanging.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a9d10b4f27a3c9a78d5bac882fdd4b6a6987c262f48fa490670fe5e235951e31 -size 1432804 diff --git a/projects/bloom/resources/Sponza/vase_plant.png b/projects/bloom/resources/Sponza/vase_plant.png deleted file mode 100644 index 7ad95e702e229f1ebd803e5203a266d15f2c07b9..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/vase_plant.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d2087371ff02212fb7014b6daefa191cf5676d2227193fff261a5d02f554cb8e -size 998089 diff --git a/projects/bloom/resources/Sponza/vase_round.png b/projects/bloom/resources/Sponza/vase_round.png deleted file mode 100644 index c17953abc000c44b8991e23c136c2b67348f3d1b..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/Sponza/vase_round.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa23d48d492d5d4ada2ddb27d1ef22952b214e6eb3b301c65f9d88442723d20a -size 1871399 diff --git a/projects/bloom/resources/shaders/comp.spv b/projects/bloom/resources/shaders/comp.spv deleted file mode 100644 index 85c7e74cfc0a89917bf6dd1a7ec449368274c1d3..0000000000000000000000000000000000000000 Binary files a/projects/bloom/resources/shaders/comp.spv and /dev/null differ diff --git a/projects/bloom/resources/shaders/composite.comp b/projects/bloom/resources/shaders/composite.comp deleted file mode 100644 index 190bed0657d70e0217bf654820d0b2b2c58f12c2..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/shaders/composite.comp +++ /dev/null @@ -1,38 +0,0 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(set=0, binding=0) uniform texture2D blurImage; -layout(set=0, binding=1) uniform texture2D lensImage; -layout(set=0, binding=2) uniform sampler linearSampler; -layout(set=0, binding=3, r11f_g11f_b10f) uniform image2D colorBuffer; - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - - -void main() -{ - if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(colorBuffer)))){ - return; - } - - ivec2 pixel_coord = ivec2(gl_GlobalInvocationID.xy); - vec2 pixel_size = vec2(1.0f) / textureSize(sampler2D(blurImage, linearSampler), 0); - vec2 UV = pixel_coord.xy * pixel_size; - - vec4 composite_color = vec4(0.0f); - - vec3 blur_color = texture(sampler2D(blurImage, linearSampler), UV).rgb; - vec3 lens_color = texture(sampler2D(lensImage, linearSampler), UV).rgb; - vec3 main_color = imageLoad(colorBuffer, pixel_coord).rgb; - - // composite blur and lens features - float bloom_weight = 0.25f; - float lens_weight = 0.25f; - float main_weight = 1 - (bloom_weight + lens_weight); - - composite_color.rgb = blur_color * bloom_weight + - lens_color * lens_weight + - main_color * main_weight; - - imageStore(colorBuffer, pixel_coord, composite_color); -} \ No newline at end of file diff --git a/projects/bloom/resources/shaders/downsample.comp b/projects/bloom/resources/shaders/downsample.comp deleted file mode 100644 index 2ab00c7c92798769153634f3479c5b7f3fb61d94..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/shaders/downsample.comp +++ /dev/null @@ -1,76 +0,0 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(set=0, binding=0) uniform texture2D inBlurImage; -layout(set=0, binding=1) uniform sampler inImageSampler; -layout(set=0, binding=2, r11f_g11f_b10f) uniform writeonly image2D outBlurImage; - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - - -void main() -{ - if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outBlurImage)))){ - return; - } - - ivec2 pixel_coord = ivec2(gl_GlobalInvocationID.xy); - vec2 pixel_size = vec2(1.0f) / imageSize(outBlurImage); - vec2 UV = pixel_coord.xy * pixel_size; - vec2 UV_offset = UV + 0.5f * pixel_size; - - vec2 color_fetches[13] = { - // center neighbourhood (RED) - vec2(-1, 1), // LT - vec2(-1, -1), // LB - vec2( 1, -1), // RB - vec2( 1, 1), // RT - - vec2(-2, 2), // LT - vec2( 0, 2), // CT - vec2( 2, 2), // RT - - vec2(0 ,-2), // LC - vec2(0 , 0), // CC - vec2(2, 0), // CR - - vec2(-2, -2), // LB - vec2(0 , -2), // CB - vec2(2 , -2) // RB - }; - - float color_weights[13] = { - // 0.5f - 1.f/8.f, - 1.f/8.f, - 1.f/8.f, - 1.f/8.f, - - // 0.125f - 1.f/32.f, - 1.f/16.f, - 1.f/32.f, - - // 0.25f - 1.f/16.f, - 1.f/8.f, - 1.f/16.f, - - // 0.125f - 1.f/32.f, - 1.f/16.f, - 1.f/32.f - }; - - vec3 sampled_color = vec3(0.0f); - - for(uint i = 0; i < 13; i++) - { - vec2 color_fetch = UV_offset + color_fetches[i] * pixel_size; - vec3 color = texture(sampler2D(inBlurImage, inImageSampler), color_fetch).rgb; - color *= color_weights[i]; - sampled_color += color; - } - - imageStore(outBlurImage, pixel_coord, vec4(sampled_color, 1.f)); -} \ No newline at end of file diff --git a/projects/bloom/resources/shaders/gammaCorrection.comp b/projects/bloom/resources/shaders/gammaCorrection.comp deleted file mode 100644 index f89ad167c846cca8e80f69d33eda83bd6ed00d46..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/shaders/gammaCorrection.comp +++ /dev/null @@ -1,20 +0,0 @@ -#version 440 - -layout(set=0, binding=0, r11f_g11f_b10f) uniform image2D inImage; -layout(set=0, binding=1, rgba8) uniform image2D outImage; - - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -void main(){ - - if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(inImage)))){ - return; - } - ivec2 uv = ivec2(gl_GlobalInvocationID.xy); - vec3 linearColor = imageLoad(inImage, uv).rgb; - // cheap Reinhard tone mapping - linearColor = linearColor/(linearColor + 1.0f); - vec3 gammaCorrected = pow(linearColor, vec3(1.f / 2.2f)); - imageStore(outImage, uv, vec4(gammaCorrected, 0.f)); -} \ No newline at end of file diff --git a/projects/bloom/resources/shaders/lensFlares.comp b/projects/bloom/resources/shaders/lensFlares.comp deleted file mode 100644 index ce27d8850b709f61332d467914ddc944dc63109f..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/shaders/lensFlares.comp +++ /dev/null @@ -1,109 +0,0 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(set=0, binding=0) uniform texture2D blurBuffer; -layout(set=0, binding=1) uniform sampler linearSampler; -layout(set=0, binding=2, r11f_g11f_b10f) uniform image2D lensBuffer; - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -vec3 sampleColorChromaticAberration(vec2 _uv) -{ - vec2 toCenter = (vec2(0.5) - _uv); - - vec3 colorScales = vec3(-1, 0, 1); - float aberrationScale = 0.1; - vec3 scaleFactors = colorScales * aberrationScale; - - float r = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.r).r; - float g = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.g).g; - float b = texture(sampler2D(blurBuffer, linearSampler), _uv + toCenter * scaleFactors.b).b; - return vec3(r, g, b); -} - -// _uv assumed to be flipped UV coordinates! -vec3 ghost_vectors(vec2 _uv) -{ - vec2 ghost_vec = (vec2(0.5f) - _uv); - - const uint c_ghost_count = 64; - const float c_ghost_spacing = length(ghost_vec) / c_ghost_count; - - ghost_vec *= c_ghost_spacing; - - vec3 ret_color = vec3(0.0f); - - for (uint i = 0; i < c_ghost_count; ++i) - { - // sample scene color - vec2 s_uv = fract(_uv + ghost_vec * vec2(i)); - vec3 s = sampleColorChromaticAberration(s_uv); - - // tint/weight - float d = distance(s_uv, vec2(0.5)); - float weight = 1.0f - smoothstep(0.0f, 0.75f, d); - s *= weight; - - ret_color += s; - } - - ret_color /= c_ghost_count; - return ret_color; -} - -vec3 halo(vec2 _uv) -{ - const float c_aspect_ratio = float(imageSize(lensBuffer).x) / float(imageSize(lensBuffer).y); - const float c_radius = 0.6f; - const float c_halo_thickness = 0.1f; - - vec2 halo_vec = vec2(0.5) - _uv; - //halo_vec.x /= c_aspect_ratio; - halo_vec = normalize(halo_vec); - //halo_vec.x *= c_aspect_ratio; - - - //vec2 w_uv = (_uv - vec2(0.5, 0.0)) * vec2(c_aspect_ratio, 1.0) + vec2(0.5, 0.0); - vec2 w_uv = _uv; - float d = distance(w_uv, vec2(0.5)); // distance to center - - float distance_to_halo = abs(d - c_radius); - - float halo_weight = 0.0f; - if(abs(d - c_radius) <= c_halo_thickness) - { - float distance_to_border = c_halo_thickness - distance_to_halo; - halo_weight = distance_to_border / c_halo_thickness; - - //halo_weight = clamp((halo_weight / 0.4f), 0.0f, 1.0f); - halo_weight = pow(halo_weight, 2.0f); - - - //halo_weight = 1.0f; - } - - return sampleColorChromaticAberration(_uv + halo_vec) * halo_weight; -} - - - -void main() -{ - if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(lensBuffer)))){ - return; - } - - ivec2 pixel_coord = ivec2(gl_GlobalInvocationID.xy); - vec2 pixel_size = vec2(1.0f) / imageSize(lensBuffer); - vec2 UV = pixel_coord.xy * pixel_size; - - vec2 flipped_UV = vec2(1.0f) - UV; - - vec3 color = vec3(0.0f); - - color += ghost_vectors(flipped_UV); - color += halo(UV); - color *= 0.5f; - - imageStore(lensBuffer, pixel_coord, vec4(color, 0.0f)); -} \ No newline at end of file diff --git a/projects/bloom/resources/shaders/perMeshResources.inc b/projects/bloom/resources/shaders/perMeshResources.inc deleted file mode 100644 index 95e4fb7c27009965659d14a9c72acfec950c37e3..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/shaders/perMeshResources.inc +++ /dev/null @@ -1,2 +0,0 @@ -layout(set=1, binding=0) uniform texture2D albedoTexture; -layout(set=1, binding=1) uniform sampler textureSampler; \ No newline at end of file diff --git a/projects/bloom/resources/shaders/shader.frag b/projects/bloom/resources/shaders/shader.frag deleted file mode 100644 index 3e95b4508f112c1ed9aa4a7050a98fa789dccd09..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/shaders/shader.frag +++ /dev/null @@ -1,45 +0,0 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable -#extension GL_GOOGLE_include_directive : enable - -#include "perMeshResources.inc" - -layout(location = 0) in vec3 passNormal; -layout(location = 1) in vec2 passUV; -layout(location = 2) in vec3 passPos; - -layout(location = 0) out vec3 outColor; - -layout(set=0, binding=0) uniform sunBuffer { - vec3 L; float padding; - mat4 lightMatrix; -}; -layout(set=0, binding=1) uniform texture2D shadowMap; -layout(set=0, binding=2) uniform sampler shadowMapSampler; - -float shadowTest(vec3 worldPos){ - vec4 lightPos = lightMatrix * vec4(worldPos, 1); - lightPos /= lightPos.w; - lightPos.xy = lightPos.xy * 0.5 + 0.5; - - if(any(lessThan(lightPos.xy, vec2(0))) || any(greaterThan(lightPos.xy, vec2(1)))){ - return 1; - } - - lightPos.z = clamp(lightPos.z, 0, 1); - - float shadowMapSample = texture(sampler2D(shadowMap, shadowMapSampler), lightPos.xy).r; - float bias = 0.01f; - shadowMapSample += bias; - return shadowMapSample < lightPos.z ? 0 : 1; -} - -void main() { - vec3 N = normalize(passNormal); - vec3 sunColor = vec3(10); - vec3 sun = sunColor * clamp(dot(N, L), 0, 1); - sun *= shadowTest(passPos); - vec3 ambient = vec3(0.05); - vec3 albedo = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb; - outColor = albedo * (sun + ambient); -} \ No newline at end of file diff --git a/projects/bloom/resources/shaders/upsample.comp b/projects/bloom/resources/shaders/upsample.comp deleted file mode 100644 index 0ddeedb5b5af9e476dc19012fed6430544006c0e..0000000000000000000000000000000000000000 --- a/projects/bloom/resources/shaders/upsample.comp +++ /dev/null @@ -1,45 +0,0 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -layout(set=0, binding=0) uniform texture2D inUpsampleImage; -layout(set=0, binding=1) uniform sampler inImageSampler; -layout(set=0, binding=2, r11f_g11f_b10f) uniform image2D outUpsampleImage; - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -void main() -{ - if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outUpsampleImage)))){ - return; - } - - - ivec2 pixel_coord = ivec2(gl_GlobalInvocationID.xy); - vec2 pixel_size = vec2(1.0f) / imageSize(outUpsampleImage); - vec2 UV = pixel_coord.xy * pixel_size; - - const float gauss_kernel[3] = {1.f, 2.f, 1.f}; - const float gauss_weight = 16.f; - - vec3 sampled_color = vec3(0.f); - - for(int i = -1; i <= 1; i++) - { - for(int j = -1; j <= 1; j++) - { - vec2 sample_location = UV + vec2(j, i) * pixel_size; - vec3 color = texture(sampler2D(inUpsampleImage, inImageSampler), sample_location).rgb; - color *= gauss_kernel[j+1]; - color *= gauss_kernel[i+1]; - color /= gauss_weight; - - sampled_color += color; - } - } - - //vec3 prev_color = imageLoad(outUpsampleImage, pixel_coord).rgb; - //float bloomRimStrength = 0.75f; // adjust this to change strength of bloom - //sampled_color = mix(prev_color, sampled_color, bloomRimStrength); - - imageStore(outUpsampleImage, pixel_coord, vec4(sampled_color, 1.f)); -} \ No newline at end of file diff --git a/projects/bloom/src/BloomAndFlares.cpp b/projects/bloom/src/BloomAndFlares.cpp deleted file mode 100644 index 6f26db9de0f2c8334b6dd7e5dd6cf4b6f48baedc..0000000000000000000000000000000000000000 --- a/projects/bloom/src/BloomAndFlares.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#include "BloomAndFlares.hpp" -#include <vkcv/shader/GLSLCompiler.hpp> - -BloomAndFlares::BloomAndFlares( - vkcv::Core *p_Core, - vk::Format colorBufferFormat, - uint32_t width, - uint32_t height) : - - p_Core(p_Core), - m_ColorBufferFormat(colorBufferFormat), - m_Width(width), - m_Height(height), - m_LinearSampler(p_Core->createSampler(vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerMipmapMode::LINEAR, - vkcv::SamplerAddressMode::CLAMP_TO_EDGE)), - m_Blur(p_Core->createImage(colorBufferFormat, width, height, 1, true, true, false)), - m_LensFeatures(p_Core->createImage(colorBufferFormat, width, height, 1, false, true, false)) -{ - vkcv::shader::GLSLCompiler compiler; - - // DOWNSAMPLE - vkcv::ShaderProgram dsProg; - compiler.compile(vkcv::ShaderStage::COMPUTE, - "resources/shaders/downsample.comp", - [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) - { - dsProg.addShader(shaderStage, path); - }); - for(uint32_t mipLevel = 0; mipLevel < m_Blur.getMipCount(); mipLevel++) - { - m_DownsampleDescSets.push_back( - p_Core->createDescriptorSet(dsProg.getReflectedDescriptors()[0])); - } - m_DownsamplePipe = p_Core->createComputePipeline( - dsProg, { p_Core->getDescriptorSet(m_DownsampleDescSets[0]).layout }); - - // UPSAMPLE - vkcv::ShaderProgram usProg; - compiler.compile(vkcv::ShaderStage::COMPUTE, - "resources/shaders/upsample.comp", - [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) - { - usProg.addShader(shaderStage, path); - }); - for(uint32_t mipLevel = 0; mipLevel < m_Blur.getMipCount(); mipLevel++) - { - m_UpsampleDescSets.push_back( - p_Core->createDescriptorSet(usProg.getReflectedDescriptors()[0])); - } - m_UpsamplePipe = p_Core->createComputePipeline( - usProg, { p_Core->getDescriptorSet(m_UpsampleDescSets[0]).layout }); - - // LENS FEATURES - vkcv::ShaderProgram lensProg; - compiler.compile(vkcv::ShaderStage::COMPUTE, - "resources/shaders/lensFlares.comp", - [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) - { - lensProg.addShader(shaderStage, path); - }); - m_LensFlareDescSet = p_Core->createDescriptorSet(lensProg.getReflectedDescriptors()[0]); - m_LensFlarePipe = p_Core->createComputePipeline( - lensProg, { p_Core->getDescriptorSet(m_LensFlareDescSet).layout }); - - // COMPOSITE - vkcv::ShaderProgram compProg; - compiler.compile(vkcv::ShaderStage::COMPUTE, - "resources/shaders/composite.comp", - [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) - { - compProg.addShader(shaderStage, path); - }); - m_CompositeDescSet = p_Core->createDescriptorSet(compProg.getReflectedDescriptors()[0]); - m_CompositePipe = p_Core->createComputePipeline( - compProg, { p_Core->getDescriptorSet(m_CompositeDescSet).layout }); -} - -void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStream, - const vkcv::ImageHandle &colorAttachment) -{ - auto dispatchCountX = static_cast<float>(m_Width) / 8.0f; - auto dispatchCountY = static_cast<float>(m_Height) / 8.0f; - // blur dispatch - uint32_t initialDispatchCount[3] = { - static_cast<uint32_t>(glm::ceil(dispatchCountX)), - static_cast<uint32_t>(glm::ceil(dispatchCountY)), - 1 - }; - - // downsample dispatch of original color attachment - p_Core->prepareImageForSampling(cmdStream, colorAttachment); - p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle()); - - vkcv::DescriptorWrites initialDownsampleWrites; - initialDownsampleWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, colorAttachment)}; - initialDownsampleWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)}; - initialDownsampleWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), 0) }; - p_Core->writeDescriptorSet(m_DownsampleDescSets[0], initialDownsampleWrites); - - p_Core->recordComputeDispatchToCmdStream( - cmdStream, - m_DownsamplePipe, - initialDispatchCount, - {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[0]).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0)); - - // downsample dispatches of blur buffer's mip maps - float mipDispatchCountX = dispatchCountX; - float mipDispatchCountY = dispatchCountY; - for(uint32_t mipLevel = 1; mipLevel < m_DownsampleDescSets.size(); mipLevel++) - { - // mip descriptor writes - vkcv::DescriptorWrites mipDescriptorWrites; - mipDescriptorWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), mipLevel - 1, true)}; - mipDescriptorWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)}; - mipDescriptorWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), mipLevel) }; - p_Core->writeDescriptorSet(m_DownsampleDescSets[mipLevel], mipDescriptorWrites); - - // mip dispatch calculation - mipDispatchCountX /= 2.0f; - mipDispatchCountY /= 2.0f; - - uint32_t mipDispatchCount[3] = { - static_cast<uint32_t>(glm::ceil(mipDispatchCountX)), - static_cast<uint32_t>(glm::ceil(mipDispatchCountY)), - 1 - }; - - if(mipDispatchCount[0] == 0) - mipDispatchCount[0] = 1; - if(mipDispatchCount[1] == 0) - mipDispatchCount[1] = 1; - - // mip blur dispatch - p_Core->recordComputeDispatchToCmdStream( - cmdStream, - m_DownsamplePipe, - mipDispatchCount, - {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[mipLevel]).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0)); - - // image barrier between mips - p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle()); - } -} - -void BloomAndFlares::execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream) -{ - // upsample dispatch - p_Core->prepareImageForStorage(cmdStream, m_Blur.getHandle()); - - const uint32_t upsampleMipLevels = std::min( - static_cast<uint32_t>(m_UpsampleDescSets.size() - 1), - static_cast<uint32_t>(5) - ); - - // upsample dispatch for each mip map - for(uint32_t mipLevel = upsampleMipLevels; mipLevel > 0; mipLevel--) - { - // mip descriptor writes - vkcv::DescriptorWrites mipUpsampleWrites; - mipUpsampleWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), mipLevel, true)}; - mipUpsampleWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)}; - mipUpsampleWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_Blur.getHandle(), mipLevel - 1) }; - p_Core->writeDescriptorSet(m_UpsampleDescSets[mipLevel], mipUpsampleWrites); - - auto mipDivisor = glm::pow(2.0f, static_cast<float>(mipLevel) - 1.0f); - - auto upsampleDispatchX = static_cast<float>(m_Width) / mipDivisor; - auto upsampleDispatchY = static_cast<float>(m_Height) / mipDivisor; - upsampleDispatchX /= 8.0f; - upsampleDispatchY /= 8.0f; - - const uint32_t upsampleDispatchCount[3] = { - static_cast<uint32_t>(glm::ceil(upsampleDispatchX)), - static_cast<uint32_t>(glm::ceil(upsampleDispatchY)), - 1 - }; - - p_Core->recordComputeDispatchToCmdStream( - cmdStream, - m_UpsamplePipe, - upsampleDispatchCount, - {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_UpsampleDescSets[mipLevel]).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0) - ); - // image barrier between mips - p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle()); - } -} - -void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStream) -{ - // lens feature generation descriptor writes - p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle()); - p_Core->prepareImageForStorage(cmdStream, m_LensFeatures.getHandle()); - - vkcv::DescriptorWrites lensFeatureWrites; - lensFeatureWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle(), 0)}; - lensFeatureWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(1, m_LinearSampler)}; - lensFeatureWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(2, m_LensFeatures.getHandle(), 0)}; - p_Core->writeDescriptorSet(m_LensFlareDescSet, lensFeatureWrites); - - auto dispatchCountX = static_cast<float>(m_Width) / 8.0f; - auto dispatchCountY = static_cast<float>(m_Height) / 8.0f; - // lens feature generation dispatch - uint32_t lensFeatureDispatchCount[3] = { - static_cast<uint32_t>(glm::ceil(dispatchCountX)), - static_cast<uint32_t>(glm::ceil(dispatchCountY)), - 1 - }; - p_Core->recordComputeDispatchToCmdStream( - cmdStream, - m_LensFlarePipe, - lensFeatureDispatchCount, - {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_LensFlareDescSet).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0)); -} - -void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStream, - const vkcv::ImageHandle &colorAttachment) -{ - p_Core->prepareImageForSampling(cmdStream, m_Blur.getHandle()); - p_Core->prepareImageForSampling(cmdStream, m_LensFeatures.getHandle()); - p_Core->prepareImageForStorage(cmdStream, colorAttachment); - - // bloom composite descriptor write - vkcv::DescriptorWrites compositeWrites; - compositeWrites.sampledImageWrites = {vkcv::SampledImageDescriptorWrite(0, m_Blur.getHandle()), - vkcv::SampledImageDescriptorWrite(1, m_LensFeatures.getHandle())}; - compositeWrites.samplerWrites = {vkcv::SamplerDescriptorWrite(2, m_LinearSampler)}; - compositeWrites.storageImageWrites = {vkcv::StorageImageDescriptorWrite(3, colorAttachment)}; - p_Core->writeDescriptorSet(m_CompositeDescSet, compositeWrites); - - float dispatchCountX = static_cast<float>(m_Width) / 8.0f; - float dispatchCountY = static_cast<float>(m_Height) / 8.0f; - - uint32_t compositeDispatchCount[3] = { - static_cast<uint32_t>(glm::ceil(dispatchCountX)), - static_cast<uint32_t>(glm::ceil(dispatchCountY)), - 1 - }; - - // bloom composite dispatch - p_Core->recordComputeDispatchToCmdStream( - cmdStream, - m_CompositePipe, - compositeDispatchCount, - {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_CompositeDescSet).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0)); -} - -void BloomAndFlares::execWholePipeline(const vkcv::CommandStreamHandle &cmdStream, - const vkcv::ImageHandle &colorAttachment) -{ - execDownsamplePipe(cmdStream, colorAttachment); - execUpsamplePipe(cmdStream); - execLensFeaturePipe(cmdStream); - execCompositePipe(cmdStream, colorAttachment); -} - -void BloomAndFlares::updateImageDimensions(uint32_t width, uint32_t height) -{ - m_Width = width; - m_Height = height; - - p_Core->getContext().getDevice().waitIdle(); - m_Blur = p_Core->createImage(m_ColorBufferFormat, m_Width, m_Height, 1, true, true, false); - m_LensFeatures = p_Core->createImage(m_ColorBufferFormat, m_Width, m_Height, 1, false, true, false); -} - - diff --git a/projects/bloom/src/BloomAndFlares.hpp b/projects/bloom/src/BloomAndFlares.hpp deleted file mode 100644 index 756b1ca154ea5232df04eb09a88bb743c5bd28aa..0000000000000000000000000000000000000000 --- a/projects/bloom/src/BloomAndFlares.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include <vkcv/Core.hpp> -#include <glm/glm.hpp> - -class BloomAndFlares{ -public: - BloomAndFlares(vkcv::Core *p_Core, - vk::Format colorBufferFormat, - uint32_t width, - uint32_t height); - - void execWholePipeline(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment); - - void updateImageDimensions(uint32_t width, uint32_t height); - -private: - vkcv::Core *p_Core; - - vk::Format m_ColorBufferFormat; - uint32_t m_Width; - uint32_t m_Height; - - vkcv::SamplerHandle m_LinearSampler; - vkcv::Image m_Blur; - vkcv::Image m_LensFeatures; - - - vkcv::PipelineHandle m_DownsamplePipe; - std::vector<vkcv::DescriptorSetHandle> m_DownsampleDescSets; // per mip desc set - - vkcv::PipelineHandle m_UpsamplePipe; - std::vector<vkcv::DescriptorSetHandle> m_UpsampleDescSets; // per mip desc set - - vkcv::PipelineHandle m_LensFlarePipe; - vkcv::DescriptorSetHandle m_LensFlareDescSet; - - vkcv::PipelineHandle m_CompositePipe; - vkcv::DescriptorSetHandle m_CompositeDescSet; - - void execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment); - void execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream); - void execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStream); - void execCompositePipe(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment); -}; - - - diff --git a/projects/bloom/src/main.cpp b/projects/bloom/src/main.cpp deleted file mode 100644 index 7a17a51f1c7d638575c0b5aafcdca49b589533ef..0000000000000000000000000000000000000000 --- a/projects/bloom/src/main.cpp +++ /dev/null @@ -1,419 +0,0 @@ -#include <iostream> -#include <vkcv/Core.hpp> -#include <GLFW/glfw3.h> -#include <vkcv/camera/CameraManager.hpp> -#include <chrono> -#include <vkcv/asset/asset_loader.hpp> -#include <vkcv/shader/GLSLCompiler.hpp> -#include <vkcv/Logger.hpp> -#include "BloomAndFlares.hpp" -#include <glm/glm.hpp> - -int main(int argc, const char** argv) { - const char* applicationName = "Bloom"; - - uint32_t windowWidth = 1920; - uint32_t windowHeight = 1080; - - vkcv::Window window = vkcv::Window::create( - applicationName, - windowWidth, - windowHeight, - true - ); - - vkcv::camera::CameraManager cameraManager(window); - uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); - uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL); - - cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f)); - cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f); - cameraManager.getCamera(camIndex).setYaw(180.0f); - - cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f); - - vkcv::Core core = vkcv::Core::create( - window, - applicationName, - VK_MAKE_VERSION(0, 0, 1), - { vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute }, - {}, - { "VK_KHR_swapchain" } - ); - - const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf"; - vkcv::asset::Scene scene; - int result = vkcv::asset::loadScene(path, scene); - - if (result == 1) { - std::cout << "Scene loading successful!" << std::endl; - } - else { - std::cout << "Scene loading failed: " << result << std::endl; - return 1; - } - - // build index and vertex buffers - assert(!scene.vertexGroups.empty()); - std::vector<std::vector<uint8_t>> vBuffers; - std::vector<std::vector<uint8_t>> iBuffers; - - std::vector<vkcv::VertexBufferBinding> vBufferBindings; - std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings; - std::vector<vkcv::asset::VertexAttribute> vAttributes; - - for (int i = 0; i < scene.vertexGroups.size(); i++) { - - vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data); - iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data); - - auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes; - - std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) { - return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type); - }); - } - - std::vector<vkcv::Buffer<uint8_t>> vertexBuffers; - for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) { - vertexBuffers.push_back(core.createBuffer<uint8_t>( - vkcv::BufferType::VERTEX, - group.vertexBuffer.data.size())); - vertexBuffers.back().fill(group.vertexBuffer.data); - } - - std::vector<vkcv::Buffer<uint8_t>> indexBuffers; - for (const auto& dataBuffer : iBuffers) { - indexBuffers.push_back(core.createBuffer<uint8_t>( - vkcv::BufferType::INDEX, - dataBuffer.size())); - indexBuffers.back().fill(dataBuffer); - } - - int vertexBufferIndex = 0; - for (const auto& vertexGroup : scene.vertexGroups) { - for (const auto& attribute : vertexGroup.vertexBuffer.attributes) { - vAttributes.push_back(attribute); - vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle())); - } - vertexBufferBindings.push_back(vBufferBindings); - vBufferBindings.clear(); - vertexBufferIndex++; - } - - const vk::Format colorBufferFormat = vk::Format::eB10G11R11UfloatPack32; - const vkcv::AttachmentDescription color_attachment( - vkcv::AttachmentOperation::STORE, - vkcv::AttachmentOperation::CLEAR, - colorBufferFormat - ); - - const vk::Format depthBufferFormat = vk::Format::eD32Sfloat; - const vkcv::AttachmentDescription depth_attachment( - vkcv::AttachmentOperation::STORE, - vkcv::AttachmentOperation::CLEAR, - depthBufferFormat - ); - - vkcv::PassConfig forwardPassDefinition({ color_attachment, depth_attachment }); - vkcv::PassHandle forwardPass = core.createPass(forwardPassDefinition); - - vkcv::shader::GLSLCompiler compiler; - - vkcv::ShaderProgram forwardProgram; - compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"), - [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { - forwardProgram.addShader(shaderStage, path); - }); - compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"), - [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { - forwardProgram.addShader(shaderStage, path); - }); - - const std::vector<vkcv::VertexAttachment> vertexAttachments = forwardProgram.getVertexAttachments(); - - std::vector<vkcv::VertexBinding> vertexBindings; - for (size_t i = 0; i < vertexAttachments.size(); i++) { - vertexBindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] })); - } - const vkcv::VertexLayout vertexLayout (vertexBindings); - - // shadow map - vkcv::SamplerHandle shadowSampler = core.createSampler( - vkcv::SamplerFilterType::NEAREST, - vkcv::SamplerFilterType::NEAREST, - vkcv::SamplerMipmapMode::NEAREST, - vkcv::SamplerAddressMode::CLAMP_TO_EDGE - ); - const vk::Format shadowMapFormat = vk::Format::eD16Unorm; - const uint32_t shadowMapResolution = 1024; - const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1); - - // light info buffer - struct LightInfo { - glm::vec3 direction; - float padding; - glm::mat4 lightMatrix; - }; - LightInfo lightInfo; - vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3)); - - vkcv::DescriptorSetHandle forwardShadingDescriptorSet = - core.createDescriptorSet({ forwardProgram.getReflectedDescriptors()[0] }); - - vkcv::DescriptorWrites forwardDescriptorWrites; - forwardDescriptorWrites.uniformBufferWrites = { vkcv::UniformBufferDescriptorWrite(0, lightBuffer.getHandle()) }; - forwardDescriptorWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(1, shadowMap.getHandle()) }; - forwardDescriptorWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(2, shadowSampler) }; - core.writeDescriptorSet(forwardShadingDescriptorSet, forwardDescriptorWrites); - - vkcv::SamplerHandle colorSampler = core.createSampler( - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerMipmapMode::LINEAR, - vkcv::SamplerAddressMode::REPEAT - ); - - // prepare per mesh descriptor sets - std::vector<vkcv::DescriptorSetHandle> perMeshDescriptorSets; - std::vector<vkcv::Image> sceneImages; - for (const auto& vertexGroup : scene.vertexGroups) { - perMeshDescriptorSets.push_back(core.createDescriptorSet(forwardProgram.getReflectedDescriptors()[1])); - - const auto& material = scene.materials[vertexGroup.materialIndex]; - - int baseColorIndex = material.baseColor; - if (baseColorIndex < 0) { - vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color"); - baseColorIndex = 0; - } - - vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex]; - - sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h)); - sceneImages.back().fill(sceneTexture.data.data()); - - vkcv::DescriptorWrites setWrites; - setWrites.sampledImageWrites = { - vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle()) - }; - setWrites.samplerWrites = { - vkcv::SamplerDescriptorWrite(1, colorSampler), - }; - core.writeDescriptorSet(perMeshDescriptorSets.back(), setWrites); - } - - const vkcv::PipelineConfig forwardPipelineConfig { - forwardProgram, - windowWidth, - windowHeight, - forwardPass, - vertexLayout, - { core.getDescriptorSet(forwardShadingDescriptorSet).layout, - core.getDescriptorSet(perMeshDescriptorSets[0]).layout }, - true - }; - - vkcv::PipelineHandle forwardPipeline = core.createGraphicsPipeline(forwardPipelineConfig); - - if (!forwardPipeline) { - std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl; - return EXIT_FAILURE; - } - - vkcv::ImageHandle depthBuffer = core.createImage(depthBufferFormat, windowWidth, windowHeight).getHandle(); - vkcv::ImageHandle colorBuffer = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true, true).getHandle(); - - const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle(); - - vkcv::ShaderProgram shadowShader; - compiler.compile(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow.vert", - [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { - shadowShader.addShader(shaderStage, path); - }); - compiler.compile(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow.frag", - [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { - shadowShader.addShader(shaderStage, path); - }); - - const std::vector<vkcv::AttachmentDescription> shadowAttachments = { - vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat) - }; - const vkcv::PassConfig shadowPassConfig(shadowAttachments); - const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig); - const vkcv::PipelineConfig shadowPipeConfig{ - shadowShader, - shadowMapResolution, - shadowMapResolution, - shadowPass, - vertexLayout, - {}, - false - }; - const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig); - - std::vector<std::array<glm::mat4, 2>> mainPassMatrices; - std::vector<glm::mat4> mvpLight; - - // gamma correction compute shader - vkcv::ShaderProgram gammaCorrectionProgram; - compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/gammaCorrection.comp", - [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { - gammaCorrectionProgram.addShader(shaderStage, path); - }); - vkcv::DescriptorSetHandle gammaCorrectionDescriptorSet = core.createDescriptorSet(gammaCorrectionProgram.getReflectedDescriptors()[0]); - vkcv::PipelineHandle gammaCorrectionPipeline = core.createComputePipeline(gammaCorrectionProgram, - { core.getDescriptorSet(gammaCorrectionDescriptorSet).layout }); - - BloomAndFlares baf(&core, colorBufferFormat, windowWidth, windowHeight); - - - // model matrices per mesh - std::vector<glm::mat4> modelMatrices; - modelMatrices.resize(scene.vertexGroups.size(), glm::mat4(1.f)); - for (const auto& mesh : scene.meshes) { - const glm::mat4 m = *reinterpret_cast<const glm::mat4*>(&mesh.modelMatrix[0]); - for (const auto& vertexGroupIndex : mesh.vertexGroups) { - modelMatrices[vertexGroupIndex] = m; - } - } - - // prepare drawcalls - std::vector<vkcv::Mesh> meshes; - for (int i = 0; i < scene.vertexGroups.size(); i++) { - vkcv::Mesh mesh( - vertexBufferBindings[i], - indexBuffers[i].getVulkanHandle(), - scene.vertexGroups[i].numIndices); - meshes.push_back(mesh); - } - - std::vector<vkcv::DrawcallInfo> drawcalls; - std::vector<vkcv::DrawcallInfo> shadowDrawcalls; - for (int i = 0; i < meshes.size(); i++) { - drawcalls.push_back(vkcv::DrawcallInfo(meshes[i], { - vkcv::DescriptorSetUsage(0, core.getDescriptorSet(forwardShadingDescriptorSet).vulkanHandle), - vkcv::DescriptorSetUsage(1, core.getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) })); - shadowDrawcalls.push_back(vkcv::DrawcallInfo(meshes[i], {})); - } - - auto start = std::chrono::system_clock::now(); - const auto appStartTime = start; - while (window.isWindowOpen()) { - vkcv::Window::pollEvents(); - - uint32_t swapchainWidth, swapchainHeight; - if (!core.beginFrame(swapchainWidth, swapchainHeight)) { - continue; - } - - if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) { - depthBuffer = core.createImage(depthBufferFormat, swapchainWidth, swapchainHeight).getHandle(); - colorBuffer = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, false, true, true).getHandle(); - - baf.updateImageDimensions(swapchainWidth, swapchainHeight); - - windowWidth = swapchainWidth; - windowHeight = swapchainHeight; - } - - auto end = std::chrono::system_clock::now(); - auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start); - - start = end; - cameraManager.update(0.000001 * static_cast<double>(deltatime.count())); - - auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - appStartTime); - - const float sunTheta = 0.0001f * static_cast<float>(duration.count()); - lightInfo.direction = glm::normalize(glm::vec3(std::cos(sunTheta), 1, std::sin(sunTheta))); - - const float shadowProjectionSize = 20.f; - glm::mat4 projectionLight = glm::ortho( - -shadowProjectionSize, - shadowProjectionSize, - -shadowProjectionSize, - shadowProjectionSize, - -shadowProjectionSize, - shadowProjectionSize); - - glm::mat4 vulkanCorrectionMatrix(1.f); - vulkanCorrectionMatrix[2][2] = 0.5; - vulkanCorrectionMatrix[3][2] = 0.5; - projectionLight = vulkanCorrectionMatrix * projectionLight; - - const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0)); - - lightInfo.lightMatrix = projectionLight * viewLight; - lightBuffer.fill({ lightInfo }); - - const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP(); - - mainPassMatrices.clear(); - mvpLight.clear(); - for (const auto& m : modelMatrices) { - mainPassMatrices.push_back({ viewProjectionCamera * m, m }); - mvpLight.push_back(lightInfo.lightMatrix * m); - } - - vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4)); - const std::vector<vkcv::ImageHandle> renderTargets = { colorBuffer, depthBuffer }; - - const vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4)); - - auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); - - // shadow map - core.recordDrawcallsToCmdStream( - cmdStream, - shadowPass, - shadowPipe, - shadowPushConstantData, - shadowDrawcalls, - { shadowMap.getHandle() }); - core.prepareImageForSampling(cmdStream, shadowMap.getHandle()); - - // main pass - core.recordDrawcallsToCmdStream( - cmdStream, - forwardPass, - forwardPipeline, - pushConstantData, - drawcalls, - renderTargets); - - const uint32_t gammaCorrectionLocalGroupSize = 8; - const uint32_t gammaCorrectionDispatchCount[3] = { - static_cast<uint32_t>(glm::ceil(static_cast<float>(windowWidth) / static_cast<float>(gammaCorrectionLocalGroupSize))), - static_cast<uint32_t>(glm::ceil(static_cast<float>(windowHeight) / static_cast<float>(gammaCorrectionLocalGroupSize))), - 1 - }; - - baf.execWholePipeline(cmdStream, colorBuffer); - - core.prepareImageForStorage(cmdStream, swapchainInput); - - // gamma correction descriptor write - vkcv::DescriptorWrites gammaCorrectionDescriptorWrites; - gammaCorrectionDescriptorWrites.storageImageWrites = { - vkcv::StorageImageDescriptorWrite(0, colorBuffer), - vkcv::StorageImageDescriptorWrite(1, swapchainInput) }; - core.writeDescriptorSet(gammaCorrectionDescriptorSet, gammaCorrectionDescriptorWrites); - - // gamma correction dispatch - core.recordComputeDispatchToCmdStream( - cmdStream, - gammaCorrectionPipeline, - gammaCorrectionDispatchCount, - { vkcv::DescriptorSetUsage(0, core.getDescriptorSet(gammaCorrectionDescriptorSet).vulkanHandle) }, - vkcv::PushConstantData(nullptr, 0)); - - // present and end - core.prepareSwapchainImageForPresent(cmdStream); - core.submitCommandStream(cmdStream); - - core.endFrame(); - } - - return 0; -} diff --git a/projects/cmd_sync_test/src/main.cpp b/projects/cmd_sync_test/src/main.cpp deleted file mode 100644 index 6e53eb8c5ec1825135778dc91b11dd6e45f44276..0000000000000000000000000000000000000000 --- a/projects/cmd_sync_test/src/main.cpp +++ /dev/null @@ -1,317 +0,0 @@ -#include <iostream> -#include <vkcv/Core.hpp> -#include <GLFW/glfw3.h> -#include <vkcv/camera/CameraManager.hpp> -#include <chrono> -#include <vkcv/asset/asset_loader.hpp> - -int main(int argc, const char** argv) { - const char* applicationName = "First Mesh"; - - uint32_t windowWidth = 800; - uint32_t windowHeight = 600; - - vkcv::Window window = vkcv::Window::create( - applicationName, - windowWidth, - windowHeight, - true - ); - - vkcv::camera::CameraManager cameraManager(window); - uint32_t camIndex = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); - uint32_t camIndex2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL); - - cameraManager.getCamera(camIndex).setPosition(glm::vec3(0.f, 0.f, 3.f)); - cameraManager.getCamera(camIndex).setNearFar(0.1f, 30.0f); - cameraManager.getCamera(camIndex).setYaw(180.0f); - - cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f); - - window.initEvents(); - - vkcv::Core core = vkcv::Core::create( - window, - applicationName, - VK_MAKE_VERSION(0, 0, 1), - { vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute }, - {}, - { "VK_KHR_swapchain" } - ); - - vkcv::asset::Scene mesh; - - const char* path = argc > 1 ? argv[1] : "resources/cube/cube.gltf"; - int result = vkcv::asset::loadScene(path, mesh); - - if (result == 1) { - std::cout << "Mesh loading successful!" << std::endl; - } - else { - std::cout << "Mesh loading failed: " << result << std::endl; - return 1; - } - - assert(mesh.vertexGroups.size() > 0); - auto vertexBuffer = core.createBuffer<uint8_t>( - vkcv::BufferType::VERTEX, - mesh.vertexGroups[0].vertexBuffer.data.size(), - vkcv::BufferMemoryType::DEVICE_LOCAL - ); - - vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data); - - auto indexBuffer = core.createBuffer<uint8_t>( - vkcv::BufferType::INDEX, - mesh.vertexGroups[0].indexBuffer.data.size(), - vkcv::BufferMemoryType::DEVICE_LOCAL - ); - - indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data); - - auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes; - - std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) { - return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type); - }); - - const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = { - vkcv::VertexBufferBinding(attributes[0].offset, vertexBuffer.getVulkanHandle()), - vkcv::VertexBufferBinding(attributes[1].offset, vertexBuffer.getVulkanHandle()), - vkcv::VertexBufferBinding(attributes[2].offset, vertexBuffer.getVulkanHandle()) }; - - const vkcv::Mesh loadedMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices); - - // an example attachment for passes that output to the window - const vkcv::AttachmentDescription present_color_attachment( - vkcv::AttachmentOperation::STORE, - vkcv::AttachmentOperation::CLEAR, - core.getSwapchain().getFormat() - ); - - const vkcv::AttachmentDescription depth_attachment( - vkcv::AttachmentOperation::STORE, - vkcv::AttachmentOperation::CLEAR, - vk::Format::eD32Sfloat - ); - - vkcv::PassConfig firstMeshPassDefinition({ present_color_attachment, depth_attachment }); - vkcv::PassHandle firstMeshPass = core.createPass(firstMeshPassDefinition); - - if (!firstMeshPass) { - std::cout << "Error. Could not create renderpass. Exiting." << std::endl; - return EXIT_FAILURE; - } - - vkcv::ShaderProgram firstMeshProgram{}; - firstMeshProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv")); - firstMeshProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv")); - - const std::vector<vkcv::VertexAttachment> vertexAttachments = firstMeshProgram.getVertexAttachments(); - - std::vector<vkcv::VertexBinding> bindings; - for (size_t i = 0; i < vertexAttachments.size(); i++) { - bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] })); - } - - const vkcv::VertexLayout firstMeshLayout (bindings); - - std::vector<vkcv::DescriptorBinding> descriptorBindings = { firstMeshProgram.getReflectedDescriptors()[0] }; - vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorBindings); - - const vkcv::PipelineConfig firstMeshPipelineConfig { - firstMeshProgram, - windowWidth, - windowHeight, - firstMeshPass, - firstMeshLayout, - { core.getDescriptorSet(descriptorSet).layout }, - true - }; - - vkcv::PipelineHandle firstMeshPipeline = core.createGraphicsPipeline(firstMeshPipelineConfig); - - if (!firstMeshPipeline) { - std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl; - return EXIT_FAILURE; - } - - //vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, mesh.texture_hack.w, mesh.texture_hack.h); - //texture.fill(mesh.texture_hack.img); - vkcv::asset::Texture &tex = mesh.textures[0]; - vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h); - texture.fill(tex.data.data()); - - vkcv::SamplerHandle sampler = core.createSampler( - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerMipmapMode::LINEAR, - vkcv::SamplerAddressMode::REPEAT - ); - - vkcv::SamplerHandle shadowSampler = core.createSampler( - vkcv::SamplerFilterType::NEAREST, - vkcv::SamplerFilterType::NEAREST, - vkcv::SamplerMipmapMode::NEAREST, - vkcv::SamplerAddressMode::CLAMP_TO_EDGE - ); - - vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle(); - - const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle(); - - const vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle); - - const std::vector<glm::vec3> instancePositions = { - glm::vec3( 0.f, -2.f, 0.f), - glm::vec3( 3.f, 0.f, 0.f), - glm::vec3(-3.f, 0.f, 0.f), - glm::vec3( 0.f, 2.f, 0.f), - glm::vec3( 0.f, -5.f, 0.f) - }; - - std::vector<glm::mat4> modelMatrices; - std::vector<vkcv::DrawcallInfo> drawcalls; - std::vector<vkcv::DrawcallInfo> shadowDrawcalls; - for (const auto& position : instancePositions) { - modelMatrices.push_back(glm::translate(glm::mat4(1.f), position)); - drawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, { descriptorUsage },1)); - shadowDrawcalls.push_back(vkcv::DrawcallInfo(loadedMesh, {},1)); - } - - modelMatrices.back() *= glm::scale(glm::mat4(1.f), glm::vec3(10.f, 1.f, 10.f)); - - std::vector<std::array<glm::mat4, 2>> mainPassMatrices; - std::vector<glm::mat4> mvpLight; - - vkcv::ShaderProgram shadowShader; - shadowShader.addShader(vkcv::ShaderStage::VERTEX, "resources/shaders/shadow_vert.spv"); - shadowShader.addShader(vkcv::ShaderStage::FRAGMENT, "resources/shaders/shadow_frag.spv"); - - const vk::Format shadowMapFormat = vk::Format::eD16Unorm; - const std::vector<vkcv::AttachmentDescription> shadowAttachments = { - vkcv::AttachmentDescription(vkcv::AttachmentOperation::STORE, vkcv::AttachmentOperation::CLEAR, shadowMapFormat) - }; - const vkcv::PassConfig shadowPassConfig(shadowAttachments); - const vkcv::PassHandle shadowPass = core.createPass(shadowPassConfig); - - const uint32_t shadowMapResolution = 1024; - const vkcv::Image shadowMap = core.createImage(shadowMapFormat, shadowMapResolution, shadowMapResolution, 1); - const vkcv::PipelineConfig shadowPipeConfig { - shadowShader, - shadowMapResolution, - shadowMapResolution, - shadowPass, - firstMeshLayout, - {}, - false - }; - - const vkcv::PipelineHandle shadowPipe = core.createGraphicsPipeline(shadowPipeConfig); - - struct LightInfo { - glm::vec3 direction; - float padding; - glm::mat4 lightMatrix; - }; - LightInfo lightInfo; - vkcv::Buffer lightBuffer = core.createBuffer<LightInfo>(vkcv::BufferType::UNIFORM, sizeof(glm::vec3)); - - vkcv::DescriptorWrites setWrites; - setWrites.sampledImageWrites = { - vkcv::SampledImageDescriptorWrite(0, texture.getHandle()), - vkcv::SampledImageDescriptorWrite(3, shadowMap.getHandle()) }; - setWrites.samplerWrites = { - vkcv::SamplerDescriptorWrite(1, sampler), - vkcv::SamplerDescriptorWrite(4, shadowSampler) }; - setWrites.uniformBufferWrites = { vkcv::UniformBufferDescriptorWrite(2, lightBuffer.getHandle()) }; - core.writeDescriptorSet(descriptorSet, setWrites); - - auto start = std::chrono::system_clock::now(); - const auto appStartTime = start; - while (window.isWindowOpen()) { - window.pollEvents(); - - uint32_t swapchainWidth, swapchainHeight; - if (!core.beginFrame(swapchainWidth, swapchainHeight)) { - continue; - } - - if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) { - depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle(); - - windowWidth = swapchainWidth; - windowHeight = swapchainHeight; - } - - auto end = std::chrono::system_clock::now(); - auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start); - - start = end; - cameraManager.update(0.000001 * static_cast<double>(deltatime.count())); - - auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - appStartTime); - - const float sunTheta = 0.001f * static_cast<float>(duration.count()); - lightInfo.direction = glm::normalize(glm::vec3(std::cos(sunTheta), 1, std::sin(sunTheta))); - - const float shadowProjectionSize = 5.f; - glm::mat4 projectionLight = glm::ortho( - -shadowProjectionSize, - shadowProjectionSize, - -shadowProjectionSize, - shadowProjectionSize, - -shadowProjectionSize, - shadowProjectionSize); - - glm::mat4 vulkanCorrectionMatrix(1.f); - vulkanCorrectionMatrix[2][2] = 0.5; - vulkanCorrectionMatrix[3][2] = 0.5; - projectionLight = vulkanCorrectionMatrix * projectionLight; - - const glm::mat4 viewLight = glm::lookAt(glm::vec3(0), -lightInfo.direction, glm::vec3(0, -1, 0)); - - lightInfo.lightMatrix = projectionLight * viewLight; - lightBuffer.fill({ lightInfo }); - - const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP(); - - mainPassMatrices.clear(); - mvpLight.clear(); - for (const auto& m : modelMatrices) { - mainPassMatrices.push_back({ viewProjectionCamera * m, m }); - mvpLight.push_back(lightInfo.lightMatrix* m); - } - - vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4)); - const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer }; - - vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4)); - - auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); - - core.recordDrawcallsToCmdStream( - cmdStream, - shadowPass, - shadowPipe, - shadowPushConstantData, - shadowDrawcalls, - { shadowMap.getHandle() }); - - core.prepareImageForSampling(cmdStream, shadowMap.getHandle()); - - core.recordDrawcallsToCmdStream( - cmdStream, - firstMeshPass, - firstMeshPipeline, - pushConstantData, - drawcalls, - renderTargets); - core.prepareSwapchainImageForPresent(cmdStream); - core.submitCommandStream(cmdStream); - - core.endFrame(); - } - - return 0; -} diff --git a/projects/first_mesh/CMakeLists.txt b/projects/first_mesh/CMakeLists.txt index eb0f028db38707272f9fbcf61662633f2868eedc..6455e75d88eee276fb89b9f7a1b3462fcbc54da2 100644 --- a/projects/first_mesh/CMakeLists.txt +++ b/projects/first_mesh/CMakeLists.txt @@ -22,7 +22,7 @@ if(MSVC) endif() # including headers of dependencies and the VkCV framework -target_include_directories(first_mesh SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include}) +target_include_directories(first_mesh SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include}) # linking with libraries from all dependencies and the VkCV framework -target_link_libraries(first_mesh vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera) +target_link_libraries(first_mesh vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler) diff --git a/projects/first_mesh/resources/shaders/compile.bat b/projects/first_mesh/resources/shaders/compile.bat deleted file mode 100644 index b4521235c40fe5fb163bab874560c2f219b7517f..0000000000000000000000000000000000000000 --- a/projects/first_mesh/resources/shaders/compile.bat +++ /dev/null @@ -1,3 +0,0 @@ -%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv -%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv -pause \ No newline at end of file diff --git a/projects/first_mesh/resources/shaders/frag.spv b/projects/first_mesh/resources/shaders/frag.spv deleted file mode 100644 index 087e4e22fb2fcec27d99b3ff2aa1a705fe755796..0000000000000000000000000000000000000000 Binary files a/projects/first_mesh/resources/shaders/frag.spv and /dev/null differ diff --git a/projects/first_mesh/resources/shaders/vert.spv b/projects/first_mesh/resources/shaders/vert.spv deleted file mode 100644 index 374c023e14b351eb43cbcda5951cbb8b3d6f96a1..0000000000000000000000000000000000000000 Binary files a/projects/first_mesh/resources/shaders/vert.spv and /dev/null differ diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp index e7546fc3a143b3638cceb36869c519336ebec751..fc682ae1f8b3d1a174ff230c274b89093bc3325c 100644 --- a/projects/first_mesh/src/main.cpp +++ b/projects/first_mesh/src/main.cpp @@ -4,6 +4,7 @@ #include <vkcv/camera/CameraManager.hpp> #include <chrono> #include <vkcv/asset/asset_loader.hpp> +#include <vkcv/shader/GLSLCompiler.hpp> int main(int argc, const char** argv) { const char* applicationName = "First Mesh"; @@ -34,9 +35,8 @@ int main(int argc, const char** argv) { if (result == 1) { std::cout << "Mesh loading successful!" << std::endl; - } - else { - std::cout << "Mesh loading failed: " << result << std::endl; + } else { + std::cerr << "Mesh loading failed: " << result << std::endl; return 1; } @@ -74,14 +74,23 @@ int main(int argc, const char** argv) { vkcv::PassHandle firstMeshPass = core.createPass(firstMeshPassDefinition); if (!firstMeshPass) { - std::cout << "Error. Could not create renderpass. Exiting." << std::endl; + std::cerr << "Error. Could not create renderpass. Exiting." << std::endl; return EXIT_FAILURE; } - vkcv::ShaderProgram firstMeshProgram{}; - firstMeshProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv")); - firstMeshProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv")); + vkcv::ShaderProgram firstMeshProgram; + vkcv::shader::GLSLCompiler compiler; + + compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"), + [&firstMeshProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + firstMeshProgram.addShader(shaderStage, path); + }); + compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"), + [&firstMeshProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + firstMeshProgram.addShader(shaderStage, path); + }); + auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes; @@ -113,12 +122,15 @@ int main(int argc, const char** argv) { vkcv::PipelineHandle firstMeshPipeline = core.createGraphicsPipeline(firstMeshPipelineConfig); if (!firstMeshPipeline) { - std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl; + std::cerr << "Error. Could not create graphics pipeline. Exiting." << std::endl; + return EXIT_FAILURE; + } + + if (mesh.textures.empty()) { + std::cerr << "Error. No textures found. Exiting." << std::endl; return EXIT_FAILURE; } - // FIXME There should be a test here to make sure there is at least 1 - // texture in the mesh. vkcv::asset::Texture &tex = mesh.textures[0]; vkcv::Image texture = core.createImage(vk::Format::eR8G8B8A8Srgb, tex.w, tex.h); texture.fill(tex.data.data()); @@ -154,14 +166,13 @@ int main(int argc, const char** argv) { vkcv::camera::CameraManager cameraManager(window); uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); - uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL); cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -3)); auto start = std::chrono::system_clock::now(); while (window.isWindowOpen()) { - window.pollEvents(); + vkcv::Window::pollEvents(); if(window.getHeight() == 0 || window.getWidth() == 0) continue; @@ -185,7 +196,8 @@ int main(int argc, const char** argv) { cameraManager.update(0.000001 * static_cast<double>(deltatime.count())); glm::mat4 mvp = cameraManager.getActiveCamera().getMVP(); - vkcv::PushConstantData pushConstantData((void*)&mvp, sizeof(glm::mat4)); + vkcv::PushConstants pushConstants (sizeof(glm::mat4)); + pushConstants.appendDrawcall(mvp); const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer }; auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); @@ -194,7 +206,7 @@ int main(int argc, const char** argv) { cmdStream, firstMeshPass, firstMeshPipeline, - pushConstantData, + pushConstants, { drawcall }, renderTargets); core.prepareSwapchainImageForPresent(cmdStream); diff --git a/projects/first_scene/CMakeLists.txt b/projects/first_scene/CMakeLists.txt index 8b90739750011a36b4c1d9e0bff7cba986074228..ba2f7b1a7ae4845a12b9701269361a0a3f8affb7 100644 --- a/projects/first_scene/CMakeLists.txt +++ b/projects/first_scene/CMakeLists.txt @@ -22,7 +22,7 @@ if(MSVC) endif() # including headers of dependencies and the VkCV framework -target_include_directories(first_scene SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include}) +target_include_directories(first_scene SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_scene_include} ${vkcv_shader_compiler_include}) # linking with libraries from all dependencies and the VkCV framework -target_link_libraries(first_scene vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera) +target_link_libraries(first_scene vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_scene vkcv_shader_compiler) diff --git a/projects/first_scene/resources/Sponza/SponzaFloor.bin b/projects/first_scene/resources/Sponza/SponzaFloor.bin new file mode 100644 index 0000000000000000000000000000000000000000..684251288f35070d2e7d244877fd844cc00ca632 --- /dev/null +++ b/projects/first_scene/resources/Sponza/SponzaFloor.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:678455aca641cb1f449aa1a5054a7cae132be81c2b333aac283053967da66df0 +size 512 diff --git a/projects/first_scene/resources/Sponza/SponzaFloor.gltf b/projects/first_scene/resources/Sponza/SponzaFloor.gltf new file mode 100644 index 0000000000000000000000000000000000000000..b45f1c55ef85f2aa1d4bff01df3d9625aa38c809 --- /dev/null +++ b/projects/first_scene/resources/Sponza/SponzaFloor.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6deb75441b1138b50a6b0eec05e60df276fe8fb6d58118fdfce2090b6fbe734 +size 3139 diff --git a/projects/first_scene/resources/shaders/compile.bat b/projects/first_scene/resources/shaders/compile.bat deleted file mode 100644 index b4521235c40fe5fb163bab874560c2f219b7517f..0000000000000000000000000000000000000000 --- a/projects/first_scene/resources/shaders/compile.bat +++ /dev/null @@ -1,3 +0,0 @@ -%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv -%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv -pause \ No newline at end of file diff --git a/projects/first_scene/resources/shaders/frag.spv b/projects/first_scene/resources/shaders/frag.spv deleted file mode 100644 index 087e4e22fb2fcec27d99b3ff2aa1a705fe755796..0000000000000000000000000000000000000000 Binary files a/projects/first_scene/resources/shaders/frag.spv and /dev/null differ diff --git a/projects/first_scene/resources/shaders/vert.spv b/projects/first_scene/resources/shaders/vert.spv deleted file mode 100644 index 374c023e14b351eb43cbcda5951cbb8b3d6f96a1..0000000000000000000000000000000000000000 Binary files a/projects/first_scene/resources/shaders/vert.spv and /dev/null differ diff --git a/projects/first_scene/src/main.cpp b/projects/first_scene/src/main.cpp index 521818732f7a60eabe9f0c2c080c6d343a71b1d8..527eba8c3a1e020e14d92f5d305e2ddced936333 100644 --- a/projects/first_scene/src/main.cpp +++ b/projects/first_scene/src/main.cpp @@ -4,17 +4,8 @@ #include <vkcv/camera/CameraManager.hpp> #include <chrono> #include <vkcv/asset/asset_loader.hpp> -#include <vkcv/Logger.hpp> - -glm::mat4 arrayTo4x4Matrix(std::array<float,16> array){ - glm::mat4 matrix; - for (int i = 0; i < 4; i++){ - for (int j = 0; j < 4; j++){ - matrix[i][j] = array[j * 4 + i]; - } - } - return matrix; -} +#include <vkcv/shader/GLSLCompiler.hpp> +#include <vkcv/scene/Scene.hpp> int main(int argc, const char** argv) { const char* applicationName = "First Scene"; @@ -32,8 +23,8 @@ int main(int argc, const char** argv) { vkcv::camera::CameraManager cameraManager(window); uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL); - - cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -3)); + + cameraManager.getCamera(camIndex0).setPosition(glm::vec3(-8, 1, -0.5)); cameraManager.getCamera(camIndex0).setNearFar(0.1f, 30.0f); cameraManager.getCamera(camIndex1).setNearFar(0.1f, 30.0f); @@ -46,66 +37,10 @@ int main(int argc, const char** argv) { {}, { "VK_KHR_swapchain" } ); - - vkcv::asset::Scene scene; - - const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf"; - int result = vkcv::asset::loadScene(path, scene); - - if (result == 1) { - std::cout << "Mesh loading successful!" << std::endl; - } - else { - std::cout << "Mesh loading failed: " << result << std::endl; - return 1; - } - - assert(!scene.vertexGroups.empty()); - std::vector<std::vector<uint8_t>> vBuffers; - std::vector<std::vector<uint8_t>> iBuffers; - - std::vector<vkcv::VertexBufferBinding> vBufferBindings; - std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings; - std::vector<vkcv::asset::VertexAttribute> vAttributes; - - for (int i = 0; i < scene.vertexGroups.size(); i++) { - - vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data); - iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data); - - auto& attributes = scene.vertexGroups[i].vertexBuffer.attributes; - - std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) { - return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type); - }); - } - - std::vector<vkcv::Buffer<uint8_t>> vertexBuffers; - for (const vkcv::asset::VertexGroup& group : scene.vertexGroups) { - vertexBuffers.push_back(core.createBuffer<uint8_t>( - vkcv::BufferType::VERTEX, - group.vertexBuffer.data.size())); - vertexBuffers.back().fill(group.vertexBuffer.data); - } - - std::vector<vkcv::Buffer<uint8_t>> indexBuffers; - for (const auto& dataBuffer : iBuffers) { - indexBuffers.push_back(core.createBuffer<uint8_t>( - vkcv::BufferType::INDEX, - dataBuffer.size())); - indexBuffers.back().fill(dataBuffer); - } - - int vertexBufferIndex = 0; - for (const auto& vertexGroup : scene.vertexGroups) { - for (const auto& attribute : vertexGroup.vertexBuffer.attributes) { - vAttributes.push_back(attribute); - vBufferBindings.push_back(vkcv::VertexBufferBinding(attribute.offset, vertexBuffers[vertexBufferIndex].getVulkanHandle())); - } - vertexBufferBindings.push_back(vBufferBindings); - vBufferBindings.clear(); - vertexBufferIndex++; - } + + vkcv::scene::Scene scene = vkcv::scene::Scene::load(core, std::filesystem::path( + argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf" + )); const vkcv::AttachmentDescription present_color_attachment( vkcv::AttachmentOperation::STORE, @@ -127,9 +62,18 @@ int main(int argc, const char** argv) { return EXIT_FAILURE; } - vkcv::ShaderProgram sceneShaderProgram{}; - sceneShaderProgram.addShader(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/vert.spv")); - sceneShaderProgram.addShader(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/frag.spv")); + vkcv::ShaderProgram sceneShaderProgram; + vkcv::shader::GLSLCompiler compiler; + + compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"), + [&sceneShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + sceneShaderProgram.addShader(shaderStage, path); + }); + + compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"), + [&sceneShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + sceneShaderProgram.addShader(shaderStage, path); + }); const std::vector<vkcv::VertexAttachment> vertexAttachments = sceneShaderProgram.getVertexAttachments(); std::vector<vkcv::VertexBinding> bindings; @@ -138,41 +82,8 @@ int main(int argc, const char** argv) { } const vkcv::VertexLayout sceneLayout(bindings); - - uint32_t setID = 0; - - std::vector<vkcv::DescriptorBinding> descriptorBindings = { sceneShaderProgram.getReflectedDescriptors()[setID] }; - - vkcv::SamplerHandle sampler = core.createSampler( - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerFilterType::LINEAR, - vkcv::SamplerMipmapMode::LINEAR, - vkcv::SamplerAddressMode::REPEAT - ); - - std::vector<vkcv::Image> sceneImages; - std::vector<vkcv::DescriptorSetHandle> descriptorSets; - for (const auto& vertexGroup : scene.vertexGroups) { - descriptorSets.push_back(core.createDescriptorSet(descriptorBindings)); - - const auto& material = scene.materials[vertexGroup.materialIndex]; - - int baseColorIndex = material.baseColor; - if (baseColorIndex < 0) { - vkcv_log(vkcv::LogLevel::WARNING, "Material lacks base color"); - baseColorIndex = 0; - } - - vkcv::asset::Texture& sceneTexture = scene.textures[baseColorIndex]; - - sceneImages.push_back(core.createImage(vk::Format::eR8G8B8A8Srgb, sceneTexture.w, sceneTexture.h)); - sceneImages.back().fill(sceneTexture.data.data()); - - vkcv::DescriptorWrites setWrites; - setWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(0, sceneImages.back().getHandle()) }; - setWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(1, sampler) }; - core.writeDescriptorSet(descriptorSets.back(), setWrites); - } + + const auto& material0 = scene.getMaterial(0); const vkcv::PipelineConfig scenePipelineDefsinition{ sceneShaderProgram, @@ -180,7 +91,7 @@ int main(int argc, const char** argv) { UINT32_MAX, scenePass, {sceneLayout}, - { core.getDescriptorSet(descriptorSets[0]).layout }, + { core.getDescriptorSet(material0.getDescriptorSet()).layout }, true }; vkcv::PipelineHandle scenePipeline = core.createGraphicsPipeline(scenePipelineDefsinition); @@ -192,26 +103,7 @@ int main(int argc, const char** argv) { vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight).getHandle(); const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle(); - - std::vector<vkcv::DrawcallInfo> drawcalls; - for(int i = 0; i < scene.vertexGroups.size(); i++){ - vkcv::Mesh renderMesh(vertexBufferBindings[i], indexBuffers[i].getVulkanHandle(), scene.vertexGroups[i].numIndices); - - vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSets[i]).vulkanHandle); - - drawcalls.push_back(vkcv::DrawcallInfo(renderMesh, {descriptorUsage},1)); - } - - std::vector<glm::mat4> modelMatrices; - modelMatrices.resize(scene.vertexGroups.size(), glm::mat4(1.f)); - for (const auto &mesh : scene.meshes) { - const glm::mat4 m = arrayTo4x4Matrix(mesh.modelMatrix); - for (const auto &vertexGroupIndex : mesh.vertexGroups) { - modelMatrices[vertexGroupIndex] = m; - } - } - std::vector<glm::mat4> mvp; - + auto start = std::chrono::system_clock::now(); while (window.isWindowOpen()) { vkcv::Window::pollEvents(); @@ -236,25 +128,24 @@ int main(int argc, const char** argv) { start = end; cameraManager.update(0.000001 * static_cast<double>(deltatime.count())); - glm::mat4 vp = cameraManager.getActiveCamera().getMVP(); - - mvp.clear(); - for (const auto& m : modelMatrices) { - mvp.push_back(vp * m); - } - - vkcv::PushConstantData pushConstantData((void*)mvp.data(), sizeof(glm::mat4)); const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer }; auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); - core.recordDrawcallsToCmdStream( - cmdStream, - scenePass, - scenePipeline, - pushConstantData, - drawcalls, - renderTargets); + auto recordMesh = [](const glm::mat4& MVP, const glm::mat4& M, + vkcv::PushConstants &pushConstants, + vkcv::DrawcallInfo& drawcallInfo) { + pushConstants.appendDrawcall(MVP); + }; + + scene.recordDrawcalls(cmdStream, + cameraManager.getActiveCamera(), + scenePass, + scenePipeline, + sizeof(glm::mat4), + recordMesh, + renderTargets); + core.prepareSwapchainImageForPresent(cmdStream); core.submitCommandStream(cmdStream); core.endFrame(); diff --git a/projects/first_triangle/shaders/comp.spv b/projects/first_triangle/shaders/comp.spv deleted file mode 100644 index b414e36b2bea66dab00746298e536d029091e0fd..0000000000000000000000000000000000000000 Binary files a/projects/first_triangle/shaders/comp.spv and /dev/null differ diff --git a/projects/first_triangle/shaders/compile.bat b/projects/first_triangle/shaders/compile.bat deleted file mode 100644 index 17743a7c49cdfc6e091c43a42a0adb755a731682..0000000000000000000000000000000000000000 --- a/projects/first_triangle/shaders/compile.bat +++ /dev/null @@ -1,4 +0,0 @@ -%VULKAN_SDK%\Bin32\glslc.exe shader.vert -o vert.spv -%VULKAN_SDK%\Bin32\glslc.exe shader.frag -o frag.spv -%VULKAN_SDK%\Bin32\glslc.exe shader.comp -o comp.spv -pause \ No newline at end of file diff --git a/projects/first_triangle/shaders/frag.spv b/projects/first_triangle/shaders/frag.spv deleted file mode 100644 index cb13e606fc0041e24ff6a63c0ec7dcca466732aa..0000000000000000000000000000000000000000 Binary files a/projects/first_triangle/shaders/frag.spv and /dev/null differ diff --git a/projects/first_triangle/shaders/shader.comp b/projects/first_triangle/shaders/shader.comp deleted file mode 100644 index fad6cd0815f2f09bf92dcc3171e2e3723f5466df..0000000000000000000000000000000000000000 --- a/projects/first_triangle/shaders/shader.comp +++ /dev/null @@ -1,25 +0,0 @@ -#version 440 - -layout(std430, binding = 0) buffer testBuffer -{ - float test1[10]; - float test2[10]; - float test3[10]; -}; - -layout( push_constant ) uniform constants{ - float pushConstant; -}; - -layout(local_size_x = 5) in; - -void main(){ - - if(gl_GlobalInvocationID.x >= 10){ - return; - } - - test1[gl_GlobalInvocationID.x] = gl_GlobalInvocationID.x; - test2[gl_GlobalInvocationID.x] = 69; // nice! - test3[gl_GlobalInvocationID.x] = pushConstant; -} \ No newline at end of file diff --git a/projects/first_triangle/shaders/vert.spv b/projects/first_triangle/shaders/vert.spv deleted file mode 100644 index 03af5758ffff1b5b6505fe98b02044849026832d..0000000000000000000000000000000000000000 Binary files a/projects/first_triangle/shaders/vert.spv and /dev/null differ diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp index 5bdd55a263f4d81d8f424c056d7d6c0b54ccb1ca..3598da5f579b608d2c29f1f6fea0b0e25a560336 100644 --- a/projects/first_triangle/src/main.cpp +++ b/projects/first_triangle/src/main.cpp @@ -2,10 +2,8 @@ #include <vkcv/Core.hpp> #include <GLFW/glfw3.h> #include <vkcv/camera/CameraManager.hpp> -#include <chrono> - #include <vkcv/shader/GLSLCompiler.hpp> -#include <vkcv/gui/GUI.hpp> +#include <chrono> int main(int argc, const char** argv) { const char* applicationName = "First Triangle"; @@ -27,57 +25,11 @@ int main(int argc, const char** argv) { {}, { "VK_KHR_swapchain" } ); - - vkcv::gui::GUI gui (core, window); - - const auto& context = core.getContext(); - const vk::Instance& instance = context.getInstance(); - const vk::PhysicalDevice& physicalDevice = context.getPhysicalDevice(); - const vk::Device& device = context.getDevice(); - - struct vec3 { - float x, y, z; - }; - - const size_t n = 5027; - auto testBuffer = core.createBuffer<vec3>(vkcv::BufferType::VERTEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL); - vec3 vec_data[n]; - - for (size_t i = 0; i < n; i++) { - vec_data[i] = { 42, static_cast<float>(i), 7 }; - } - - testBuffer.fill(vec_data); - - auto triangleIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, n, vkcv::BufferMemoryType::DEVICE_LOCAL); + auto triangleIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, 3, vkcv::BufferMemoryType::DEVICE_LOCAL); uint16_t indices[3] = { 0, 1, 2 }; triangleIndexBuffer.fill(&indices[0], sizeof(indices)); - /*vec3* m = buffer.map(); - m[0] = { 0, 0, 0 }; - m[1] = { 0, 0, 0 }; - m[2] = { 0, 0, 0 }; - buffer.unmap();*/ - - vkcv::SamplerHandle sampler = core.createSampler( - vkcv::SamplerFilterType::NEAREST, - vkcv::SamplerFilterType::NEAREST, - vkcv::SamplerMipmapMode::NEAREST, - vkcv::SamplerAddressMode::REPEAT - ); - - std::cout << "Physical device: " << physicalDevice.getProperties().deviceName << std::endl; - - switch (physicalDevice.getProperties().vendorID) { - case 0x1002: std::cout << "Running AMD huh? You like underdogs, are you a Linux user?" << std::endl; break; - case 0x10DE: std::cout << "An NVidia GPU, how predictable..." << std::endl; break; - case 0x8086: std::cout << "Poor child, running on an Intel GPU, probably integrated..." - "or perhaps you are from the future with a dedicated one?" << std::endl; break; - case 0x13B5: std::cout << "ARM? What the hell are you running on, next thing I know you're trying to run Vulkan on a leg..." << std::endl; break; - default: std::cout << "Unknown GPU vendor?! Either you're on an exotic system or your driver is broken..." << std::endl; - } - // an example attachment for passes that output to the window const vkcv::AttachmentDescription present_color_attachment( vkcv::AttachmentOperation::STORE, @@ -93,7 +45,7 @@ int main(int argc, const char** argv) { return EXIT_FAILURE; } - vkcv::ShaderProgram triangleShaderProgram{}; + vkcv::ShaderProgram triangleShaderProgram; vkcv::shader::GLSLCompiler compiler; compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("shaders/shader.vert"), @@ -123,49 +75,9 @@ int main(int argc, const char** argv) { std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl; return EXIT_FAILURE; } - - // Compute Pipeline - vkcv::ShaderProgram computeShaderProgram{}; - computeShaderProgram.addShader(vkcv::ShaderStage::COMPUTE, std::filesystem::path("shaders/comp.spv")); - - // take care, assuming shader has exactly one descriptor set - vkcv::DescriptorSetHandle computeDescriptorSet = core.createDescriptorSet(computeShaderProgram.getReflectedDescriptors()[0]); - - vkcv::PipelineHandle computePipeline = core.createComputePipeline( - computeShaderProgram, - { core.getDescriptorSet(computeDescriptorSet).layout }); - - struct ComputeTestBuffer { - float test1[10]; - float test2[10]; - float test3[10]; - }; - - vkcv::Buffer computeTestBuffer = core.createBuffer<ComputeTestBuffer>(vkcv::BufferType::STORAGE, 1); - - vkcv::DescriptorWrites computeDescriptorWrites; - computeDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, computeTestBuffer.getHandle()) }; - core.writeDescriptorSet(computeDescriptorSet, computeDescriptorWrites); - - /* - * BufferHandle triangleVertices = core.createBuffer(vertices); - * BufferHandle triangleIndices = core.createBuffer(indices); - * - * // triangle Model creation goes here - * - * - * // attachment creation goes here - * PassHandle trianglePass = core.CreatePass(presentationPass); - * - * // shader creation goes here - * // material creation goes here - * - * PipelineHandle trianglePipeline = core.CreatePipeline(trianglePipeline); - */ + auto start = std::chrono::system_clock::now(); - vkcv::ImageHandle swapchainImageHandle = vkcv::ImageHandle::createSwapchainImageHandle(); - const vkcv::Mesh renderMesh({}, triangleIndexBuffer.getVulkanHandle(), 3); vkcv::DrawcallInfo drawcall(renderMesh, {},1); @@ -181,7 +93,7 @@ int main(int argc, const char** argv) { while (window.isWindowOpen()) { - window.pollEvents(); + vkcv::Window::pollEvents(); uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem if (!core.beginFrame(swapchainWidth, swapchainHeight)) { @@ -195,37 +107,21 @@ int main(int argc, const char** argv) { cameraManager.update(0.000001 * static_cast<double>(deltatime.count())); glm::mat4 mvp = cameraManager.getActiveCamera().getMVP(); - vkcv::PushConstantData pushConstantData((void*)&mvp, sizeof(glm::mat4)); + vkcv::PushConstants pushConstants (sizeof(glm::mat4)); + pushConstants.appendDrawcall(mvp); + auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); core.recordDrawcallsToCmdStream( cmdStream, trianglePass, trianglePipeline, - pushConstantData, + pushConstants, { drawcall }, { swapchainInput }); - const uint32_t dispatchSize[3] = { 2, 1, 1 }; - const float theMeaningOfLife = 42; - - core.recordComputeDispatchToCmdStream( - cmdStream, - computePipeline, - dispatchSize, - { vkcv::DescriptorSetUsage(0, core.getDescriptorSet(computeDescriptorSet).vulkanHandle) }, - vkcv::PushConstantData((void*)&theMeaningOfLife, sizeof(theMeaningOfLife))); - core.prepareSwapchainImageForPresent(cmdStream); core.submitCommandStream(cmdStream); - - gui.beginGUI(); - - ImGui::Begin("Hello world"); - ImGui::Text("This is a test!"); - ImGui::End(); - - gui.endGUI(); core.endFrame(); } diff --git a/projects/indirect_dispatch/.gitignore b/projects/indirect_dispatch/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5f18d9c205e538dabeb0282640bede0359edc33d --- /dev/null +++ b/projects/indirect_dispatch/.gitignore @@ -0,0 +1 @@ +indirect_dispatch diff --git a/projects/indirect_dispatch/CMakeLists.txt b/projects/indirect_dispatch/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..7bc86cbc8470352f13bbfcc62f793b0a99d92884 --- /dev/null +++ b/projects/indirect_dispatch/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.16) +project(indirect_dispatch) + +# setting c++ standard for the project +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# this should fix the execution path to load local files from the project +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +# adding source files to the project +add_executable(indirect_dispatch src/main.cpp) + +target_sources(indirect_dispatch PRIVATE + src/App.hpp + src/App.cpp + + src/AppConfig.hpp + src/MotionBlurConfig.hpp + + src/AppSetup.hpp + src/AppSetup.cpp + + src/MotionBlur.hpp + src/MotionBlur.cpp + + src/MotionBlurSetup.hpp + src/MotionBlurSetup.cpp) + +# this should fix the execution path to load local files from the project (for MSVC) +if(MSVC) + set_target_properties(indirect_dispatch PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + set_target_properties(indirect_dispatch PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + + # in addition to setting the output directory, the working directory has to be set + # by default visual studio sets the working directory to the build directory, when using the debugger + set_target_properties(indirect_dispatch PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) +endif() + +# including headers of dependencies and the VkCV framework +target_include_directories(indirect_dispatch SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include}) + +# linking with libraries from all dependencies and the VkCV framework +target_link_libraries(indirect_dispatch vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_testing vkcv_camera vkcv_shader_compiler vkcv_gui) \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/models/cube.bin b/projects/indirect_dispatch/resources/models/cube.bin new file mode 100644 index 0000000000000000000000000000000000000000..728d38cd39cd10c30a93c15eef021cb0cf7dda74 --- /dev/null +++ b/projects/indirect_dispatch/resources/models/cube.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccc59e0be3552b4347457bc935d8e548b52f12ca91716a0f0dc37d5bac65f123 +size 840 diff --git a/projects/indirect_dispatch/resources/models/cube.gltf b/projects/indirect_dispatch/resources/models/cube.gltf new file mode 100644 index 0000000000000000000000000000000000000000..ef975c326c71ec1a2fa650a422989534f1c32191 --- /dev/null +++ b/projects/indirect_dispatch/resources/models/cube.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0072448af64bdebffe8eec5a7f32f110579b1a256cd97438bf227e4cc4a87328 +size 2571 diff --git a/projects/indirect_dispatch/resources/models/grid.png b/projects/indirect_dispatch/resources/models/grid.png new file mode 100644 index 0000000000000000000000000000000000000000..5f40eee62f7f9dba3dc156ff6a3653ea2e7f5391 --- /dev/null +++ b/projects/indirect_dispatch/resources/models/grid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a11c33e4935d93723ab11f597f2aca1ca1ff84af66f2e2d10a01580eb0b7831a +size 40135 diff --git a/projects/indirect_dispatch/resources/models/ground.bin b/projects/indirect_dispatch/resources/models/ground.bin new file mode 100644 index 0000000000000000000000000000000000000000..e29e4f18552def1ac64c167d994be959f82e35c7 --- /dev/null +++ b/projects/indirect_dispatch/resources/models/ground.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8e20cd1c62da3111536283517b63a149f258ea82b1dff8ddafdb79020065b7c +size 140 diff --git a/projects/indirect_dispatch/resources/models/ground.gltf b/projects/indirect_dispatch/resources/models/ground.gltf new file mode 100644 index 0000000000000000000000000000000000000000..6935d3e21a06da1629087c9b0b7f957c57feaf6e --- /dev/null +++ b/projects/indirect_dispatch/resources/models/ground.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a12b8d7cca8110d4ffa9bc4a2223286d1ccfd9c087739a75294e0a3fbfb65c5 +size 2840 diff --git a/projects/indirect_dispatch/resources/shaders/gammaCorrection.comp b/projects/indirect_dispatch/resources/shaders/gammaCorrection.comp new file mode 100644 index 0000000000000000000000000000000000000000..7a6e129d7f8658d3ea424d35b809a3384d12bccc --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/gammaCorrection.comp @@ -0,0 +1,24 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +layout(set=0, binding=0) uniform texture2D inTexture; +layout(set=0, binding=1) uniform sampler textureSampler; +layout(set=0, binding=2, rgba8) uniform image2D outImage; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +void main(){ + + ivec2 outImageRes = imageSize(outImage); + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); + + if(any(greaterThanEqual(coord, outImageRes))) + return; + + vec2 uv = vec2(coord) / outImageRes; + vec3 linearColor = texture(sampler2D(inTexture, textureSampler), uv).rgb; + + vec3 gammaCorrected = pow(linearColor, vec3(1 / 2.2)); + + imageStore(outImage, coord, vec4(gammaCorrected, 0.f)); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/mesh.frag b/projects/indirect_dispatch/resources/shaders/mesh.frag new file mode 100644 index 0000000000000000000000000000000000000000..531c9cbf8b5e097af618d2ca639821a62a30611d --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/mesh.frag @@ -0,0 +1,17 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 passNormal; +layout(location = 1) in vec2 passUV; + +layout(location = 0) out vec3 outColor; + +layout(set=0, binding=0) uniform texture2D albedoTexture; +layout(set=0, binding=1) uniform sampler textureSampler; + +void main() { + vec3 albedo = texture(sampler2D(albedoTexture, textureSampler), passUV).rgb; + vec3 N = normalize(passNormal); + float light = max(N.y * 0.5 + 0.5, 0); + outColor = light * albedo; +} \ No newline at end of file diff --git a/projects/bloom/resources/shaders/shader.vert b/projects/indirect_dispatch/resources/shaders/mesh.vert similarity index 68% rename from projects/bloom/resources/shaders/shader.vert rename to projects/indirect_dispatch/resources/shaders/mesh.vert index 926f86af2860cb57c44d2d5ee78712b6ae155e5c..734fd63cdee66e5fbf61cc427ca21fae18a31d82 100644 --- a/projects/bloom/resources/shaders/shader.vert +++ b/projects/indirect_dispatch/resources/shaders/mesh.vert @@ -7,7 +7,6 @@ layout(location = 2) in vec2 inUV; layout(location = 0) out vec3 passNormal; layout(location = 1) out vec2 passUV; -layout(location = 2) out vec3 passPos; layout( push_constant ) uniform constants{ mat4 mvp; @@ -16,7 +15,6 @@ layout( push_constant ) uniform constants{ void main() { gl_Position = mvp * vec4(inPosition, 1.0); - passNormal = mat3(model) * inNormal; // assuming no weird stuff like shearing or non-uniform scaling + passNormal = (model * vec4(inNormal, 0)).xyz; passUV = inUV; - passPos = (model * vec4(inPosition, 1)).xyz; } \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionBlur.comp b/projects/indirect_dispatch/resources/shaders/motionBlur.comp new file mode 100644 index 0000000000000000000000000000000000000000..091c21aa7ddfe9db1780aa64adc77fd5457a3843 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionBlur.comp @@ -0,0 +1,194 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +#include "motionBlur.inc" +#include "motionBlurConfig.inc" +#include "motionBlurWorkTile.inc" + +layout(set=0, binding=0) uniform texture2D inColor; +layout(set=0, binding=1) uniform texture2D inDepth; +layout(set=0, binding=2) uniform texture2D inMotionFullRes; +layout(set=0, binding=3) uniform texture2D inMotionNeighbourhoodMax; +layout(set=0, binding=4) uniform sampler nearestSampler; +layout(set=0, binding=5, r11f_g11f_b10f) uniform image2D outImage; + +layout(set=0, binding=6) buffer WorkTileBuffer { + WorkTiles workTiles; +}; + +layout(local_size_x = motionTileSize, local_size_y = motionTileSize, local_size_z = 1) in; + +layout( push_constant ) uniform constants{ + // computed from delta time and shutter speed + float motionScaleFactor; + // camera planes are needed to linearize depth + float cameraNearPlane; + float cameraFarPlane; + float motionTileOffsetLength; +}; + +float linearizeDepth(float depth, float near, float far){ + return near * far / (far + depth * (near - far)); +} + +struct SampleData{ + vec3 color; + float depthLinear; + vec2 coordinate; + vec2 motion; + float velocityPixels; +}; + +struct PointSpreadCompare{ + float foreground; + float background; +}; + +// results in range [0, 1] +// computes if the sample pixel in the foreground would blur over the main pixel and if the sample pixel in the background would be part of the main pixel background +// contribution depends on if the distance between pixels is smaller than it's velocity +// note that compared to the constant falloff used in McGuire's papers this function from Jimenez is constant until the last pixel +// this is important for the later gradient computation +PointSpreadCompare samplePointSpreadCompare(SampleData mainPixel, SampleData samplePixel){ + + float sampleOffset = distance(mainPixel.coordinate, samplePixel.coordinate); + + PointSpreadCompare pointSpread; + pointSpread.foreground = clamp(1 - sampleOffset + samplePixel.velocityPixels, 0, 1); + pointSpread.background = clamp(1 - sampleOffset + mainPixel.velocityPixels, 0, 1); + + return pointSpread; +} + +struct DepthClassification{ + float foreground; + float background; +}; + +// classifies depthSample compared to depthMain in regards to being in the fore- or background +// the range is [0, 1] and sums to 1 +DepthClassification sampleDepthClassification(SampleData mainPixel, SampleData samplePixel){ + + const float softDepthExtent = 0.1; + + DepthClassification classification; + // only the sign is different, so the latter term will cancel out on addition, so only two times 0.5 remains which sums to one + classification.foreground = clamp(0.5 + (mainPixel.depthLinear - samplePixel.depthLinear) / softDepthExtent, 0, 1); + classification.background = clamp(0.5 - (mainPixel.depthLinear - samplePixel.depthLinear) / softDepthExtent, 0, 1); + return classification; +} + +// reconstruction filter and helper functions from "Next Generation Post Processing in Call of Duty Advanced Warfare", Jimenez +// returns value in range [0, 1] +float computeSampleWeigth(SampleData mainPixel, SampleData samplePixel){ + + PointSpreadCompare pointSpread = samplePointSpreadCompare( mainPixel, samplePixel); + DepthClassification depthClassification = sampleDepthClassification(mainPixel, samplePixel); + + return + depthClassification.foreground * pointSpread.foreground + + depthClassification.background * pointSpread.background; +} + +SampleData loadSampleData(vec2 uv){ + + SampleData data; + data.color = texture(sampler2D(inColor, nearestSampler), uv).rgb; + data.coordinate = ivec2(uv * imageSize(outImage)); + data.motion = processMotionVector(texture(sampler2D(inMotionFullRes, nearestSampler), uv).rg, motionScaleFactor, imageSize(outImage)); + data.velocityPixels = length(data.motion * imageSize(outImage)); + data.depthLinear = texture(sampler2D(inDepth, nearestSampler), uv).r; + data.depthLinear = linearizeDepth(data.depthLinear, cameraNearPlane, cameraFarPlane); + + return data; +} + +void main(){ + + uint tileIndex = gl_WorkGroupID.x; + ivec2 tileCoordinates = workTiles.tileXY[tileIndex]; + ivec2 coord = ivec2(tileCoordinates * motionTileSize + gl_LocalInvocationID.xy); + + if(any(greaterThanEqual(coord, imageSize(outImage)))) + return; + + ivec2 textureRes = textureSize(sampler2D(inColor, nearestSampler), 0); + vec2 uv = vec2(coord + 0.5) / textureRes; // + 0.5 to shift uv into pixel center + + // the motion tile lookup is jittered, so the hard edges in the blur are replaced by noise + // dither is shifted, so it does not line up with motion tiles + float motionOffset = motionTileOffsetLength * (dither(coord + ivec2(ditherSize / 2)) * 2 - 1); + vec2 motionNeighbourhoodMax = processMotionVector(texelFetch(sampler2D(inMotionNeighbourhoodMax, nearestSampler), ivec2(coord + motionOffset) / motionTileSize, 0).rg, motionScaleFactor, imageSize(outImage)); + + SampleData mainPixel = loadSampleData(uv); + + // early out on movement less than half a pixel + if(length(motionNeighbourhoodMax * imageSize(outImage)) <= 0.5){ + imageStore(outImage, coord, vec4(mainPixel.color, 0.f)); + return; + } + + vec3 color = vec3(0); + float weightSum = 0; + + // clamping start and end points avoids artifacts at image borders + // the sampler clamps the sample uvs anyways, but without clamping here, many samples can be stuck at the border + vec2 uvStart = clamp(uv - motionNeighbourhoodMax, 0, 1); + vec2 uvEnd = clamp(uv + motionNeighbourhoodMax, 0, 1); + + // samples are placed evenly, but the entire filter is jittered + // dither returns either 0 or 1 + // the sampleUV code expects an offset in range [-0.5, 0.5], so the dither is rescaled to a binary -0.25/0.25 + float random = dither(coord) * 0.5 - 0.25; + + const int sampleCountHalf = 8; + + // two samples are processed at a time to allow for mirrored background reconstruction + for(int i = 0; i < sampleCountHalf; i++){ + + vec2 sampleUV1 = mix(uv, uvEnd, (i + random + 1) / float(sampleCountHalf + 1)); + vec2 sampleUV2 = mix(uv, uvStart, (i + random + 1) / float(sampleCountHalf + 1)); + + SampleData sample1 = loadSampleData(sampleUV1); + SampleData sample2 = loadSampleData(sampleUV2); + + float weight1 = computeSampleWeigth(mainPixel, sample1); + float weight2 = computeSampleWeigth(mainPixel, sample2); + + bool mirroredBackgroundReconstruction = true; + if(mirroredBackgroundReconstruction){ + // see Jimenez paper for details and comparison + // problem is that in the foreground the background is reconstructed, which is blurry + // in the background the background is obviously known, so it is sharper + // at the border between fore- and background this causes a discontinuity + // to fix this the weights are mirrored on this border, effectively reconstructing the background, even though it is known + + // these bools check if sample1 is an affected background pixel (further away and slower moving than sample2) + bool inBackground = sample1.depthLinear > sample2.depthLinear; + bool blurredOver = sample1.velocityPixels < sample2.velocityPixels; + + // this mirrors the weights depending on the results: + // if both conditions are true, then weight2 is mirrored to weight1 + // if both conditions are false, then weight1 is mirrored to weight2, as sample2 is an affected background pixel + // if only one condition is true, then the weights are kept as is + weight1 = inBackground && blurredOver ? weight2 : weight1; + weight2 = inBackground || blurredOver ? weight2 : weight1; + } + + weightSum += weight1; + weightSum += weight2; + + color += sample1.color * weight1; + color += sample2.color * weight2; + } + + // normalize color and weight + weightSum /= sampleCountHalf * 2; + color /= sampleCountHalf * 2; + + // the main color is considered the background + // the weight sum can be interpreted as the alpha of the combined samples, see Jimenez paper + color += (1 - weightSum) * mainPixel.color; + + imageStore(outImage, coord, vec4(color, 0.f)); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionBlur.inc b/projects/indirect_dispatch/resources/shaders/motionBlur.inc new file mode 100644 index 0000000000000000000000000000000000000000..6fdaf4c5f5e4b07a3111946b0732137f42f295ef --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionBlur.inc @@ -0,0 +1,35 @@ +#ifndef MOTION_BLUR +#define MOTION_BLUR + +#include "motionBlurConfig.inc" + +// see "A Reconstruction Filter for Plausible Motion Blur", section 2.2 +vec2 processMotionVector(vec2 motion, float motionScaleFactor, ivec2 imageResolution){ + // every frame a pixel should blur over the distance it moves + // as we blur in two directions (where it was and where it will be) we must half the motion + vec2 motionHalf = motion * 0.5; + vec2 motionScaled = motionHalf * motionScaleFactor; // scale factor contains shutter speed and delta time + + // pixels are anisotropic, so the ratio for clamping the velocity is computed in pixels instead of uv coordinates + vec2 motionPixel = motionScaled * imageResolution; + float velocityPixels = length(motionPixel); + + float epsilon = 0.0001; + + // this clamps the motion to not exceed the radius given by the motion tile size + return motionScaled * max(0.5, min(velocityPixels, motionTileSize)) / (velocityPixels + epsilon); +} + +const int ditherSize = 4; + +// simple binary dither pattern +// could be optimized to avoid modulo and branch +float dither(ivec2 coord){ + + bool x = coord.x % ditherSize < (ditherSize / 2); + bool y = coord.y % ditherSize < (ditherSize / 2); + + return x ^^ y ? 1 : 0; +} + +#endif // #ifndef MOTION_BLUR \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionBlurColorCopy.comp b/projects/indirect_dispatch/resources/shaders/motionBlurColorCopy.comp new file mode 100644 index 0000000000000000000000000000000000000000..1d8f210c86c2670241fa1d011835b120a39eddc0 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionBlurColorCopy.comp @@ -0,0 +1,29 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +#include "motionBlurConfig.inc" +#include "motionBlurWorkTile.inc" + +layout(set=0, binding=0) uniform texture2D inColor; +layout(set=0, binding=1) uniform sampler nearestSampler; +layout(set=0, binding=2, r11f_g11f_b10f) uniform image2D outImage; + +layout(set=0, binding=3) buffer WorkTileBuffer { + WorkTiles workTiles; +}; + +layout(local_size_x = motionTileSize, local_size_y = motionTileSize, local_size_z = 1) in; + +void main(){ + + uint tileIndex = gl_WorkGroupID.x; + ivec2 tileCoordinates = workTiles.tileXY[tileIndex]; + ivec2 coordinate = ivec2(tileCoordinates * motionTileSize + gl_LocalInvocationID.xy); + + if(any(greaterThanEqual(coordinate, imageSize(outImage)))) + return; + + vec3 color = texelFetch(sampler2D(inColor, nearestSampler), coordinate, 0).rgb; + + imageStore(outImage, coordinate, vec4(color, 0.f)); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionBlurConfig.inc b/projects/indirect_dispatch/resources/shaders/motionBlurConfig.inc new file mode 100644 index 0000000000000000000000000000000000000000..5b8679da119d84242c55d7d89de80ed8b64e5cc9 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionBlurConfig.inc @@ -0,0 +1,8 @@ +#ifndef MOTION_BLUR_CONFIG +#define MOTION_BLUR_CONFIG + +const int motionTileSize = 16; +const int maxMotionBlurWidth = 3840; +const int maxMotionBlurHeight = 2160; + +#endif // #ifndef MOTION_BLUR_CONFIG \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionBlurFastPath.comp b/projects/indirect_dispatch/resources/shaders/motionBlurFastPath.comp new file mode 100644 index 0000000000000000000000000000000000000000..2e27ebedcc4be1da93ff89a187fe1d3e992e8d22 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionBlurFastPath.comp @@ -0,0 +1,68 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +#include "motionBlur.inc" +#include "motionBlurConfig.inc" +#include "motionBlurWorkTile.inc" + +layout(set=0, binding=0) uniform texture2D inColor; +layout(set=0, binding=1) uniform texture2D inMotionNeighbourhoodMax; +layout(set=0, binding=2) uniform sampler nearestSampler; +layout(set=0, binding=3, r11f_g11f_b10f) uniform image2D outImage; + +layout(set=0, binding=4) buffer WorkTileBuffer { + WorkTiles workTiles; +}; + +layout(local_size_x = motionTileSize, local_size_y = motionTileSize, local_size_z = 1) in; + +layout( push_constant ) uniform constants{ + // computed from delta time and shutter speed + float motionScaleFactor; +}; + +void main(){ + + uint tileIndex = gl_WorkGroupID.x; + ivec2 tileCoordinates = workTiles.tileXY[tileIndex]; + ivec2 coord = ivec2(tileCoordinates * motionTileSize + gl_LocalInvocationID.xy); + + if(any(greaterThanEqual(coord, imageSize(outImage)))) + return; + + ivec2 textureRes = textureSize(sampler2D(inColor, nearestSampler), 0); + vec2 uv = vec2(coord + 0.5) / textureRes; // + 0.5 to shift uv into pixel center + + vec2 motionNeighbourhoodMax = processMotionVector(texelFetch(sampler2D(inMotionNeighbourhoodMax, nearestSampler), coord / motionTileSize, 0).rg, motionScaleFactor, imageSize(outImage)); + + // early out on movement less than half a pixel + if(length(motionNeighbourhoodMax * imageSize(outImage)) <= 0.5){ + vec3 color = texture(sampler2D(inColor, nearestSampler), uv).rgb; + imageStore(outImage, coord, vec4(color, 0.f)); + return; + } + + vec3 color = vec3(0); + + // clamping start and end points avoids artifacts at image borders + // the sampler clamps the sample uvs anyways, but without clamping here, many samples can be stuck at the border + vec2 uvStart = clamp(uv - motionNeighbourhoodMax, 0, 1); + vec2 uvEnd = clamp(uv + motionNeighbourhoodMax, 0, 1); + + // samples are placed evenly, but the entire filter is jittered + // dither returns either 0 or 1 + // the sampleUV code expects an offset in range [-0.5, 0.5], so the dither is rescaled to a binary -0.25/0.25 + float random = dither(coord) * 0.5 - 0.25; + + const int sampleCount = 16; + + for(int i = 0; i < sampleCount; i++){ + + vec2 sampleUV = mix(uvStart, uvEnd, (i + random + 1) / float(sampleCount + 1)); + color += texture(sampler2D(inColor, nearestSampler), sampleUV).rgb; + } + + color /= sampleCount; + + imageStore(outImage, coord, vec4(color, 0.f)); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionBlurTileClassification.comp b/projects/indirect_dispatch/resources/shaders/motionBlurTileClassification.comp new file mode 100644 index 0000000000000000000000000000000000000000..3c6f9e3715951ac4fe6770725c3314590cbbff47 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionBlurTileClassification.comp @@ -0,0 +1,58 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +#include "motionBlurWorkTile.inc" + +layout(set=0, binding=0) uniform texture2D inMotionMax; +layout(set=0, binding=1) uniform texture2D inMotionMin; +layout(set=0, binding=2) uniform sampler nearestSampler; + +layout(set=0, binding=3) buffer FullPathTileBuffer { + WorkTiles fullPathTiles; +}; + +layout(set=0, binding=4) buffer CopyPathTileBuffer { + WorkTiles copyPathTiles; +}; + +layout(set=0, binding=5) buffer FastPathTileBuffer { + WorkTiles fastPathTiles; +}; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout( push_constant ) uniform constants{ + uint width; + uint height; + float fastPathThreshold; +}; + +void main(){ + + ivec2 tileCoord = ivec2(gl_GlobalInvocationID.xy); + + if(any(greaterThanEqual(gl_GlobalInvocationID.xy, textureSize(sampler2D(inMotionMax, nearestSampler), 0)))) + return; + + vec2 motionMax = texelFetch(sampler2D(inMotionMax, nearestSampler), tileCoord, 0).rg; + vec2 motionMin = texelFetch(sampler2D(inMotionMin, nearestSampler), tileCoord, 0).rg; + + vec2 motionPixelMax = motionMax * vec2(width, height); + vec2 motionPixelMin = motionMin * vec2(width, height); + + float velocityPixelMax = length(motionPixelMax); + float minMaxDistance = distance(motionPixelMin, motionPixelMax); + + if(velocityPixelMax <= 0.5){ + uint index = atomicAdd(copyPathTiles.tileCount, 1); + copyPathTiles.tileXY[index] = tileCoord; + } + else if(minMaxDistance <= fastPathThreshold){ + uint index = atomicAdd(fastPathTiles.tileCount, 1); + fastPathTiles.tileXY[index] = tileCoord; + } + else{ + uint index = atomicAdd(fullPathTiles.tileCount, 1); + fullPathTiles.tileXY[index] = tileCoord; + } +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionBlurTileClassificationVis.comp b/projects/indirect_dispatch/resources/shaders/motionBlurTileClassificationVis.comp new file mode 100644 index 0000000000000000000000000000000000000000..3382ff5ef0b407b9a3a7785eda0d19efe5a8f96e --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionBlurTileClassificationVis.comp @@ -0,0 +1,56 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +#include "motionBlurConfig.inc" +#include "motionBlurWorkTile.inc" + +layout(set=0, binding=0) uniform texture2D inColor; +layout(set=0, binding=1) uniform sampler nearestSampler; +layout(set=0, binding=2, r11f_g11f_b10f) uniform image2D outImage; + +layout(set=0, binding=3) buffer FullPathTileBuffer { + WorkTiles fullPathTiles; +}; + +layout(set=0, binding=4) buffer CopyPathTileBuffer { + WorkTiles copyPathTiles; +}; + +layout(set=0, binding=5) buffer FastPathTileBuffer { + WorkTiles fastPathTiles; +}; + +layout(local_size_x = motionTileSize, local_size_y = motionTileSize, local_size_z = 1) in; + +void main(){ + + uint tileIndexFullPath = gl_WorkGroupID.x; + uint tileIndexCopyPath = gl_WorkGroupID.x - fullPathTiles.tileCount; + uint tileIndexFastPath = gl_WorkGroupID.x - fullPathTiles.tileCount - copyPathTiles.tileCount; + + vec3 debugColor; + ivec2 tileCoordinates; + + if(tileIndexFullPath < fullPathTiles.tileCount){ + debugColor = vec3(1, 0, 0); + tileCoordinates = fullPathTiles.tileXY[tileIndexFullPath]; + } + else if(tileIndexCopyPath < copyPathTiles.tileCount){ + debugColor = vec3(0, 1, 0); + tileCoordinates = copyPathTiles.tileXY[tileIndexCopyPath]; + } + else if(tileIndexFastPath < fastPathTiles.tileCount){ + debugColor = vec3(0, 0, 1); + tileCoordinates = fastPathTiles.tileXY[tileIndexFastPath]; + } + else{ + return; + } + + ivec2 coordinate = ivec2(tileCoordinates * motionTileSize + gl_LocalInvocationID.xy); + vec3 color = texelFetch(sampler2D(inColor, nearestSampler), coordinate, 0).rgb; + + color = mix(color, debugColor, 0.5); + + imageStore(outImage, coordinate, vec4(color, 0)); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionBlurWorkTile.inc b/projects/indirect_dispatch/resources/shaders/motionBlurWorkTile.inc new file mode 100644 index 0000000000000000000000000000000000000000..8577f100aac524b93eecac406606a962bc52d222 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionBlurWorkTile.inc @@ -0,0 +1,19 @@ +#ifndef MOTION_BLUR_WORK_TILE +#define MOTION_BLUR_WORK_TILE + +#include "motionBlurConfig.inc" + +const int maxTileCount = + (maxMotionBlurWidth + motionTileSize - 1) / motionTileSize * + (maxMotionBlurHeight + motionTileSize - 1) / motionTileSize; + +struct WorkTiles{ + uint tileCount; + // dispatch Y/Z are here so the buffer can be used directly as an indirect dispatch argument buffer + uint dispatchY; + uint dispatchZ; + + ivec2 tileXY[maxTileCount]; +}; + +#endif // #ifndef MOTION_BLUR_WORK_TILE \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionBlurWorkTileReset.comp b/projects/indirect_dispatch/resources/shaders/motionBlurWorkTileReset.comp new file mode 100644 index 0000000000000000000000000000000000000000..d4b55582a0a18c0c6a3fecf1dd6ce69ed49ca2c1 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionBlurWorkTileReset.comp @@ -0,0 +1,32 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +#include "motionBlurWorkTile.inc" + +layout(set=0, binding=0) buffer FullPathTileBuffer { + WorkTiles fullPathTiles; +}; + +layout(set=0, binding=1) buffer CopyPathTileBuffer { + WorkTiles copyPathTiles; +}; + +layout(set=0, binding=2) buffer FastPathTileBuffer { + WorkTiles fastPathTiles; +}; + +layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; + +void main(){ + fullPathTiles.tileCount = 0; + fullPathTiles.dispatchY = 1; + fullPathTiles.dispatchZ = 1; + + copyPathTiles.tileCount = 0; + copyPathTiles.dispatchY = 1; + copyPathTiles.dispatchZ = 1; + + fastPathTiles.tileCount = 0; + fastPathTiles.dispatchY = 1; + fastPathTiles.dispatchZ = 1; +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionVector.inc b/projects/indirect_dispatch/resources/shaders/motionVector.inc new file mode 100644 index 0000000000000000000000000000000000000000..498478cbc38b9666366eaa3d3e1a715dfc30236b --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionVector.inc @@ -0,0 +1,9 @@ +vec2 computeMotionVector(vec4 NDC, vec4 NDCPrevious){ + vec2 ndc = NDC.xy / NDC.w; + vec2 ndcPrevious = NDCPrevious.xy / NDCPrevious.w; + + vec2 uv = ndc * 0.5 + 0.5; + vec2 uvPrevious = ndcPrevious * 0.5 + 0.5; + + return uvPrevious - uv; +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionVectorMinMax.comp b/projects/indirect_dispatch/resources/shaders/motionVectorMinMax.comp new file mode 100644 index 0000000000000000000000000000000000000000..4ad350b0d5300aa63a66d7aceb00ea0b642d07ee --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionVectorMinMax.comp @@ -0,0 +1,48 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable +#include "motionBlurConfig.inc" + +layout(set=0, binding=0) uniform texture2D inMotion; +layout(set=0, binding=1) uniform sampler textureSampler; +layout(set=0, binding=2, rg16) uniform image2D outMotionMax; +layout(set=0, binding=3, rg16) uniform image2D outMotionMin; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +void main(){ + + ivec2 outImageRes = imageSize(outMotionMax); + ivec2 motionTileCoord = ivec2(gl_GlobalInvocationID.xy); + + if(any(greaterThanEqual(motionTileCoord, outImageRes))) + return; + + float velocityMax = 0; + vec2 motionMax = vec2(0); + + float velocityMin = 100000; + vec2 motionMin = vec2(0); + + ivec2 motionBufferBaseCoord = motionTileCoord * motionTileSize; + + for(int x = 0; x < motionTileSize; x++){ + for(int y = 0; y < motionTileSize; y++){ + ivec2 sampleCoord = motionBufferBaseCoord + ivec2(x, y); + vec2 motionSample = texelFetch(sampler2D(inMotion, textureSampler), sampleCoord, 0).rg; + float velocitySample = length(motionSample); + + if(velocitySample > velocityMax){ + velocityMax = velocitySample; + motionMax = motionSample; + } + + if(velocitySample < velocityMin){ + velocityMin = velocitySample; + motionMin = motionSample; + } + } + } + + imageStore(outMotionMax, motionTileCoord, vec4(motionMax, 0, 0)); + imageStore(outMotionMin, motionTileCoord, vec4(motionMin, 0, 0)); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionVectorMinMaxNeighbourhood.comp b/projects/indirect_dispatch/resources/shaders/motionVectorMinMaxNeighbourhood.comp new file mode 100644 index 0000000000000000000000000000000000000000..4d6e7c0af6115e816ba087570e5585ffde23b1e6 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionVectorMinMaxNeighbourhood.comp @@ -0,0 +1,51 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +layout(set=0, binding=0) uniform texture2D inMotionMax; +layout(set=0, binding=1) uniform texture2D inMotionMin; +layout(set=0, binding=2) uniform sampler textureSampler; +layout(set=0, binding=3, rg16) uniform image2D outMotionMaxNeighbourhood; +layout(set=0, binding=4, rg16) uniform image2D outMotionMinNeighbourhood; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +void main(){ + + ivec2 outImageRes = imageSize(outMotionMaxNeighbourhood); + ivec2 motionTileCoord = ivec2(gl_GlobalInvocationID.xy); + + if(any(greaterThanEqual(motionTileCoord, outImageRes))) + return; + + float velocityMax = 0; + vec2 motionMax = vec2(0); + + float velocityMin = 10000; + vec2 motionMin = vec2(0); + + for(int x = -1; x <= 1; x++){ + for(int y = -1; y <= 1; y++){ + ivec2 sampleCoord = motionTileCoord + ivec2(x, y); + + vec2 motionSampleMax = texelFetch(sampler2D(inMotionMax, textureSampler), sampleCoord, 0).rg; + float velocitySampleMax = length(motionSampleMax); + + if(velocitySampleMax > velocityMax){ + velocityMax = velocitySampleMax; + motionMax = motionSampleMax; + } + + + vec2 motionSampleMin = texelFetch(sampler2D(inMotionMin, textureSampler), sampleCoord, 0).rg; + float velocitySampleMin = length(motionSampleMin); + + if(velocitySampleMin < velocityMin){ + velocityMin = velocitySampleMin; + motionMin = motionSampleMin; + } + } + } + + imageStore(outMotionMaxNeighbourhood, motionTileCoord, vec4(motionMax, 0, 0)); + imageStore(outMotionMinNeighbourhood, motionTileCoord, vec4(motionMin, 0, 0)); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/motionVectorVisualisation.comp b/projects/indirect_dispatch/resources/shaders/motionVectorVisualisation.comp new file mode 100644 index 0000000000000000000000000000000000000000..1cfb09c87e8288b8ea80c6ddfbe5f0d4918b7f2e --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/motionVectorVisualisation.comp @@ -0,0 +1,30 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +#include "motionBlurConfig.inc" + +layout(set=0, binding=0) uniform texture2D inMotion; +layout(set=0, binding=1) uniform sampler textureSampler; +layout(set=0, binding=2, r11f_g11f_b10f) uniform image2D outImage; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout( push_constant ) uniform constants{ + float range; +}; + +void main(){ + + ivec2 outImageRes = imageSize(outImage); + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); + + if(any(greaterThanEqual(coord, outImageRes))) + return; + + vec2 motionVector = texelFetch(sampler2D(inMotion, textureSampler), coord / motionTileSize, 0).rg; + vec2 motionVectorNormalized = clamp(motionVector / range, -1, 1); + + vec2 color = motionVectorNormalized * 0.5 + 0.5; + + imageStore(outImage, coord, vec4(color, 0.5, 0)); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/prepass.frag b/projects/indirect_dispatch/resources/shaders/prepass.frag new file mode 100644 index 0000000000000000000000000000000000000000..ccfc84d982253f7b89551c099a92b5686a811163 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/prepass.frag @@ -0,0 +1,14 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_GOOGLE_include_directive : enable + +#include "motionVector.inc" + +layout(location = 0) in vec4 passNDC; +layout(location = 1) in vec4 passNDCPrevious; + +layout(location = 0) out vec2 outMotion; + +void main() { + outMotion = computeMotionVector(passNDC, passNDCPrevious); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/prepass.vert b/projects/indirect_dispatch/resources/shaders/prepass.vert new file mode 100644 index 0000000000000000000000000000000000000000..230346208007fae0bb7724b5b6d05f62726c4ded --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/prepass.vert @@ -0,0 +1,18 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 inPosition; + +layout(location = 0) out vec4 passNDC; +layout(location = 1) out vec4 passNDCPrevious; + +layout( push_constant ) uniform constants{ + mat4 mvp; + mat4 mvpPrevious; +}; + +void main() { + gl_Position = mvp * vec4(inPosition, 1.0); + passNDC = gl_Position; + passNDCPrevious = mvpPrevious * vec4(inPosition, 1.0); +} \ No newline at end of file diff --git a/projects/bloom/resources/shaders/shadow.frag b/projects/indirect_dispatch/resources/shaders/sky.frag similarity index 53% rename from projects/bloom/resources/shaders/shadow.frag rename to projects/indirect_dispatch/resources/shaders/sky.frag index 848f853f556660b4900b5db7fb6fc98d57c1cd5b..efc0e03b2d6ee1c71930c866293da66857bd56c7 100644 --- a/projects/bloom/resources/shaders/shadow.frag +++ b/projects/indirect_dispatch/resources/shaders/sky.frag @@ -1,6 +1,8 @@ #version 450 #extension GL_ARB_separate_shader_objects : enable -void main() { +layout(location = 0) out vec3 outColor; +void main() { + outColor = vec3(0, 0.2, 0.9); } \ No newline at end of file diff --git a/projects/bloom/resources/shaders/shadow.vert b/projects/indirect_dispatch/resources/shaders/sky.vert similarity index 58% rename from projects/bloom/resources/shaders/shadow.vert rename to projects/indirect_dispatch/resources/shaders/sky.vert index e0f41d42d575fa64fedbfa04adf89ac0f4aeebe8..44b48cd7f3bfc44e2e43edef0d474581d50608de 100644 --- a/projects/bloom/resources/shaders/shadow.vert +++ b/projects/indirect_dispatch/resources/shaders/sky.vert @@ -4,9 +4,10 @@ layout(location = 0) in vec3 inPosition; layout( push_constant ) uniform constants{ - mat4 mvp; + mat4 viewProjection; }; void main() { - gl_Position = mvp * vec4(inPosition, 1.0); + gl_Position = viewProjection * vec4(inPosition, 0.0); + gl_Position.w = gl_Position.z; } \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/skyPrepass.frag b/projects/indirect_dispatch/resources/shaders/skyPrepass.frag new file mode 100644 index 0000000000000000000000000000000000000000..64ec4f18bbcf89153d70019ace570da53d44a505 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/skyPrepass.frag @@ -0,0 +1,14 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_GOOGLE_include_directive : enable + +#include "motionVector.inc" + +layout(location = 0) out vec2 outMotion; + +layout(location = 0) in vec4 passNDC; +layout(location = 1) in vec4 passNDCPrevious; + +void main() { + outMotion = computeMotionVector(passNDC, passNDCPrevious); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/resources/shaders/skyPrepass.vert b/projects/indirect_dispatch/resources/shaders/skyPrepass.vert new file mode 100644 index 0000000000000000000000000000000000000000..31b9016a592d097825a09e1daa888cb7b72b2cbc --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/skyPrepass.vert @@ -0,0 +1,22 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 inPosition; + +layout( push_constant ) uniform constants{ + mat4 viewProjection; + mat4 viewProjectionPrevious; +}; + +layout(location = 0) out vec4 passNDC; +layout(location = 1) out vec4 passNDCPrevious; + +void main() { + gl_Position = viewProjection * vec4(inPosition, 0.0); + gl_Position.w = gl_Position.z; + + passNDC = gl_Position; + + passNDCPrevious = viewProjectionPrevious * vec4(inPosition, 0.0); + passNDCPrevious.w = passNDCPrevious.z; +} \ No newline at end of file diff --git a/projects/indirect_dispatch/src/App.cpp b/projects/indirect_dispatch/src/App.cpp new file mode 100644 index 0000000000000000000000000000000000000000..92d548acde9c5a27e69c6daf4d92ca1da9d50a2c --- /dev/null +++ b/projects/indirect_dispatch/src/App.cpp @@ -0,0 +1,369 @@ +#include "App.hpp" +#include "AppConfig.hpp" +#include <chrono> +#include <vkcv/gui/GUI.hpp> +#include <functional> + +App::App() : + m_applicationName("Indirect Dispatch"), + m_windowWidth(AppConfig::defaultWindowWidth), + m_windowHeight(AppConfig::defaultWindowHeight), + m_window(vkcv::Window::create( + m_applicationName, + m_windowWidth, + m_windowHeight, + true)), + m_core(vkcv::Core::create( + m_window, + m_applicationName, + VK_MAKE_VERSION(0, 0, 1), + { vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer }, + {}, + { "VK_KHR_swapchain" })), + m_cameraManager(m_window){} + +bool App::initialize() { + + if (!loadMeshPass(m_core, &m_meshPass)) + return false; + + if (!loadSkyPass(m_core, &m_skyPass)) + return false; + + if (!loadPrePass(m_core, &m_prePass)) + return false; + + if (!loadSkyPrePass(m_core, &m_skyPrePass)) + return false; + + if (!loadComputePass(m_core, "resources/shaders/gammaCorrection.comp", &m_gammaCorrectionPass)) + return false; + + if (!loadMesh(m_core, "resources/models/cube.gltf", &m_cubeMesh)) + return false; + + if (!loadMesh(m_core, "resources/models/ground.gltf", &m_groundMesh)) + return false; + + if(!loadImage(m_core, "resources/models/grid.png", &m_gridTexture)) + return false; + + if (!m_motionBlur.initialize(&m_core, m_windowWidth, m_windowHeight)) + return false; + + m_linearSampler = m_core.createSampler( + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerMipmapMode::LINEAR, + vkcv::SamplerAddressMode::CLAMP_TO_EDGE); + + m_renderTargets = createRenderTargets(m_core, m_windowWidth, m_windowHeight); + + const int cameraIndex = m_cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); + m_cameraManager.getCamera(cameraIndex).setPosition(glm::vec3(0, 1, -3)); + m_cameraManager.getCamera(cameraIndex).setNearFar(0.1f, 30.f); + + vkcv::DescriptorWrites meshPassDescriptorWrites; + meshPassDescriptorWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(0, m_gridTexture) }; + meshPassDescriptorWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(1, m_linearSampler) }; + m_core.writeDescriptorSet(m_meshPass.descriptorSet, meshPassDescriptorWrites); + + return true; +} + +void App::run() { + + auto frameStartTime = std::chrono::system_clock::now(); + const auto appStartTime = std::chrono::system_clock::now(); + const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle(); + const vkcv::DrawcallInfo skyDrawcall(m_cubeMesh.mesh, {}, 1); + + vkcv::gui::GUI gui(m_core, m_window); + + eMotionVectorVisualisationMode motionVectorVisualisationMode = eMotionVectorVisualisationMode::None; + eMotionBlurMode motionBlurMode = eMotionBlurMode::Default; + + bool freezeFrame = false; + float motionBlurTileOffsetLength = 3; + float objectVerticalSpeed = 5; + float objectAmplitude = 0; + float objectMeanHeight = 1; + float objectRotationSpeedX = 5; + float objectRotationSpeedY = 5; + int cameraShutterSpeedInverse = 24; + float motionVectorVisualisationRange = 0.008; + float motionBlurFastPathThreshold = 1; + + glm::mat4 viewProjection = m_cameraManager.getActiveCamera().getMVP(); + glm::mat4 viewProjectionPrevious = m_cameraManager.getActiveCamera().getMVP(); + + struct Object { + MeshResources meshResources; + glm::mat4 modelMatrix = glm::mat4(1.f); + glm::mat4 mvp = glm::mat4(1.f); + glm::mat4 mvpPrevious = glm::mat4(1.f); + std::function<void(float, Object&)> modelMatrixUpdate; + }; + std::vector<Object> sceneObjects; + + Object ground; + ground.meshResources = m_groundMesh; + sceneObjects.push_back(ground); + + Object sphere; + sphere.meshResources = m_cubeMesh; + sphere.modelMatrixUpdate = [&](float time, Object& obj) { + const float currentHeight = objectMeanHeight + objectAmplitude * glm::sin(time * objectVerticalSpeed); + const glm::mat4 translation = glm::translate(glm::mat4(1), glm::vec3(0, currentHeight, 0)); + const glm::mat4 rotationX = glm::rotate(glm::mat4(1), objectRotationSpeedX * time, glm::vec3(1, 0, 0)); + const glm::mat4 rotationY = glm::rotate(glm::mat4(1), objectRotationSpeedY * time, glm::vec3(0, 1, 0)); + obj.modelMatrix = translation * rotationX * rotationY; + }; + sceneObjects.push_back(sphere); + + bool spaceWasPressed = false; + + m_window.e_key.add([&](int key, int scancode, int action, int mods) { + if (key == GLFW_KEY_SPACE) { + if (action == GLFW_PRESS) { + if (!spaceWasPressed) { + freezeFrame = !freezeFrame; + } + spaceWasPressed = true; + } + else if (action == GLFW_RELEASE) { + spaceWasPressed = false; + } + } + }); + + auto frameEndTime = std::chrono::system_clock::now(); + + while (m_window.isWindowOpen()) { + + vkcv::Window::pollEvents(); + + if (!freezeFrame) { + + frameStartTime = frameEndTime; + viewProjectionPrevious = viewProjection; + + for (Object& obj : sceneObjects) { + obj.mvpPrevious = obj.mvp; + } + } + + if (m_window.getHeight() == 0 || m_window.getWidth() == 0) + continue; + + uint32_t swapchainWidth, swapchainHeight; + if (!m_core.beginFrame(swapchainWidth, swapchainHeight)) + continue; + + const bool hasResolutionChanged = (swapchainWidth != m_windowWidth) || (swapchainHeight != m_windowHeight); + if (hasResolutionChanged) { + m_windowWidth = swapchainWidth; + m_windowHeight = swapchainHeight; + + m_renderTargets = createRenderTargets(m_core, m_windowWidth, m_windowHeight); + m_motionBlur.setResolution(m_windowWidth, m_windowHeight); + } + + if(!freezeFrame) + frameEndTime = std::chrono::system_clock::now(); + + const float microsecondToSecond = 0.000001; + const float fDeltaTimeSeconds = microsecondToSecond * std::chrono::duration_cast<std::chrono::microseconds>(frameEndTime - frameStartTime).count(); + + m_cameraManager.update(fDeltaTimeSeconds); + + const auto time = frameEndTime - appStartTime; + const float fCurrentTime = std::chrono::duration_cast<std::chrono::milliseconds>(time).count() * 0.001f; + + // update matrices + if (!freezeFrame) { + + viewProjection = m_cameraManager.getActiveCamera().getMVP(); + + for (Object& obj : sceneObjects) { + if (obj.modelMatrixUpdate) { + obj.modelMatrixUpdate(fCurrentTime, obj); + } + obj.mvp = viewProjection * obj.modelMatrix; + } + } + + const vkcv::CommandStreamHandle cmdStream = m_core.createCommandStream(vkcv::QueueType::Graphics); + + // prepass + vkcv::PushConstants prepassPushConstants(sizeof(glm::mat4) * 2); + + for (const Object& obj : sceneObjects) { + glm::mat4 prepassMatrices[2] = { obj.mvp, obj.mvpPrevious }; + prepassPushConstants.appendDrawcall(prepassMatrices); + } + + const std::vector<vkcv::ImageHandle> prepassRenderTargets = { + m_renderTargets.motionBuffer, + m_renderTargets.depthBuffer }; + + std::vector<vkcv::DrawcallInfo> prepassSceneDrawcalls; + for (const Object& obj : sceneObjects) { + prepassSceneDrawcalls.push_back(vkcv::DrawcallInfo(obj.meshResources.mesh, {})); + } + + m_core.recordDrawcallsToCmdStream( + cmdStream, + m_prePass.renderPass, + m_prePass.pipeline, + prepassPushConstants, + prepassSceneDrawcalls, + prepassRenderTargets); + + // sky prepass + glm::mat4 skyPrepassMatrices[2] = { + viewProjection, + viewProjectionPrevious }; + vkcv::PushConstants skyPrepassPushConstants(sizeof(glm::mat4) * 2); + skyPrepassPushConstants.appendDrawcall(skyPrepassMatrices); + + m_core.recordDrawcallsToCmdStream( + cmdStream, + m_skyPrePass.renderPass, + m_skyPrePass.pipeline, + skyPrepassPushConstants, + { skyDrawcall }, + prepassRenderTargets); + + // main pass + const std::vector<vkcv::ImageHandle> renderTargets = { + m_renderTargets.colorBuffer, + m_renderTargets.depthBuffer }; + + vkcv::PushConstants meshPushConstants(2 * sizeof(glm::mat4)); + for (const Object& obj : sceneObjects) { + glm::mat4 matrices[2] = { obj.mvp, obj.modelMatrix }; + meshPushConstants.appendDrawcall(matrices); + } + + std::vector<vkcv::DrawcallInfo> forwardSceneDrawcalls; + for (const Object& obj : sceneObjects) { + forwardSceneDrawcalls.push_back(vkcv::DrawcallInfo( + obj.meshResources.mesh, + { vkcv::DescriptorSetUsage(0, m_core.getDescriptorSet(m_meshPass.descriptorSet).vulkanHandle) })); + } + + m_core.recordDrawcallsToCmdStream( + cmdStream, + m_meshPass.renderPass, + m_meshPass.pipeline, + meshPushConstants, + forwardSceneDrawcalls, + renderTargets); + + // sky + vkcv::PushConstants skyPushConstants(sizeof(glm::mat4)); + skyPushConstants.appendDrawcall(viewProjection); + + m_core.recordDrawcallsToCmdStream( + cmdStream, + m_skyPass.renderPass, + m_skyPass.pipeline, + skyPushConstants, + { skyDrawcall }, + renderTargets); + + // motion blur + vkcv::ImageHandle motionBlurOutput; + + if (motionVectorVisualisationMode == eMotionVectorVisualisationMode::None) { + float cameraNear; + float cameraFar; + m_cameraManager.getActiveCamera().getNearFar(cameraNear, cameraFar); + + motionBlurOutput = m_motionBlur.render( + cmdStream, + m_renderTargets.motionBuffer, + m_renderTargets.colorBuffer, + m_renderTargets.depthBuffer, + motionBlurMode, + cameraNear, + cameraFar, + fDeltaTimeSeconds, + cameraShutterSpeedInverse, + motionBlurTileOffsetLength, + motionBlurFastPathThreshold); + } + else { + motionBlurOutput = m_motionBlur.renderMotionVectorVisualisation( + cmdStream, + m_renderTargets.motionBuffer, + motionVectorVisualisationMode, + motionVectorVisualisationRange); + } + + // gamma correction + vkcv::DescriptorWrites gammaCorrectionDescriptorWrites; + gammaCorrectionDescriptorWrites.sampledImageWrites = { + vkcv::SampledImageDescriptorWrite(0, motionBlurOutput) }; + gammaCorrectionDescriptorWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(1, m_linearSampler) }; + gammaCorrectionDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(2, swapchainInput) }; + + m_core.writeDescriptorSet(m_gammaCorrectionPass.descriptorSet, gammaCorrectionDescriptorWrites); + + m_core.prepareImageForSampling(cmdStream, motionBlurOutput); + m_core.prepareImageForStorage (cmdStream, swapchainInput); + + const uint32_t fullScreenImageDispatch[3] = { + static_cast<uint32_t>((m_windowWidth + 7) / 8), + static_cast<uint32_t>((m_windowHeight + 7) / 8), + static_cast<uint32_t>(1) }; + + m_core.recordComputeDispatchToCmdStream( + cmdStream, + m_gammaCorrectionPass.pipeline, + fullScreenImageDispatch, + { vkcv::DescriptorSetUsage(0, m_core.getDescriptorSet(m_gammaCorrectionPass.descriptorSet).vulkanHandle) }, + vkcv::PushConstants(0)); + + m_core.prepareSwapchainImageForPresent(cmdStream); + m_core.submitCommandStream(cmdStream); + + gui.beginGUI(); + ImGui::Begin("Settings"); + + ImGui::Checkbox("Freeze frame", &freezeFrame); + ImGui::InputFloat("Motion tile offset length", &motionBlurTileOffsetLength); + ImGui::InputFloat("Motion blur fast path threshold", &motionBlurFastPathThreshold); + + ImGui::Combo( + "Motion blur mode", + reinterpret_cast<int*>(&motionBlurMode), + MotionBlurModeLabels, + static_cast<int>(eMotionBlurMode::OptionCount)); + + ImGui::Combo( + "Debug view", + reinterpret_cast<int*>(&motionVectorVisualisationMode), + MotionVectorVisualisationModeLabels, + static_cast<int>(eMotionVectorVisualisationMode::OptionCount)); + + if (motionVectorVisualisationMode != eMotionVectorVisualisationMode::None) + ImGui::InputFloat("Motion vector visualisation range", &motionVectorVisualisationRange); + + ImGui::InputInt("Camera shutter speed inverse", &cameraShutterSpeedInverse); + + ImGui::InputFloat("Object movement speed", &objectVerticalSpeed); + ImGui::InputFloat("Object movement amplitude", &objectAmplitude); + ImGui::InputFloat("Object mean height", &objectMeanHeight); + ImGui::InputFloat("Object rotation speed X", &objectRotationSpeedX); + ImGui::InputFloat("Object rotation speed Y", &objectRotationSpeedY); + + ImGui::End(); + gui.endGUI(); + + m_core.endFrame(); + } +} \ No newline at end of file diff --git a/projects/indirect_dispatch/src/App.hpp b/projects/indirect_dispatch/src/App.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d580793b0fdc4e7dc8c8654d29a75f04e14ea422 --- /dev/null +++ b/projects/indirect_dispatch/src/App.hpp @@ -0,0 +1,38 @@ +#pragma once +#include <vkcv/Core.hpp> +#include <vkcv/camera/CameraManager.hpp> +#include "AppSetup.hpp" +#include "MotionBlur.hpp" + +class App { +public: + App(); + bool initialize(); + void run(); +private: + const char* m_applicationName; + + int m_windowWidth; + int m_windowHeight; + + vkcv::Window m_window; + vkcv::Core m_core; + vkcv::camera::CameraManager m_cameraManager; + + MotionBlur m_motionBlur; + + vkcv::ImageHandle m_gridTexture; + + MeshResources m_cubeMesh; + MeshResources m_groundMesh; + + GraphicPassHandles m_meshPass; + GraphicPassHandles m_skyPass; + GraphicPassHandles m_prePass; + GraphicPassHandles m_skyPrePass; + + ComputePassHandles m_gammaCorrectionPass; + + AppRenderTargets m_renderTargets; + vkcv::SamplerHandle m_linearSampler; +}; \ No newline at end of file diff --git a/projects/indirect_dispatch/src/AppConfig.hpp b/projects/indirect_dispatch/src/AppConfig.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c89c34ea8e3c0c45708ca998a642faffb31403d3 --- /dev/null +++ b/projects/indirect_dispatch/src/AppConfig.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "vulkan/vulkan.hpp" + +namespace AppConfig{ + const int defaultWindowWidth = 1280; + const int defaultWindowHeight = 720; + const vk::Format depthBufferFormat = vk::Format::eD32Sfloat; + const vk::Format colorBufferFormat = vk::Format::eB10G11R11UfloatPack32; + const vk::Format motionBufferFormat = vk::Format::eR16G16Sfloat; +} \ No newline at end of file diff --git a/projects/indirect_dispatch/src/AppSetup.cpp b/projects/indirect_dispatch/src/AppSetup.cpp new file mode 100644 index 0000000000000000000000000000000000000000..267ac6bd8ef44dcee9b3a05d7204e8d33fbe86a7 --- /dev/null +++ b/projects/indirect_dispatch/src/AppSetup.cpp @@ -0,0 +1,301 @@ +#include "AppSetup.hpp" +#include "AppConfig.hpp" +#include <vkcv/asset/asset_loader.hpp> +#include <vkcv/shader/GLSLCompiler.hpp> + +bool loadMesh(vkcv::Core& core, const std::filesystem::path& path, MeshResources* outMesh) { + assert(outMesh); + + vkcv::asset::Scene scene; + const int meshLoadResult = vkcv::asset::loadScene(path.string(), scene); + + if (meshLoadResult != 1) { + vkcv_log(vkcv::LogLevel::ERROR, "Mesh loading failed"); + return false; + } + + if (scene.meshes.size() < 1) { + vkcv_log(vkcv::LogLevel::ERROR, "Cube mesh scene does not contain any vertex groups"); + return false; + } + assert(!scene.vertexGroups.empty()); + + auto& vertexData = scene.vertexGroups[0].vertexBuffer; + auto& indexData = scene.vertexGroups[0].indexBuffer; + + vkcv::Buffer vertexBuffer = core.createBuffer<uint8_t>( + vkcv::BufferType::VERTEX, + vertexData.data.size(), + vkcv::BufferMemoryType::DEVICE_LOCAL); + + vkcv::Buffer indexBuffer = core.createBuffer<uint8_t>( + vkcv::BufferType::INDEX, + indexData.data.size(), + vkcv::BufferMemoryType::DEVICE_LOCAL); + + vertexBuffer.fill(vertexData.data); + indexBuffer.fill(indexData.data); + + outMesh->vertexBuffer = vertexBuffer.getHandle(); + outMesh->indexBuffer = indexBuffer.getHandle(); + + auto& attributes = vertexData.attributes; + + std::sort(attributes.begin(), attributes.end(), + [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) { + return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type); + }); + + const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = { + vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[0].offset), vertexBuffer.getVulkanHandle()), + vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[1].offset), vertexBuffer.getVulkanHandle()), + vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[2].offset), vertexBuffer.getVulkanHandle()) }; + + outMesh->mesh = vkcv::Mesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), scene.vertexGroups[0].numIndices); + + return true; +} + +bool loadImage(vkcv::Core& core, const std::filesystem::path& path, vkcv::ImageHandle* outImage) { + + assert(outImage); + + const vkcv::asset::Texture textureData = vkcv::asset::loadTexture(path); + + if (textureData.channels != 4) { + vkcv_log(vkcv::LogLevel::ERROR, "Expecting image with four components"); + return false; + } + + vkcv::Image image = core.createImage( + vk::Format::eR8G8B8A8Srgb, + textureData.width, + textureData.height, + 1, + true); + + image.fill(textureData.data.data(), textureData.data.size()); + image.generateMipChainImmediate(); + image.switchLayout(vk::ImageLayout::eReadOnlyOptimalKHR); + + *outImage = image.getHandle(); + return true; +} + +bool loadGraphicPass( + vkcv::Core& core, + const std::filesystem::path vertexPath, + const std::filesystem::path fragmentPath, + const vkcv::PassConfig& passConfig, + const vkcv::DepthTest depthTest, + GraphicPassHandles* outPassHandles) { + + assert(outPassHandles); + + outPassHandles->renderPass = core.createPass(passConfig); + + if (!outPassHandles->renderPass) { + vkcv_log(vkcv::LogLevel::ERROR, "Error: Could not create renderpass"); + return false; + } + + vkcv::ShaderProgram shaderProgram; + vkcv::shader::GLSLCompiler compiler; + + compiler.compile(vkcv::ShaderStage::VERTEX, vertexPath, + [&shaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + shaderProgram.addShader(shaderStage, path); + }); + + compiler.compile(vkcv::ShaderStage::FRAGMENT, fragmentPath, + [&shaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + shaderProgram.addShader(shaderStage, path); + }); + + const std::vector<vkcv::VertexAttachment> vertexAttachments = shaderProgram.getVertexAttachments(); + std::vector<vkcv::VertexBinding> bindings; + for (size_t i = 0; i < vertexAttachments.size(); i++) { + bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] })); + } + + const vkcv::VertexLayout vertexLayout(bindings); + + const auto descriptorBindings = shaderProgram.getReflectedDescriptors(); + const bool hasDescriptor = descriptorBindings.size() > 0; + if (hasDescriptor) + outPassHandles->descriptorSet = core.createDescriptorSet(descriptorBindings[0]); + + std::vector<vk::DescriptorSetLayout> descriptorLayouts; + if (hasDescriptor) + descriptorLayouts.push_back(core.getDescriptorSet(outPassHandles->descriptorSet).layout); + + vkcv::PipelineConfig pipelineConfig{ + shaderProgram, + UINT32_MAX, + UINT32_MAX, + outPassHandles->renderPass, + { vertexLayout }, + descriptorLayouts, + true }; + pipelineConfig.m_depthTest = depthTest; + outPassHandles->pipeline = core.createGraphicsPipeline(pipelineConfig); + + if (!outPassHandles->pipeline) { + vkcv_log(vkcv::LogLevel::ERROR, "Error: Could not create graphics pipeline"); + return false; + } + + return true; +} + +bool loadMeshPass(vkcv::Core& core, GraphicPassHandles* outHandles) { + + assert(outHandles); + + vkcv::AttachmentDescription colorAttachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::DONT_CARE, + AppConfig::colorBufferFormat); + + vkcv::AttachmentDescription depthAttachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::LOAD, + AppConfig::depthBufferFormat); + + return loadGraphicPass( + core, + "resources/shaders/mesh.vert", + "resources/shaders/mesh.frag", + vkcv::PassConfig({ colorAttachment, depthAttachment }), + vkcv::DepthTest::Equal, + outHandles); +} + +bool loadSkyPass(vkcv::Core& core, GraphicPassHandles* outHandles) { + + assert(outHandles); + + vkcv::AttachmentDescription colorAttachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::LOAD, + AppConfig::colorBufferFormat); + + vkcv::AttachmentDescription depthAttachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::LOAD, + AppConfig::depthBufferFormat); + + return loadGraphicPass( + core, + "resources/shaders/sky.vert", + "resources/shaders/sky.frag", + vkcv::PassConfig({ colorAttachment, depthAttachment }), + vkcv::DepthTest::Equal, + outHandles); +} + +bool loadPrePass(vkcv::Core& core, GraphicPassHandles* outHandles) { + assert(outHandles); + + vkcv::AttachmentDescription motionAttachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::CLEAR, + AppConfig::motionBufferFormat); + + vkcv::AttachmentDescription depthAttachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::CLEAR, + AppConfig::depthBufferFormat); + + return loadGraphicPass( + core, + "resources/shaders/prepass.vert", + "resources/shaders/prepass.frag", + vkcv::PassConfig({ motionAttachment, depthAttachment }), + vkcv::DepthTest::LessEqual, + outHandles); +} + +bool loadSkyPrePass(vkcv::Core& core, GraphicPassHandles* outHandles) { + assert(outHandles); + + vkcv::AttachmentDescription motionAttachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::LOAD, + AppConfig::motionBufferFormat); + + vkcv::AttachmentDescription depthAttachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::LOAD, + AppConfig::depthBufferFormat); + + return loadGraphicPass( + core, + "resources/shaders/skyPrepass.vert", + "resources/shaders/skyPrepass.frag", + vkcv::PassConfig({ motionAttachment, depthAttachment }), + vkcv::DepthTest::LessEqual, + outHandles); +} + +bool loadComputePass(vkcv::Core& core, const std::filesystem::path& path, ComputePassHandles* outComputePass) { + + assert(outComputePass); + vkcv::ShaderProgram shaderProgram; + vkcv::shader::GLSLCompiler compiler; + + compiler.compile(vkcv::ShaderStage::COMPUTE, path, + [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + shaderProgram.addShader(shaderStage, path); + }); + + if (shaderProgram.getReflectedDescriptors().size() < 1) { + vkcv_log(vkcv::LogLevel::ERROR, "Compute shader has no descriptor set"); + return false; + } + + outComputePass->descriptorSet = core.createDescriptorSet(shaderProgram.getReflectedDescriptors()[0]); + + outComputePass->pipeline = core.createComputePipeline( + shaderProgram, + { core.getDescriptorSet(outComputePass->descriptorSet).layout }); + + if (!outComputePass->pipeline) { + vkcv_log(vkcv::LogLevel::ERROR, "Compute shader pipeline creation failed"); + return false; + } + + return true; +} + +AppRenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const uint32_t height) { + + AppRenderTargets targets; + + targets.depthBuffer = core.createImage( + AppConfig::depthBufferFormat, + width, + height, + 1, + false).getHandle(); + + targets.colorBuffer = core.createImage( + AppConfig::colorBufferFormat, + width, + height, + 1, + false, + false, + true).getHandle(); + + targets.motionBuffer = core.createImage( + AppConfig::motionBufferFormat, + width, + height, + 1, + false, + false, + true).getHandle(); + + return targets; +} \ No newline at end of file diff --git a/projects/indirect_dispatch/src/AppSetup.hpp b/projects/indirect_dispatch/src/AppSetup.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3125bc516b553de715d6e51bbda259e3e16f758f --- /dev/null +++ b/projects/indirect_dispatch/src/AppSetup.hpp @@ -0,0 +1,47 @@ +#pragma once +#include <vkcv/Core.hpp> + +struct AppRenderTargets { + vkcv::ImageHandle depthBuffer; + vkcv::ImageHandle colorBuffer; + vkcv::ImageHandle motionBuffer; +}; + +struct GraphicPassHandles { + vkcv::PipelineHandle pipeline; + vkcv::PassHandle renderPass; + vkcv::DescriptorSetHandle descriptorSet; +}; + +struct ComputePassHandles { + vkcv::PipelineHandle pipeline; + vkcv::DescriptorSetHandle descriptorSet; +}; + +struct MeshResources { + vkcv::Mesh mesh; + vkcv::BufferHandle vertexBuffer; + vkcv::BufferHandle indexBuffer; +}; + +// loads position, uv and normal of the first mesh in a scene +bool loadMesh(vkcv::Core& core, const std::filesystem::path& path, MeshResources* outMesh); + +bool loadImage(vkcv::Core& core, const std::filesystem::path& path, vkcv::ImageHandle* outImage); + +bool loadGraphicPass( + vkcv::Core& core, + const std::filesystem::path vertexPath, + const std::filesystem::path fragmentPath, + const vkcv::PassConfig& passConfig, + const vkcv::DepthTest depthTest, + GraphicPassHandles* outPassHandles); + +bool loadMeshPass (vkcv::Core& core, GraphicPassHandles* outHandles); +bool loadSkyPass (vkcv::Core& core, GraphicPassHandles* outHandles); +bool loadPrePass (vkcv::Core& core, GraphicPassHandles* outHandles); +bool loadSkyPrePass(vkcv::Core& core, GraphicPassHandles* outHandles); + +bool loadComputePass(vkcv::Core& core, const std::filesystem::path& path, ComputePassHandles* outComputePass); + +AppRenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const uint32_t height); \ No newline at end of file diff --git a/projects/indirect_dispatch/src/MotionBlur.cpp b/projects/indirect_dispatch/src/MotionBlur.cpp new file mode 100644 index 0000000000000000000000000000000000000000..49f650a97e2fea5821959ae53f468e6fe7de6ffe --- /dev/null +++ b/projects/indirect_dispatch/src/MotionBlur.cpp @@ -0,0 +1,437 @@ +#include "MotionBlur.hpp" +#include "MotionBlurConfig.hpp" +#include "MotionBlurSetup.hpp" +#include <array> + +std::array<uint32_t, 3> computeFullscreenDispatchSize( + const uint32_t imageWidth, + const uint32_t imageHeight, + const uint32_t localGroupSize) { + + // optimized divide and ceil + return std::array<uint32_t, 3>{ + static_cast<uint32_t>(imageWidth + (localGroupSize - 1)) / localGroupSize, + static_cast<uint32_t>(imageHeight + (localGroupSize - 1)) / localGroupSize, + static_cast<uint32_t>(1) }; +} + +bool MotionBlur::initialize(vkcv::Core* corePtr, const uint32_t targetWidth, const uint32_t targetHeight) { + + if (!corePtr) { + vkcv_log(vkcv::LogLevel::ERROR, "MotionBlur got invalid corePtr") + return false; + } + + m_core = corePtr; + + if (!loadComputePass(*m_core, "resources/shaders/motionBlur.comp", &m_motionBlurPass)) + return false; + + if (!loadComputePass(*m_core, "resources/shaders/motionVectorMinMax.comp", &m_motionVectorMinMaxPass)) + return false; + + if (!loadComputePass(*m_core, "resources/shaders/motionVectorMinMaxNeighbourhood.comp", &m_motionVectorMinMaxNeighbourhoodPass)) + return false; + + if (!loadComputePass(*m_core, "resources/shaders/motionVectorVisualisation.comp", &m_motionVectorVisualisationPass)) + return false; + + if (!loadComputePass(*m_core, "resources/shaders/motionBlurColorCopy.comp", &m_colorCopyPass)) + return false; + + if (!loadComputePass(*m_core, "resources/shaders/motionBlurTileClassification.comp", &m_tileClassificationPass)) + return false; + + if (!loadComputePass(*m_core, "resources/shaders/motionBlurWorkTileReset.comp", &m_tileResetPass)) + return false; + + if (!loadComputePass(*m_core, "resources/shaders/motionBlurTileClassificationVis.comp", &m_tileVisualisationPass)) + return false; + + if (!loadComputePass(*m_core, "resources/shaders/motionBlurFastPath.comp", &m_motionBlurFastPathPass)) + return false; + + // work tile buffers and descriptors + const uint32_t workTileBufferSize = static_cast<uint32_t>(2 * sizeof(uint32_t)) * (3 + + ((MotionBlurConfig::maxWidth + MotionBlurConfig::maxMotionTileSize - 1) / MotionBlurConfig::maxMotionTileSize) * + ((MotionBlurConfig::maxHeight + MotionBlurConfig::maxMotionTileSize - 1) / MotionBlurConfig::maxMotionTileSize)); + + m_copyPathWorkTileBuffer = m_core->createBuffer<uint32_t>( + vkcv::BufferType::STORAGE, + workTileBufferSize, + vkcv::BufferMemoryType::DEVICE_LOCAL, + true).getHandle(); + + m_fullPathWorkTileBuffer = m_core->createBuffer<uint32_t>( + vkcv::BufferType::STORAGE, + workTileBufferSize, + vkcv::BufferMemoryType::DEVICE_LOCAL, + true).getHandle(); + + m_fastPathWorkTileBuffer = m_core->createBuffer<uint32_t>( + vkcv::BufferType::STORAGE, + workTileBufferSize, + vkcv::BufferMemoryType::DEVICE_LOCAL, + true).getHandle(); + + vkcv::DescriptorWrites tileResetDescriptorWrites; + tileResetDescriptorWrites.storageBufferWrites = { + vkcv::BufferDescriptorWrite(0, m_fullPathWorkTileBuffer), + vkcv::BufferDescriptorWrite(1, m_copyPathWorkTileBuffer), + vkcv::BufferDescriptorWrite(2, m_fastPathWorkTileBuffer) }; + + m_core->writeDescriptorSet(m_tileResetPass.descriptorSet, tileResetDescriptorWrites); + + + m_renderTargets = MotionBlurSetup::createRenderTargets(targetWidth, targetHeight, *m_core); + + m_nearestSampler = m_core->createSampler( + vkcv::SamplerFilterType::NEAREST, + vkcv::SamplerFilterType::NEAREST, + vkcv::SamplerMipmapMode::NEAREST, + vkcv::SamplerAddressMode::CLAMP_TO_EDGE); + + return true; +} + +void MotionBlur::setResolution(const uint32_t targetWidth, const uint32_t targetHeight) { + m_renderTargets = MotionBlurSetup::createRenderTargets(targetWidth, targetHeight, *m_core); +} + +vkcv::ImageHandle MotionBlur::render( + const vkcv::CommandStreamHandle cmdStream, + const vkcv::ImageHandle motionBufferFullRes, + const vkcv::ImageHandle colorBuffer, + const vkcv::ImageHandle depthBuffer, + const eMotionBlurMode mode, + const float cameraNear, + const float cameraFar, + const float deltaTimeSeconds, + const float cameraShutterSpeedInverse, + const float motionTileOffsetLength, + const float fastPathThreshold) { + + computeMotionTiles(cmdStream, motionBufferFullRes); + + // work tile reset + const uint32_t dispatchSizeOne[3] = { 1, 1, 1 }; + + m_core->recordComputeDispatchToCmdStream( + cmdStream, + m_tileResetPass.pipeline, + dispatchSizeOne, + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_tileResetPass.descriptorSet).vulkanHandle) }, + vkcv::PushConstants(0)); + + m_core->recordBufferMemoryBarrier(cmdStream, m_fullPathWorkTileBuffer); + m_core->recordBufferMemoryBarrier(cmdStream, m_copyPathWorkTileBuffer); + m_core->recordBufferMemoryBarrier(cmdStream, m_fastPathWorkTileBuffer); + + // work tile classification + vkcv::DescriptorWrites tileClassificationDescriptorWrites; + tileClassificationDescriptorWrites.sampledImageWrites = { + vkcv::SampledImageDescriptorWrite(0, m_renderTargets.motionMaxNeighbourhood), + vkcv::SampledImageDescriptorWrite(1, m_renderTargets.motionMinNeighbourhood) }; + tileClassificationDescriptorWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(2, m_nearestSampler) }; + tileClassificationDescriptorWrites.storageBufferWrites = { + vkcv::BufferDescriptorWrite(3, m_fullPathWorkTileBuffer), + vkcv::BufferDescriptorWrite(4, m_copyPathWorkTileBuffer), + vkcv::BufferDescriptorWrite(5, m_fastPathWorkTileBuffer) }; + + m_core->writeDescriptorSet(m_tileClassificationPass.descriptorSet, tileClassificationDescriptorWrites); + + const auto tileClassificationDispatch = computeFullscreenDispatchSize( + m_core->getImageWidth(m_renderTargets.motionMaxNeighbourhood), + m_core->getImageHeight(m_renderTargets.motionMaxNeighbourhood), + 8); + + struct ClassificationConstants { + uint32_t width; + uint32_t height; + float fastPathThreshold; + }; + ClassificationConstants classificationConstants; + classificationConstants.width = m_core->getImageWidth(m_renderTargets.outputColor); + classificationConstants.height = m_core->getImageHeight(m_renderTargets.outputColor); + classificationConstants.fastPathThreshold = fastPathThreshold; + + vkcv::PushConstants classificationPushConstants(sizeof(ClassificationConstants)); + classificationPushConstants.appendDrawcall(classificationConstants); + + m_core->prepareImageForSampling(cmdStream, m_renderTargets.motionMaxNeighbourhood); + m_core->prepareImageForSampling(cmdStream, m_renderTargets.motionMinNeighbourhood); + + m_core->recordComputeDispatchToCmdStream( + cmdStream, + m_tileClassificationPass.pipeline, + tileClassificationDispatch.data(), + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_tileClassificationPass.descriptorSet).vulkanHandle) }, + classificationPushConstants); + + m_core->recordBufferMemoryBarrier(cmdStream, m_fullPathWorkTileBuffer); + m_core->recordBufferMemoryBarrier(cmdStream, m_copyPathWorkTileBuffer); + m_core->recordBufferMemoryBarrier(cmdStream, m_fastPathWorkTileBuffer); + + vkcv::DescriptorWrites motionBlurDescriptorWrites; + motionBlurDescriptorWrites.sampledImageWrites = { + vkcv::SampledImageDescriptorWrite(0, colorBuffer), + vkcv::SampledImageDescriptorWrite(1, depthBuffer), + vkcv::SampledImageDescriptorWrite(2, motionBufferFullRes), + vkcv::SampledImageDescriptorWrite(3, m_renderTargets.motionMaxNeighbourhood) }; + motionBlurDescriptorWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(4, m_nearestSampler) }; + motionBlurDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(5, m_renderTargets.outputColor) }; + motionBlurDescriptorWrites.storageBufferWrites = { + vkcv::BufferDescriptorWrite(6, m_fullPathWorkTileBuffer)}; + + m_core->writeDescriptorSet(m_motionBlurPass.descriptorSet, motionBlurDescriptorWrites); + + + vkcv::DescriptorWrites colorCopyDescriptorWrites; + colorCopyDescriptorWrites.sampledImageWrites = { + vkcv::SampledImageDescriptorWrite(0, colorBuffer) }; + colorCopyDescriptorWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(1, m_nearestSampler) }; + colorCopyDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(2, m_renderTargets.outputColor) }; + colorCopyDescriptorWrites.storageBufferWrites = { + vkcv::BufferDescriptorWrite(3, m_copyPathWorkTileBuffer) }; + + m_core->writeDescriptorSet(m_colorCopyPass.descriptorSet, colorCopyDescriptorWrites); + + + vkcv::DescriptorWrites fastPathDescriptorWrites; + fastPathDescriptorWrites.sampledImageWrites = { + vkcv::SampledImageDescriptorWrite(0, colorBuffer), + vkcv::SampledImageDescriptorWrite(1, m_renderTargets.motionMaxNeighbourhood) }; + fastPathDescriptorWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(2, m_nearestSampler) }; + fastPathDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(3, m_renderTargets.outputColor) }; + fastPathDescriptorWrites.storageBufferWrites = { + vkcv::BufferDescriptorWrite(4, m_fastPathWorkTileBuffer) }; + + m_core->writeDescriptorSet(m_motionBlurFastPathPass.descriptorSet, fastPathDescriptorWrites); + + // must match layout in "motionBlur.comp" + struct MotionBlurConstantData { + float motionFactor; + float cameraNearPlane; + float cameraFarPlane; + float motionTileOffsetLength; + }; + MotionBlurConstantData motionBlurConstantData; + + const float deltaTimeMotionBlur = deltaTimeSeconds; + + motionBlurConstantData.motionFactor = 1 / (deltaTimeMotionBlur * cameraShutterSpeedInverse); + motionBlurConstantData.cameraNearPlane = cameraNear; + motionBlurConstantData.cameraFarPlane = cameraFar; + motionBlurConstantData.motionTileOffsetLength = motionTileOffsetLength; + + vkcv::PushConstants motionBlurPushConstants(sizeof(motionBlurConstantData)); + motionBlurPushConstants.appendDrawcall(motionBlurConstantData); + + struct FastPathConstants { + float motionFactor; + }; + FastPathConstants fastPathConstants; + fastPathConstants.motionFactor = motionBlurConstantData.motionFactor; + + vkcv::PushConstants fastPathPushConstants(sizeof(FastPathConstants)); + fastPathPushConstants.appendDrawcall(fastPathConstants); + + m_core->prepareImageForStorage(cmdStream, m_renderTargets.outputColor); + m_core->prepareImageForSampling(cmdStream, colorBuffer); + m_core->prepareImageForSampling(cmdStream, depthBuffer); + m_core->prepareImageForSampling(cmdStream, m_renderTargets.motionMaxNeighbourhood); + + if (mode == eMotionBlurMode::Default) { + m_core->recordComputeIndirectDispatchToCmdStream( + cmdStream, + m_motionBlurPass.pipeline, + m_fullPathWorkTileBuffer, + 0, + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_motionBlurPass.descriptorSet).vulkanHandle) }, + motionBlurPushConstants); + + m_core->recordComputeIndirectDispatchToCmdStream( + cmdStream, + m_colorCopyPass.pipeline, + m_copyPathWorkTileBuffer, + 0, + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_colorCopyPass.descriptorSet).vulkanHandle) }, + vkcv::PushConstants(0)); + + m_core->recordComputeIndirectDispatchToCmdStream( + cmdStream, + m_motionBlurFastPathPass.pipeline, + m_fastPathWorkTileBuffer, + 0, + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_motionBlurFastPathPass.descriptorSet).vulkanHandle) }, + fastPathPushConstants); + } + else if(mode == eMotionBlurMode::Disabled) { + return colorBuffer; + } + else if (mode == eMotionBlurMode::TileVisualisation) { + + vkcv::DescriptorWrites visualisationDescriptorWrites; + visualisationDescriptorWrites.sampledImageWrites = { + vkcv::SampledImageDescriptorWrite(0, colorBuffer) }; + visualisationDescriptorWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(1, m_nearestSampler) }; + visualisationDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(2, m_renderTargets.outputColor)}; + visualisationDescriptorWrites.storageBufferWrites = { + vkcv::BufferDescriptorWrite(3, m_fullPathWorkTileBuffer), + vkcv::BufferDescriptorWrite(4, m_copyPathWorkTileBuffer), + vkcv::BufferDescriptorWrite(5, m_fastPathWorkTileBuffer) }; + + m_core->writeDescriptorSet(m_tileVisualisationPass.descriptorSet, visualisationDescriptorWrites); + + const uint32_t tileCount = + (m_core->getImageWidth(m_renderTargets.outputColor) + MotionBlurConfig::maxMotionTileSize - 1) / MotionBlurConfig::maxMotionTileSize * + (m_core->getImageHeight(m_renderTargets.outputColor) + MotionBlurConfig::maxMotionTileSize - 1) / MotionBlurConfig::maxMotionTileSize; + + const uint32_t dispatchCounts[3] = { + tileCount, + 1, + 1 }; + + m_core->recordComputeDispatchToCmdStream( + cmdStream, + m_tileVisualisationPass.pipeline, + dispatchCounts, + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_tileVisualisationPass.descriptorSet).vulkanHandle) }, + vkcv::PushConstants(0)); + } + else { + vkcv_log(vkcv::LogLevel::ERROR, "Unknown eMotionBlurMode enum option"); + return colorBuffer; + } + + return m_renderTargets.outputColor; +} + +vkcv::ImageHandle MotionBlur::renderMotionVectorVisualisation( + const vkcv::CommandStreamHandle cmdStream, + const vkcv::ImageHandle motionBuffer, + const eMotionVectorVisualisationMode mode, + const float velocityRange) { + + computeMotionTiles(cmdStream, motionBuffer); + + vkcv::ImageHandle visualisationInput; + if ( mode == eMotionVectorVisualisationMode::FullResolution) + visualisationInput = motionBuffer; + else if (mode == eMotionVectorVisualisationMode::MaxTile) + visualisationInput = m_renderTargets.motionMax; + else if (mode == eMotionVectorVisualisationMode::MaxTileNeighbourhood) + visualisationInput = m_renderTargets.motionMaxNeighbourhood; + else if (mode == eMotionVectorVisualisationMode::MinTile) + visualisationInput = m_renderTargets.motionMin; + else if (mode == eMotionVectorVisualisationMode::MinTileNeighbourhood) + visualisationInput = m_renderTargets.motionMinNeighbourhood; + else if (mode == eMotionVectorVisualisationMode::None) { + vkcv_log(vkcv::LogLevel::ERROR, "renderMotionVectorVisualisation called with visualisation mode 'None'"); + return motionBuffer; + } + else { + vkcv_log(vkcv::LogLevel::ERROR, "Unknown eDebugView enum value"); + return motionBuffer; + } + + vkcv::DescriptorWrites motionVectorVisualisationDescriptorWrites; + motionVectorVisualisationDescriptorWrites.sampledImageWrites = { + vkcv::SampledImageDescriptorWrite(0, visualisationInput) }; + motionVectorVisualisationDescriptorWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(1, m_nearestSampler) }; + motionVectorVisualisationDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(2, m_renderTargets.outputColor) }; + + m_core->writeDescriptorSet( + m_motionVectorVisualisationPass.descriptorSet, + motionVectorVisualisationDescriptorWrites); + + m_core->prepareImageForSampling(cmdStream, visualisationInput); + m_core->prepareImageForStorage(cmdStream, m_renderTargets.outputColor); + + vkcv::PushConstants motionVectorVisualisationPushConstants(sizeof(float)); + motionVectorVisualisationPushConstants.appendDrawcall(velocityRange); + + const auto dispatchSizes = computeFullscreenDispatchSize( + m_core->getImageWidth(m_renderTargets.outputColor), + m_core->getImageHeight(m_renderTargets.outputColor), + 8); + + m_core->recordComputeDispatchToCmdStream( + cmdStream, + m_motionVectorVisualisationPass.pipeline, + dispatchSizes.data(), + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_motionVectorVisualisationPass.descriptorSet).vulkanHandle) }, + motionVectorVisualisationPushConstants); + + return m_renderTargets.outputColor; +} + +void MotionBlur::computeMotionTiles( + const vkcv::CommandStreamHandle cmdStream, + const vkcv::ImageHandle motionBufferFullRes) { + + // motion vector min max tiles + vkcv::DescriptorWrites motionVectorMaxTilesDescriptorWrites; + motionVectorMaxTilesDescriptorWrites.sampledImageWrites = { + vkcv::SampledImageDescriptorWrite(0, motionBufferFullRes) }; + motionVectorMaxTilesDescriptorWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(1, m_nearestSampler) }; + motionVectorMaxTilesDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(2, m_renderTargets.motionMax), + vkcv::StorageImageDescriptorWrite(3, m_renderTargets.motionMin) }; + + m_core->writeDescriptorSet(m_motionVectorMinMaxPass.descriptorSet, motionVectorMaxTilesDescriptorWrites); + + m_core->prepareImageForSampling(cmdStream, motionBufferFullRes); + m_core->prepareImageForStorage(cmdStream, m_renderTargets.motionMax); + m_core->prepareImageForStorage(cmdStream, m_renderTargets.motionMin); + + const std::array<uint32_t, 3> motionTileDispatchCounts = computeFullscreenDispatchSize( + m_core->getImageWidth( m_renderTargets.motionMax), + m_core->getImageHeight(m_renderTargets.motionMax), + 8); + + m_core->recordComputeDispatchToCmdStream( + cmdStream, + m_motionVectorMinMaxPass.pipeline, + motionTileDispatchCounts.data(), + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_motionVectorMinMaxPass.descriptorSet).vulkanHandle) }, + vkcv::PushConstants(0)); + + // motion vector min max neighbourhood + vkcv::DescriptorWrites motionVectorMaxNeighbourhoodDescriptorWrites; + motionVectorMaxNeighbourhoodDescriptorWrites.sampledImageWrites = { + vkcv::SampledImageDescriptorWrite(0, m_renderTargets.motionMax), + vkcv::SampledImageDescriptorWrite(1, m_renderTargets.motionMin) }; + motionVectorMaxNeighbourhoodDescriptorWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(2, m_nearestSampler) }; + motionVectorMaxNeighbourhoodDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(3, m_renderTargets.motionMaxNeighbourhood), + vkcv::StorageImageDescriptorWrite(4, m_renderTargets.motionMinNeighbourhood) }; + + m_core->writeDescriptorSet(m_motionVectorMinMaxNeighbourhoodPass.descriptorSet, motionVectorMaxNeighbourhoodDescriptorWrites); + + m_core->prepareImageForSampling(cmdStream, m_renderTargets.motionMax); + m_core->prepareImageForSampling(cmdStream, m_renderTargets.motionMin); + + m_core->prepareImageForStorage(cmdStream, m_renderTargets.motionMaxNeighbourhood); + m_core->prepareImageForStorage(cmdStream, m_renderTargets.motionMinNeighbourhood); + + m_core->recordComputeDispatchToCmdStream( + cmdStream, + m_motionVectorMinMaxNeighbourhoodPass.pipeline, + motionTileDispatchCounts.data(), + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_motionVectorMinMaxNeighbourhoodPass.descriptorSet).vulkanHandle) }, + vkcv::PushConstants(0)); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/src/MotionBlur.hpp b/projects/indirect_dispatch/src/MotionBlur.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b50f0af60d566dc0e4fc00c31b7b834e11679bf5 --- /dev/null +++ b/projects/indirect_dispatch/src/MotionBlur.hpp @@ -0,0 +1,84 @@ +#pragma once +#include <vkcv/Core.hpp> +#include "AppSetup.hpp" +#include "MotionBlurSetup.hpp" + +// selection for motion blur input and visualisation +enum class eMotionVectorVisualisationMode : int { + None = 0, + FullResolution = 1, + MaxTile = 2, + MaxTileNeighbourhood = 3, + MinTile = 4, + MinTileNeighbourhood = 5, + OptionCount = 6 }; + +static const char* MotionVectorVisualisationModeLabels[6] = { + "None", + "Full resolution", + "Max tile", + "Tile neighbourhood max", + "Min Tile", + "Tile neighbourhood min"}; + +enum class eMotionBlurMode : int { + Default = 0, + Disabled = 1, + TileVisualisation = 2, + OptionCount = 3 }; + +static const char* MotionBlurModeLabels[3] = { + "Default", + "Disabled", + "Tile visualisation" }; + +class MotionBlur { +public: + + bool initialize(vkcv::Core* corePtr, const uint32_t targetWidth, const uint32_t targetHeight); + void setResolution(const uint32_t targetWidth, const uint32_t targetHeight); + + vkcv::ImageHandle render( + const vkcv::CommandStreamHandle cmdStream, + const vkcv::ImageHandle motionBufferFullRes, + const vkcv::ImageHandle colorBuffer, + const vkcv::ImageHandle depthBuffer, + const eMotionBlurMode mode, + const float cameraNear, + const float cameraFar, + const float deltaTimeSeconds, + const float cameraShutterSpeedInverse, + const float motionTileOffsetLength, + const float fastPathThreshold); + + vkcv::ImageHandle renderMotionVectorVisualisation( + const vkcv::CommandStreamHandle cmdStream, + const vkcv::ImageHandle motionBuffer, + const eMotionVectorVisualisationMode mode, + const float velocityRange); + +private: + // computes max per tile and neighbourhood tile max + void computeMotionTiles( + const vkcv::CommandStreamHandle cmdStream, + const vkcv::ImageHandle motionBufferFullRes); + + vkcv::Core* m_core; + + MotionBlurRenderTargets m_renderTargets; + vkcv::SamplerHandle m_nearestSampler; + + ComputePassHandles m_motionBlurPass; + ComputePassHandles m_motionVectorMinMaxPass; + ComputePassHandles m_motionVectorMinMaxNeighbourhoodPass; + ComputePassHandles m_motionVectorVisualisationPass; + ComputePassHandles m_colorCopyPass; + ComputePassHandles m_tileClassificationPass; + ComputePassHandles m_tileResetPass; + ComputePassHandles m_tileVisualisationPass; + ComputePassHandles m_motionBlurFastPathPass; + + vkcv::BufferHandle m_fullPathWorkTileBuffer; + vkcv::BufferHandle m_copyPathWorkTileBuffer; + vkcv::BufferHandle m_fastPathWorkTileBuffer; +}; \ No newline at end of file diff --git a/projects/indirect_dispatch/src/MotionBlurConfig.hpp b/projects/indirect_dispatch/src/MotionBlurConfig.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7552abd246ca8d2e7489c5065f43ef8b48af7cd2 --- /dev/null +++ b/projects/indirect_dispatch/src/MotionBlurConfig.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "vulkan/vulkan.hpp" + +namespace MotionBlurConfig { + const vk::Format motionVectorTileFormat = vk::Format::eR16G16Sfloat; + const vk::Format outputColorFormat = vk::Format::eB10G11R11UfloatPack32; + const uint32_t maxMotionTileSize = 16; // must match "motionTileSize" in motionBlurConfig.inc + const uint32_t maxWidth = 3840; + const uint32_t maxHeight = 2160; +} \ No newline at end of file diff --git a/projects/indirect_dispatch/src/MotionBlurSetup.cpp b/projects/indirect_dispatch/src/MotionBlurSetup.cpp new file mode 100644 index 0000000000000000000000000000000000000000..82d2593a5b976f9389b58dddac43e3a45d1db303 --- /dev/null +++ b/projects/indirect_dispatch/src/MotionBlurSetup.cpp @@ -0,0 +1,57 @@ +#include "MotionBlurSetup.hpp" +#include "MotionBlurConfig.hpp" + +namespace MotionBlurSetup { + +MotionBlurRenderTargets createRenderTargets(const uint32_t width, const uint32_t height, vkcv::Core& core) { + + MotionBlurRenderTargets targets; + + // divide and ceil to int + const uint32_t motionMaxWidth = (width + (MotionBlurConfig::maxMotionTileSize - 1)) / MotionBlurConfig::maxMotionTileSize; + const uint32_t motionMaxheight = (height + (MotionBlurConfig::maxMotionTileSize - 1)) / MotionBlurConfig::maxMotionTileSize; + + targets.motionMax = core.createImage( + MotionBlurConfig::motionVectorTileFormat, + motionMaxWidth, + motionMaxheight, + 1, + false, + true).getHandle(); + + targets.motionMaxNeighbourhood = core.createImage( + MotionBlurConfig::motionVectorTileFormat, + motionMaxWidth, + motionMaxheight, + 1, + false, + true).getHandle(); + + targets.motionMin = core.createImage( + MotionBlurConfig::motionVectorTileFormat, + motionMaxWidth, + motionMaxheight, + 1, + false, + true).getHandle(); + + targets.motionMinNeighbourhood = core.createImage( + MotionBlurConfig::motionVectorTileFormat, + motionMaxWidth, + motionMaxheight, + 1, + false, + true).getHandle(); + + targets.outputColor = core.createImage( + MotionBlurConfig::outputColorFormat, + width, + height, + 1, + false, + true).getHandle(); + + return targets; +} + +} // namespace MotionBlurSetup \ No newline at end of file diff --git a/projects/indirect_dispatch/src/MotionBlurSetup.hpp b/projects/indirect_dispatch/src/MotionBlurSetup.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ca169d7c6b04aa152d42ba36c3d2e02e563bbd91 --- /dev/null +++ b/projects/indirect_dispatch/src/MotionBlurSetup.hpp @@ -0,0 +1,14 @@ +#pragma once +#include <vkcv/Core.hpp> + +struct MotionBlurRenderTargets { + vkcv::ImageHandle outputColor; + vkcv::ImageHandle motionMax; + vkcv::ImageHandle motionMaxNeighbourhood; + vkcv::ImageHandle motionMin; + vkcv::ImageHandle motionMinNeighbourhood; +}; + +namespace MotionBlurSetup { + MotionBlurRenderTargets createRenderTargets(const uint32_t width, const uint32_t height, vkcv::Core& core); +} \ No newline at end of file diff --git a/projects/indirect_dispatch/src/main.cpp b/projects/indirect_dispatch/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b27e0bcb8f1991d76b570b79da9cc4734cf52950 --- /dev/null +++ b/projects/indirect_dispatch/src/main.cpp @@ -0,0 +1,13 @@ +#include "App.hpp" + +int main(int argc, const char** argv) { + + App app; + if (!app.initialize()) { + std::cerr << "Application initialization failed, exiting" << std::endl; + return 1; + } + app.run(); + + return 0; +} diff --git a/projects/mesh_shader/.gitignore b/projects/mesh_shader/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..54601c357bf3fb97b914a6e657c042a5c6a985d7 --- /dev/null +++ b/projects/mesh_shader/.gitignore @@ -0,0 +1 @@ +mesh_shader diff --git a/projects/mesh_shader/CMakeLists.txt b/projects/mesh_shader/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..1aa5d5ff3977a47dce75a38329216d550b1b9311 --- /dev/null +++ b/projects/mesh_shader/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.16) +project(mesh_shader) + +# setting c++ standard for the project +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# this should fix the execution path to load local files from the project +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +# adding source files to the project +add_executable(mesh_shader src/main.cpp) + +target_sources(mesh_shader PRIVATE) + +# this should fix the execution path to load local files from the project (for MSVC) +if(MSVC) + set_target_properties(mesh_shader PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + set_target_properties(mesh_shader PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + + # in addition to setting the output directory, the working directory has to be set + # by default visual studio sets the working directory to the build directory, when using the debugger + set_target_properties(mesh_shader PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) +endif() + +# including headers of dependencies and the VkCV framework +target_include_directories(mesh_shader SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_testing_include} ${vkcv_camera_include} ${vkcv_meshlet_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include}) + +# linking with libraries from all dependencies and the VkCV framework +target_link_libraries(mesh_shader vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_testing vkcv_camera vkcv_meshlet vkcv_shader_compiler vkcv_gui) \ No newline at end of file diff --git a/projects/mesh_shader/resources/Bunny/Bunny.glb b/projects/mesh_shader/resources/Bunny/Bunny.glb new file mode 100644 index 0000000000000000000000000000000000000000..181f1f92f1906e1e1ba900768580203efe19e9be --- /dev/null +++ b/projects/mesh_shader/resources/Bunny/Bunny.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8bc6fab11929ca11bdf4e892ffb03b621b10307f705cdea17d82d3dee3b9aae +size 4045836 diff --git a/projects/mesh_shader/resources/monke.glb b/projects/mesh_shader/resources/monke.glb new file mode 100644 index 0000000000000000000000000000000000000000..47d0b9131f15a8f0697318d0a47302c71cad1db8 --- /dev/null +++ b/projects/mesh_shader/resources/monke.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:597584db90a3f51088beea6652d8320e82cb025f9d3d036b89e54ad72c732a06 +size 98612 diff --git a/projects/mesh_shader/resources/shaders/common.inc b/projects/mesh_shader/resources/shaders/common.inc new file mode 100644 index 0000000000000000000000000000000000000000..280ffee215a8b8342b78d1f5558d63a05e16859b --- /dev/null +++ b/projects/mesh_shader/resources/shaders/common.inc @@ -0,0 +1,4 @@ +struct ObjectMatrices{ + mat4 model; + mat4 mvp; +}; \ No newline at end of file diff --git a/projects/mesh_shader/resources/shaders/meshlet.inc b/projects/mesh_shader/resources/shaders/meshlet.inc new file mode 100644 index 0000000000000000000000000000000000000000..0594f62ceead8ffca09b585305075eb6046f3c46 --- /dev/null +++ b/projects/mesh_shader/resources/shaders/meshlet.inc @@ -0,0 +1,8 @@ +struct Meshlet{ + uint vertexOffset; + uint vertexCount; + uint indexOffset; + uint indexCount; + vec3 meanPosition; + float boundingSphereRadius; +}; \ No newline at end of file diff --git a/projects/mesh_shader/resources/shaders/shader.frag b/projects/mesh_shader/resources/shaders/shader.frag new file mode 100644 index 0000000000000000000000000000000000000000..f4f6982f2089e6c8e102027f3b8763bb38f8e59c --- /dev/null +++ b/projects/mesh_shader/resources/shaders/shader.frag @@ -0,0 +1,32 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 passNormal; +layout(location = 1) in flat uint passTaskIndex; +layout(location = 0) out vec3 outColor; + +uint lowbias32(uint x) +{ + x ^= x >> 16; + x *= 0x7feb352dU; + x ^= x >> 15; + x *= 0x846ca68bU; + x ^= x >> 16; + return x; +} + +float hashToFloat(uint hash){ + return (hash % 255) / 255.f; +} + +vec3 colorFromIndex(uint i){ + return vec3( + hashToFloat(lowbias32(i+0)), + hashToFloat(lowbias32(i+1)), + hashToFloat(lowbias32(i+2))); +} + +void main() { + outColor = normalize(passNormal) * 0.5 + 0.5; + outColor = colorFromIndex(passTaskIndex); +} \ No newline at end of file diff --git a/projects/mesh_shader/resources/shaders/shader.mesh b/projects/mesh_shader/resources/shaders/shader.mesh new file mode 100644 index 0000000000000000000000000000000000000000..30c98610f4776204ff526c57c1f793e371194629 --- /dev/null +++ b/projects/mesh_shader/resources/shaders/shader.mesh @@ -0,0 +1,78 @@ +#version 460 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_GOOGLE_include_directive : enable +#extension GL_NV_mesh_shader : require + +#include "meshlet.inc" + +layout(local_size_x=32) in; + +layout(triangles) out; +layout(max_vertices=64, max_primitives=126) out; + +layout(location = 0) out vec3 passNormal[]; +layout(location = 1) out uint passTaskIndex[]; + +struct Vertex +{ + vec3 position; float padding0; + vec3 normal; float padding1; +}; + +layout(std430, binding = 0) readonly buffer vertexBuffer +{ + Vertex vertices[]; +}; + +layout(std430, binding = 1) readonly buffer indexBuffer +{ + uint localIndices[]; // breaks for 16 bit indices +}; + +layout(std430, binding = 2) readonly buffer meshletBuffer +{ + Meshlet meshlets[]; +}; + +taskNV in Task { + uint meshletIndices[32]; + mat4 mvp; +} IN; + +void main() { + + uint meshletIndex = IN.meshletIndices[gl_WorkGroupID.x]; + Meshlet meshlet = meshlets[meshletIndex]; + + // set vertices + for(uint i = 0; i < 2; i++){ + + uint workIndex = gl_LocalInvocationID.x + 32 * i; + if(workIndex >= meshlet.vertexCount){ + break; + } + + uint vertexIndex = meshlet.vertexOffset + workIndex; + Vertex vertex = vertices[vertexIndex]; + + gl_MeshVerticesNV[workIndex].gl_Position = IN.mvp * vec4(vertex.position, 1); + passNormal[workIndex] = vertex.normal; + passTaskIndex[workIndex] = meshletIndex; + } + + // set local indices + for(uint i = 0; i < 12; i++){ + + uint workIndex = gl_LocalInvocationID.x + i * 32; + if(workIndex >= meshlet.indexCount){ + break; + } + + uint indexBufferIndex = meshlet.indexOffset + workIndex; + gl_PrimitiveIndicesNV[workIndex] = localIndices[indexBufferIndex]; + } + + if(gl_LocalInvocationID.x == 0){ + gl_PrimitiveCountNV = meshlet.indexCount / 3; + } +} \ No newline at end of file diff --git a/projects/mesh_shader/resources/shaders/shader.task b/projects/mesh_shader/resources/shaders/shader.task new file mode 100644 index 0000000000000000000000000000000000000000..7a692e98e6384767191d76cef940e295ca127d62 --- /dev/null +++ b/projects/mesh_shader/resources/shaders/shader.task @@ -0,0 +1,78 @@ +#version 460 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_NV_mesh_shader : require +#extension GL_GOOGLE_include_directive : enable + +#include "meshlet.inc" +#include "common.inc" + +layout(local_size_x=32) in; + +taskNV out Task { + uint meshletIndices[32]; + mat4 mvp; +} OUT; + +layout( push_constant ) uniform constants{ + uint matrixIndex; + uint meshletCount; +}; + +// TODO: reuse mesh stage binding at location 2 after required fix in framework +layout(std430, binding = 5) readonly buffer meshletBuffer +{ + Meshlet meshlets[]; +}; + +struct Plane{ + vec3 pointOnPlane; + float padding0; + vec3 normal; + float padding1; +}; + +layout(set=0, binding=3, std140) uniform cameraPlaneBuffer{ + Plane cameraPlanes[6]; +}; + +layout(std430, binding = 4) readonly buffer matrixBuffer +{ + ObjectMatrices objectMatrices[]; +}; + +shared uint taskCount; + +bool isSphereInsideFrustum(vec3 spherePos, float sphereRadius, Plane cameraPlanes[6]){ + bool isInside = true; + for(int i = 0; i < 6; i++){ + Plane p = cameraPlanes[i]; + isInside = isInside && dot(p.normal, spherePos - p.pointOnPlane) - sphereRadius < 0; + } + return isInside; +} + +void main() { + + if(gl_LocalInvocationID.x >= meshletCount){ + return; + } + + uint meshletIndex = gl_GlobalInvocationID.x; + Meshlet meshlet = meshlets[meshletIndex]; + + if(gl_LocalInvocationID.x == 0){ + taskCount = 0; + } + + // TODO: scaling support + vec3 meshletPositionWorld = (vec4(meshlet.meanPosition, 1) * objectMatrices[matrixIndex].model).xyz; + if(isSphereInsideFrustum(meshletPositionWorld, meshlet.boundingSphereRadius, cameraPlanes)){ + uint outIndex = atomicAdd(taskCount, 1); + OUT.meshletIndices[outIndex] = gl_GlobalInvocationID.x; + } + + if(gl_LocalInvocationID.x == 0){ + gl_TaskCountNV = taskCount; + OUT.mvp = objectMatrices[matrixIndex].mvp; + } +} \ No newline at end of file diff --git a/projects/mesh_shader/resources/shaders/shader.vert b/projects/mesh_shader/resources/shaders/shader.vert new file mode 100644 index 0000000000000000000000000000000000000000..fca5057976f995183c040195bdbd592c63f1074e --- /dev/null +++ b/projects/mesh_shader/resources/shaders/shader.vert @@ -0,0 +1,29 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_GOOGLE_include_directive : enable + +#include "common.inc" + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inNormal; + +layout(location = 0) out vec3 passNormal; +layout(location = 1) out uint dummyOutput; + +layout(std430, binding = 0) readonly buffer matrixBuffer +{ + ObjectMatrices objectMatrices[]; +}; + +layout( push_constant ) uniform constants{ + uint matrixIndex; + uint padding; // pad to same size as mesh shader constants +}; + + +void main() { + gl_Position = objectMatrices[matrixIndex].mvp * vec4(inPosition, 1.0); + passNormal = inNormal; + + dummyOutput = padding * 0; // padding must be used, else compiler shrinks constant size +} \ No newline at end of file diff --git a/projects/mesh_shader/src/main.cpp b/projects/mesh_shader/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..611a324f875f5726ebd674e3ee51d27ad2d8e849 --- /dev/null +++ b/projects/mesh_shader/src/main.cpp @@ -0,0 +1,387 @@ +#include <iostream> +#include <vkcv/Core.hpp> +#include <GLFW/glfw3.h> +#include <vkcv/camera/CameraManager.hpp> +#include <chrono> + +#include <vkcv/shader/GLSLCompiler.hpp> +#include <vkcv/gui/GUI.hpp> +#include <vkcv/asset/asset_loader.hpp> +#include <vkcv/meshlet/Meshlet.hpp> +#include <vkcv/meshlet/Tipsify.hpp> +#include <vkcv/meshlet/Forsyth.hpp> + +struct Plane { + glm::vec3 pointOnPlane; + float padding0; + glm::vec3 normal; + float padding1; +}; + +struct CameraPlanes { + Plane planes[6]; +}; + +CameraPlanes computeCameraPlanes(const vkcv::camera::Camera& camera) { + const float fov = camera.getFov(); + const glm::vec3 pos = camera.getPosition(); + const float ratio = camera.getRatio(); + const glm::vec3 forward = glm::normalize(camera.getFront()); + float near; + float far; + camera.getNearFar(near, far); + + glm::vec3 up = glm::vec3(0, -1, 0); + glm::vec3 right = glm::normalize(glm::cross(forward, up)); + up = glm::cross(forward, right); + + const glm::vec3 nearCenter = pos + forward * near; + const glm::vec3 farCenter = pos + forward * far; + + const float tanFovHalf = glm::tan(fov / 2); + + const glm::vec3 nearUpCenter = nearCenter + up * tanFovHalf * near; + const glm::vec3 nearDownCenter = nearCenter - up * tanFovHalf * near; + const glm::vec3 nearRightCenter = nearCenter + right * tanFovHalf * near * ratio; + const glm::vec3 nearLeftCenter = nearCenter - right * tanFovHalf * near * ratio; + + const glm::vec3 farUpCenter = farCenter + up * tanFovHalf * far; + const glm::vec3 farDownCenter = farCenter - up * tanFovHalf * far; + const glm::vec3 farRightCenter = farCenter + right * tanFovHalf * far * ratio; + const glm::vec3 farLeftCenter = farCenter - right * tanFovHalf * far * ratio; + + CameraPlanes cameraPlanes; + // near + cameraPlanes.planes[0].pointOnPlane = nearCenter; + cameraPlanes.planes[0].normal = -forward; + // far + cameraPlanes.planes[1].pointOnPlane = farCenter; + cameraPlanes.planes[1].normal = forward; + + // top + cameraPlanes.planes[2].pointOnPlane = nearUpCenter; + cameraPlanes.planes[2].normal = glm::normalize(glm::cross(farUpCenter - nearUpCenter, right)); + // bot + cameraPlanes.planes[3].pointOnPlane = nearDownCenter; + cameraPlanes.planes[3].normal = glm::normalize(glm::cross(right, farDownCenter - nearDownCenter)); + + // right + cameraPlanes.planes[4].pointOnPlane = nearRightCenter; + cameraPlanes.planes[4].normal = glm::normalize(glm::cross(up, farRightCenter - nearRightCenter)); + // left + cameraPlanes.planes[5].pointOnPlane = nearLeftCenter; + cameraPlanes.planes[5].normal = glm::normalize(glm::cross(farLeftCenter - nearLeftCenter, up)); + + return cameraPlanes; +} + +int main(int argc, const char** argv) { + const char* applicationName = "Mesh shader"; + + const int windowWidth = 1280; + const int windowHeight = 720; + vkcv::Window window = vkcv::Window::create( + applicationName, + windowWidth, + windowHeight, + false + ); + + vkcv::Core core = vkcv::Core::create( + window, + applicationName, + VK_MAKE_VERSION(0, 0, 1), + { vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute }, + {}, + { "VK_KHR_swapchain", VK_NV_MESH_SHADER_EXTENSION_NAME } + ); + + vkcv::gui::GUI gui (core, window); + + vkcv::asset::Scene mesh; + const char* path = argc > 1 ? argv[1] : "resources/Bunny/Bunny.glb"; + vkcv::asset::loadScene(path, mesh); + + assert(!mesh.vertexGroups.empty()); + + auto vertexBuffer = core.createBuffer<uint8_t>( + vkcv::BufferType::VERTEX, + mesh.vertexGroups[0].vertexBuffer.data.size(), + vkcv::BufferMemoryType::DEVICE_LOCAL + ); + vertexBuffer.fill(mesh.vertexGroups[0].vertexBuffer.data); + + auto indexBuffer = core.createBuffer<uint8_t>( + vkcv::BufferType::INDEX, + mesh.vertexGroups[0].indexBuffer.data.size(), + vkcv::BufferMemoryType::DEVICE_LOCAL + ); + indexBuffer.fill(mesh.vertexGroups[0].indexBuffer.data); + + // format data for mesh shader + auto& attributes = mesh.vertexGroups[0].vertexBuffer.attributes; + + std::sort(attributes.begin(), attributes.end(), [](const vkcv::asset::VertexAttribute& x, const vkcv::asset::VertexAttribute& y) { + return static_cast<uint32_t>(x.type) < static_cast<uint32_t>(y.type); + }); + + const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = { + vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[0].offset), vertexBuffer.getVulkanHandle()), + vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[1].offset), vertexBuffer.getVulkanHandle()), + vkcv::VertexBufferBinding(static_cast<vk::DeviceSize>(attributes[2].offset), vertexBuffer.getVulkanHandle()) }; + + const auto& bunny = mesh.vertexGroups[0]; + std::vector<vkcv::meshlet::Vertex> interleavedVertices = vkcv::meshlet::convertToVertices(bunny.vertexBuffer.data, bunny.numVertices, attributes[0], attributes[1]); + // mesh shader buffers + const auto& assetLoaderIndexBuffer = mesh.vertexGroups[0].indexBuffer; + std::vector<uint32_t> indexBuffer32Bit = vkcv::meshlet::assetLoaderIndicesTo32BitIndices(assetLoaderIndexBuffer.data, assetLoaderIndexBuffer.type); + vkcv::meshlet::VertexCacheReorderResult tipsifyResult = vkcv::meshlet::tipsifyMesh(indexBuffer32Bit, interleavedVertices.size()); + vkcv::meshlet::VertexCacheReorderResult forsythResult = vkcv::meshlet::forsythReorder(indexBuffer32Bit, interleavedVertices.size()); + + const auto meshShaderModelData = createMeshShaderModelData(interleavedVertices, forsythResult.indexBuffer, forsythResult.skippedIndices); + + auto meshShaderVertexBuffer = core.createBuffer<vkcv::meshlet::Vertex>( + vkcv::BufferType::STORAGE, + meshShaderModelData.vertices.size()); + meshShaderVertexBuffer.fill(meshShaderModelData.vertices); + + auto meshShaderIndexBuffer = core.createBuffer<uint32_t>( + vkcv::BufferType::STORAGE, + meshShaderModelData.localIndices.size()); + meshShaderIndexBuffer.fill(meshShaderModelData.localIndices); + + auto meshletBuffer = core.createBuffer<vkcv::meshlet::Meshlet>( + vkcv::BufferType::STORAGE, + meshShaderModelData.meshlets.size(), + vkcv::BufferMemoryType::DEVICE_LOCAL + ); + meshletBuffer.fill(meshShaderModelData.meshlets); + + // attachments + const vkcv::AttachmentDescription present_color_attachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::CLEAR, + core.getSwapchain().getFormat()); + + const vkcv::AttachmentDescription depth_attachment( + vkcv::AttachmentOperation::STORE, + vkcv::AttachmentOperation::CLEAR, + vk::Format::eD32Sfloat + ); + + vkcv::PassConfig bunnyPassDefinition({ present_color_attachment, depth_attachment }); + vkcv::PassHandle renderPass = core.createPass(bunnyPassDefinition); + + if (!renderPass) + { + std::cout << "Error. Could not create renderpass. Exiting." << std::endl; + return EXIT_FAILURE; + } + + vkcv::ShaderProgram bunnyShaderProgram{}; + vkcv::shader::GLSLCompiler compiler; + + compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"), + [&bunnyShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + bunnyShaderProgram.addShader(shaderStage, path); + }); + + compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"), + [&bunnyShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + bunnyShaderProgram.addShader(shaderStage, path); + }); + + const std::vector<vkcv::VertexAttachment> vertexAttachments = bunnyShaderProgram.getVertexAttachments(); + std::vector<vkcv::VertexBinding> bindings; + for (size_t i = 0; i < vertexAttachments.size(); i++) { + bindings.push_back(vkcv::VertexBinding(i, { vertexAttachments[i] })); + } + const vkcv::VertexLayout bunnyLayout (bindings); + + vkcv::DescriptorSetHandle vertexShaderDescriptorSet = core.createDescriptorSet(bunnyShaderProgram.getReflectedDescriptors()[0]); + + const vkcv::PipelineConfig bunnyPipelineDefinition { + bunnyShaderProgram, + (uint32_t)windowWidth, + (uint32_t)windowHeight, + renderPass, + { bunnyLayout }, + { core.getDescriptorSet(vertexShaderDescriptorSet).layout }, + false + }; + + struct ObjectMatrices { + glm::mat4 model; + glm::mat4 mvp; + }; + const size_t objectCount = 1; + vkcv::Buffer<ObjectMatrices> matrixBuffer = core.createBuffer<ObjectMatrices>(vkcv::BufferType::STORAGE, objectCount); + + vkcv::DescriptorWrites vertexShaderDescriptorWrites; + vertexShaderDescriptorWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0, matrixBuffer.getHandle()) }; + core.writeDescriptorSet(vertexShaderDescriptorSet, vertexShaderDescriptorWrites); + + vkcv::PipelineHandle bunnyPipeline = core.createGraphicsPipeline(bunnyPipelineDefinition); + + if (!bunnyPipeline) + { + std::cout << "Error. Could not create graphics pipeline. Exiting." << std::endl; + return EXIT_FAILURE; + } + + // mesh shader + vkcv::ShaderProgram meshShaderProgram; + compiler.compile(vkcv::ShaderStage::TASK, std::filesystem::path("resources/shaders/shader.task"), + [&meshShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + meshShaderProgram.addShader(shaderStage, path); + }); + + compiler.compile(vkcv::ShaderStage::MESH, std::filesystem::path("resources/shaders/shader.mesh"), + [&meshShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + meshShaderProgram.addShader(shaderStage, path); + }); + + compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"), + [&meshShaderProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + meshShaderProgram.addShader(shaderStage, path); + }); + + uint32_t setID = 0; + vkcv::DescriptorSetHandle meshShaderDescriptorSet = core.createDescriptorSet( meshShaderProgram.getReflectedDescriptors()[setID]); + const vkcv::VertexLayout meshShaderLayout(bindings); + + const vkcv::PipelineConfig meshShaderPipelineDefinition{ + meshShaderProgram, + (uint32_t)windowWidth, + (uint32_t)windowHeight, + renderPass, + {meshShaderLayout}, + {core.getDescriptorSet(meshShaderDescriptorSet).layout}, + false + }; + + vkcv::PipelineHandle meshShaderPipeline = core.createGraphicsPipeline(meshShaderPipelineDefinition); + + if (!meshShaderPipeline) + { + std::cout << "Error. Could not create mesh shader pipeline. Exiting." << std::endl; + return EXIT_FAILURE; + } + + vkcv::Buffer<CameraPlanes> cameraPlaneBuffer = core.createBuffer<CameraPlanes>(vkcv::BufferType::UNIFORM, 1); + + vkcv::DescriptorWrites meshShaderWrites; + meshShaderWrites.storageBufferWrites = { + vkcv::BufferDescriptorWrite(0, meshShaderVertexBuffer.getHandle()), + vkcv::BufferDescriptorWrite(1, meshShaderIndexBuffer.getHandle()), + vkcv::BufferDescriptorWrite(2, meshletBuffer.getHandle()), + vkcv::BufferDescriptorWrite(4, matrixBuffer.getHandle()), + vkcv::BufferDescriptorWrite(5, meshletBuffer.getHandle()), + }; + meshShaderWrites.uniformBufferWrites = { + vkcv::BufferDescriptorWrite(3, cameraPlaneBuffer.getHandle()), + }; + + core.writeDescriptorSet( meshShaderDescriptorSet, meshShaderWrites); + + vkcv::ImageHandle depthBuffer = core.createImage(vk::Format::eD32Sfloat, windowWidth, windowHeight, 1, false).getHandle(); + + auto start = std::chrono::system_clock::now(); + + vkcv::ImageHandle swapchainImageHandle = vkcv::ImageHandle::createSwapchainImageHandle(); + + const vkcv::Mesh renderMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices, vkcv::IndexBitCount::Bit32); + + const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle(); + + vkcv::camera::CameraManager cameraManager(window); + uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); + + cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -2)); + + bool useMeshShader = true; + bool updateFrustumPlanes = true; + + while (window.isWindowOpen()) + { + vkcv::Window::pollEvents(); + + uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem + if (!core.beginFrame(swapchainWidth, swapchainHeight)) { + continue; + } + + auto end = std::chrono::system_clock::now(); + auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start); + start = end; + + cameraManager.update(0.000001 * static_cast<double>(deltatime.count())); + + const vkcv::camera::Camera& camera = cameraManager.getActiveCamera(); + + ObjectMatrices objectMatrices; + objectMatrices.model = *reinterpret_cast<glm::mat4*>(&mesh.meshes.front().modelMatrix); + objectMatrices.mvp = camera.getMVP() * objectMatrices.model; + + matrixBuffer.fill({ objectMatrices }); + + struct PushConstants { + uint32_t matrixIndex; + uint32_t meshletCount; + }; + PushConstants pushConstants{ 0, static_cast<uint32_t>(meshShaderModelData.meshlets.size()) }; + + if (updateFrustumPlanes) { + const CameraPlanes cameraPlanes = computeCameraPlanes(camera); + cameraPlaneBuffer.fill({ cameraPlanes }); + } + + const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer }; + auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); + + vkcv::PushConstants pushConstantData(sizeof(pushConstants)); + pushConstantData.appendDrawcall(pushConstants); + + if (useMeshShader) { + + vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(meshShaderDescriptorSet).vulkanHandle); + const uint32_t taskCount = (meshShaderModelData.meshlets.size() + 31) / 32; + + core.recordMeshShaderDrawcalls( + cmdStream, + renderPass, + meshShaderPipeline, + pushConstantData, + { vkcv::MeshShaderDrawcall({descriptorUsage}, taskCount)}, + { renderTargets }); + } + else { + + vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(vertexShaderDescriptorSet).vulkanHandle); + + core.recordDrawcallsToCmdStream( + cmdStream, + renderPass, + bunnyPipeline, + pushConstantData, + { vkcv::DrawcallInfo(renderMesh, { descriptorUsage }) }, + { renderTargets }); + } + + core.prepareSwapchainImageForPresent(cmdStream); + core.submitCommandStream(cmdStream); + + gui.beginGUI(); + + ImGui::Begin("Settings"); + ImGui::Checkbox("Use mesh shader", &useMeshShader); + ImGui::Checkbox("Update frustum culling", &updateFrustumPlanes); + + ImGui::End(); + + gui.endGUI(); + + core.endFrame(); + } + return 0; +} diff --git a/projects/particle_simulation/src/BloomAndFlares.cpp b/projects/particle_simulation/src/BloomAndFlares.cpp index 23ace2bc35a2e421613718c62380f9161a408f70..5961aae664a39dfb9bd597ffa7648c9b67999af4 100644 --- a/projects/particle_simulation/src/BloomAndFlares.cpp +++ b/projects/particle_simulation/src/BloomAndFlares.cpp @@ -104,7 +104,7 @@ void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStre m_DownsamplePipe, initialDispatchCount, {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[0]).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); // downsample dispatches of blur buffer's mip maps float mipDispatchCountX = dispatchCountX; @@ -139,7 +139,7 @@ void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStre m_DownsamplePipe, mipDispatchCount, {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[mipLevel]).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); // image barrier between mips p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle()); @@ -184,7 +184,7 @@ void BloomAndFlares::execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream m_UpsamplePipe, upsampleDispatchCount, {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_UpsampleDescSets[mipLevel]).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0) + vkcv::PushConstants(0) ); // image barrier between mips p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle()); @@ -216,7 +216,7 @@ void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStr m_LensFlarePipe, lensFeatureDispatchCount, {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_LensFlareDescSet).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); } void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStream, @@ -249,7 +249,7 @@ void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStrea m_CompositePipe, compositeDispatchCount, {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_CompositeDescSet).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); } void BloomAndFlares::execWholePipeline(const vkcv::CommandStreamHandle &cmdStream, @@ -263,6 +263,10 @@ void BloomAndFlares::execWholePipeline(const vkcv::CommandStreamHandle &cmdStrea void BloomAndFlares::updateImageDimensions(uint32_t width, uint32_t height) { + if ((width == m_Width) && (height == m_Height)) { + return; + } + m_Width = width; m_Height = height; diff --git a/projects/particle_simulation/src/Particle.cpp b/projects/particle_simulation/src/Particle.cpp index 387728eb366430e4373282da785bbff47de17e7a..b80d063d382c9ae1cb63887388cce065b8289b63 100644 --- a/projects/particle_simulation/src/Particle.cpp +++ b/projects/particle_simulation/src/Particle.cpp @@ -3,16 +3,17 @@ Particle::Particle(glm::vec3 position, glm::vec3 velocity, float lifeTime) : m_position(position), -m_velocity(velocity), -m_lifeTime(lifeTime), -m_reset_velocity(velocity) + m_lifeTime(lifeTime), + m_velocity(velocity), + m_mass(1.0f), + m_reset_velocity(velocity) {} const glm::vec3& Particle::getPosition()const{ return m_position; } -const bool Particle::isAlive()const{ +bool Particle::isAlive()const{ return m_lifeTime > 0.f; } diff --git a/projects/particle_simulation/src/Particle.hpp b/projects/particle_simulation/src/Particle.hpp index f374218fd8a08f1e1bf367bdc899a71c55ea1b78..73e7cbf517709ee03274cfd199081ade3f756545 100644 --- a/projects/particle_simulation/src/Particle.hpp +++ b/projects/particle_simulation/src/Particle.hpp @@ -17,7 +17,7 @@ public: void update( const float delta ); - const bool isAlive()const; + bool isAlive()const; void setLifeTime( const float lifeTime ); @@ -28,7 +28,7 @@ private: glm::vec3 m_position; float m_lifeTime; glm::vec3 m_velocity; - float mass = 1.f; + float m_mass; glm::vec3 m_reset_velocity; - float padding_3 = 0.f; + float padding_3; }; diff --git a/projects/particle_simulation/src/main.cpp b/projects/particle_simulation/src/main.cpp index a22044f0d2588a43a5e7a0f6cba25d9c7460be9f..07ba6b194ce72dbad15a921ca13a4814c6d4f5df 100644 --- a/projects/particle_simulation/src/main.cpp +++ b/projects/particle_simulation/src/main.cpp @@ -58,12 +58,28 @@ int main(int argc, const char **argv) { return EXIT_FAILURE; } - // use space or use water - bool useSpace = true; + // use space or use water or gravity + std::string shaderPathCompute = "shaders/shader_space.comp"; + std::string shaderPathFragment = "shaders/shader_space.frag"; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--space") == 0) { + shaderPathCompute = "shaders/shader_space.comp"; + shaderPathFragment = "shaders/shader_space.frag"; + } else + if (strcmp(argv[i], "--water") == 0) { + shaderPathCompute = "shaders/shader_water.comp"; + shaderPathFragment = "shaders/shader_water.frag"; + } else + if (strcmp(argv[i], "--gravity") == 0) { + shaderPathCompute = "shaders/shader_gravity.comp"; + shaderPathFragment = "shaders/shader_space.frag"; + } + } vkcv::shader::GLSLCompiler compiler; vkcv::ShaderProgram computeShaderProgram{}; - compiler.compile(vkcv::ShaderStage::COMPUTE, useSpace ? "shaders/shader_space.comp" : "shaders/shader_water.comp", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + compiler.compile(vkcv::ShaderStage::COMPUTE, shaderPathCompute, [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { computeShaderProgram.addShader(shaderStage, path); }); @@ -81,7 +97,7 @@ int main(int argc, const char **argv) { compiler.compile(vkcv::ShaderStage::VERTEX, "shaders/shader.vert", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { particleShaderProgram.addShader(shaderStage, path); }); - compiler.compile(vkcv::ShaderStage::FRAGMENT, useSpace ? "shaders/shader_space.frag" : "shaders/shader_water.frag", [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + compiler.compile(vkcv::ShaderStage::FRAGMENT, shaderPathFragment, [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { particleShaderProgram.addShader(shaderStage, path); }); @@ -147,13 +163,13 @@ int main(int argc, const char **argv) { particleBuffer.fill(particleSystem.getParticles()); vkcv::DescriptorWrites setWrites; - setWrites.uniformBufferWrites = {vkcv::UniformBufferDescriptorWrite(0,color.getHandle()), - vkcv::UniformBufferDescriptorWrite(1,position.getHandle())}; - setWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(2,particleBuffer.getHandle())}; + setWrites.uniformBufferWrites = {vkcv::BufferDescriptorWrite(0,color.getHandle()), + vkcv::BufferDescriptorWrite(1,position.getHandle())}; + setWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(2,particleBuffer.getHandle())}; core.writeDescriptorSet(descriptorSet, setWrites); vkcv::DescriptorWrites computeWrites; - computeWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0,particleBuffer.getHandle())}; + computeWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0,particleBuffer.getHandle())}; core.writeDescriptorSet(computeDescriptorSet, computeWrites); if (!particlePipeline || !computePipeline) @@ -167,38 +183,16 @@ int main(int argc, const char **argv) { const vkcv::Mesh renderMesh({vertexBufferBindings}, particleIndexBuffer.getVulkanHandle(), particleIndexBuffer.getCount()); vkcv::DescriptorSetUsage descriptorUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle); - //vkcv::DrawcallInfo drawcalls(renderMesh, {vkcv::DescriptorSetUsage(0, core.getDescriptorSet(descriptorSet).vulkanHandle)}); - glm::vec2 pos = glm::vec2(0.f); - glm::vec3 spawnPosition = glm::vec3(0.f); - glm::vec4 tempPosition = glm::vec4(0.f); + auto pos = glm::vec2(0.f); + auto spawnPosition = glm::vec3(0.f); window.e_mouseMove.add([&](double offsetX, double offsetY) { pos = glm::vec2(static_cast<float>(offsetX), static_cast<float>(offsetY)); -// std::cout << offsetX << " , " << offsetY << std::endl; - // borders are assumed to be 0.5 - //pos = glm::vec2((pos.x -0.5f * static_cast<float>(window.getWidth()))/static_cast<float>(window.getWidth()), (pos.y -0.5f * static_cast<float>(window.getHeight()))/static_cast<float>(window.getHeight())); - //borders are assumed to be 1 pos.x = (-2 * pos.x + static_cast<float>(window.getWidth())) / static_cast<float>(window.getWidth()); pos.y = (-2 * pos.y + static_cast<float>(window.getHeight())) / static_cast<float>(window.getHeight()); - glm::vec4 row1 = glm::row(cameraManager.getCamera(0).getView(), 0); - glm::vec4 row2 = glm::row(cameraManager.getCamera(0).getView(), 1); - glm::vec4 row3 = glm::row(cameraManager.getCamera(0).getView(), 2); - glm::vec4 camera_pos = glm::column(cameraManager.getCamera(0).getView(), 3); -// std::cout << "row1: " << row1.x << ", " << row1.y << ", " << row1.z << std::endl; -// std::cout << "row2: " << row2.x << ", " << row2.y << ", " << row2.z << std::endl; -// std::cout << "row3: " << row3.x << ", " << row3.y << ", " << row3.z << std::endl; -// std::cout << "camerapos: " << camera_pos.x << ", " << camera_pos.y << ", " << camera_pos.z << std::endl; -// std::cout << "camerapos: " << camera_pos.x << ", " << camera_pos.y << ", " << camera_pos.z << std::endl; - //glm::vec4 view_axis = glm::row(cameraManager.getCamera().getView(), 2); - // std::cout << "view_axis: " << view_axis.x << ", " << view_axis.y << ", " << view_axis.z << std::endl; - //std::cout << "Front: " << cameraManager.getCamera().getFront().x << ", " << cameraManager.getCamera().getFront().z << ", " << cameraManager.getCamera().getFront().z << std::endl; - glm::mat4 viewmat = cameraManager.getCamera(0).getView(); spawnPosition = glm::vec3(pos.x, pos.y, 0.f); - tempPosition = glm::vec4(spawnPosition, 1.0f); - spawnPosition = glm::vec3(tempPosition.x, tempPosition.y, tempPosition.z); particleSystem.setRespawnPos(glm::vec3(-spawnPosition.x, spawnPosition.y, spawnPosition.z)); -// std::cout << "respawn pos: " << spawnPosition.x << ", " << spawnPosition.y << ", " << spawnPosition.z << std::endl; }); std::vector<glm::mat4> modelMatrices; @@ -242,7 +236,7 @@ int main(int argc, const char **argv) { std::uniform_real_distribution<float> rdm = std::uniform_real_distribution<float>(0.95f, 1.05f); std::default_random_engine rdmEngine; while (window.isWindowOpen()) { - window.pollEvents(); + vkcv::Window::pollEvents(); uint32_t swapchainWidth, swapchainHeight; if (!core.beginFrame(swapchainWidth, swapchainHeight)) { @@ -255,35 +249,42 @@ int main(int argc, const char **argv) { auto end = std::chrono::system_clock::now(); float deltatime = 0.000001 * static_cast<float>( std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() ); start = end; -// particleSystem.updateParticles(deltatime); cameraManager.update(deltatime); // split view and projection to allow for easy billboarding in shader - glm::mat4 renderingMatrices[2]; - renderingMatrices[0] = cameraManager.getActiveCamera().getView(); - renderingMatrices[1] = cameraManager.getActiveCamera().getProjection(); + struct { + glm::mat4 view; + glm::mat4 projection; + } renderingMatrices; + + renderingMatrices.view = cameraManager.getActiveCamera().getView(); + renderingMatrices.projection = cameraManager.getActiveCamera().getProjection(); auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); float random = rdm(rdmEngine); glm::vec2 pushData = glm::vec2(deltatime, random); - vkcv::PushConstantData pushConstantDataCompute( &pushData, sizeof(glm::vec2)); + vkcv::PushConstants pushConstantsCompute (sizeof(glm::vec2)); + pushConstantsCompute.appendDrawcall(pushData); + uint32_t computeDispatchCount[3] = {static_cast<uint32_t> (std::ceil(particleSystem.getParticles().size()/256.f)),1,1}; core.recordComputeDispatchToCmdStream(cmdStream, computePipeline, computeDispatchCount, {vkcv::DescriptorSetUsage(0,core.getDescriptorSet(computeDescriptorSet).vulkanHandle)}, - pushConstantDataCompute); + pushConstantsCompute); core.recordBufferMemoryBarrier(cmdStream, particleBuffer.getHandle()); - vkcv::PushConstantData pushConstantDataDraw((void *) &renderingMatrices[0], 2 * sizeof(glm::mat4)); + vkcv::PushConstants pushConstantsDraw (sizeof(renderingMatrices)); + pushConstantsDraw.appendDrawcall(renderingMatrices); + core.recordDrawcallsToCmdStream( cmdStream, particlePass, particlePipeline, - pushConstantDataDraw, + pushConstantsDraw, {drawcalls}, { colorBuffer }); @@ -309,7 +310,7 @@ int main(int argc, const char **argv) { tonemappingPipe, tonemappingDispatchCount, {vkcv::DescriptorSetUsage(0, core.getDescriptorSet(tonemappingDescriptor).vulkanHandle) }, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); core.prepareSwapchainImageForPresent(cmdStream); core.submitCommandStream(cmdStream); diff --git a/projects/voxelization/CMakeLists.txt b/projects/voxelization/CMakeLists.txt index c962409f2e14994f0c38b923de7b9b1a4d198cab..d2f533b0f9c7313ddcc6046fb29378c3a507d1fe 100644 --- a/projects/voxelization/CMakeLists.txt +++ b/projects/voxelization/CMakeLists.txt @@ -30,7 +30,7 @@ if(MSVC) endif() # including headers of dependencies and the VkCV framework -target_include_directories(voxelization SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include}) +target_include_directories(voxelization SYSTEM BEFORE PRIVATE ${vkcv_include} ${vkcv_includes} ${vkcv_asset_loader_include} ${vkcv_camera_include} ${vkcv_shader_compiler_include} ${vkcv_gui_include} ${vkcv_upscaling_include}) # linking with libraries from all dependencies and the VkCV framework -target_link_libraries(voxelization vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler vkcv_gui) +target_link_libraries(voxelization vkcv ${vkcv_libraries} vkcv_asset_loader ${vkcv_asset_loader_libraries} vkcv_camera vkcv_shader_compiler vkcv_gui vkcv_upscaling) diff --git a/projects/voxelization/resources/shaders/postEffects.comp b/projects/voxelization/resources/shaders/postEffects.comp new file mode 100644 index 0000000000000000000000000000000000000000..c0f9fe1a764bcdabac5501e2f82692c6f476e9e6 --- /dev/null +++ b/projects/voxelization/resources/shaders/postEffects.comp @@ -0,0 +1,149 @@ +#version 440 +#extension GL_GOOGLE_include_directive : enable + +#include "luma.inc" + +layout(set=0, binding=0) uniform texture2D inTexture; +layout(set=0, binding=1) uniform sampler textureSampler; +layout(set=0, binding=2, rgba8) uniform image2D outImage; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout( push_constant ) uniform constants{ + float time; +}; + +// from: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/ +vec3 ACESFilm(vec3 x) +{ + float a = 2.51f; + float b = 0.03f; + float c = 2.43f; + float d = 0.59f; + float e = 0.14f; + return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0, 1); +} + +// From Dave Hoskins: https://www.shadertoy.com/view/4djSRW. +float hash(vec3 p3){ + p3 = fract(p3 * 0.1031); + p3 += dot(p3,p3.yzx + 19.19); + return fract((p3.x + p3.y) * p3.z); +} + +// From iq: https://www.shadertoy.com/view/4sfGzS. +float noise(vec3 x){ + vec3 i = floor(x); + vec3 f = fract(x); + f = f*f*(3.0-2.0*f); + return mix(mix(mix(hash(i+vec3(0, 0, 0)), + hash(i+vec3(1, 0, 0)),f.x), + mix(hash(i+vec3(0, 1, 0)), + hash(i+vec3(1, 1, 0)),f.x),f.y), + mix(mix(hash(i+vec3(0, 0, 1)), + hash(i+vec3(1, 0, 1)),f.x), + mix(hash(i+vec3(0, 1, 1)), + hash(i+vec3(1, 1, 1)),f.x),f.y),f.z); +} + +// From: https://www.shadertoy.com/view/3sGSWVF +// Slightly high-passed continuous value-noise. +float grainSource(vec3 x, float strength, float pitch){ + float center = noise(x); + float v1 = center - noise(vec3( 1, 0, 0)/pitch + x) + 0.5; + float v2 = center - noise(vec3( 0, 1, 0)/pitch + x) + 0.5; + float v3 = center - noise(vec3(-1, 0, 0)/pitch + x) + 0.5; + float v4 = center - noise(vec3( 0,-1, 0)/pitch + x) + 0.5; + + float total = (v1 + v2 + v3 + v4) / 4.0; + return mix(1, 0.5 + total, strength); +} + +vec3 applyGrain(ivec2 uv, vec3 c){ + float grainLift = 0.6; + float grainStrength = 0.4; + float grainTimeFactor = 0.1; + + float timeColorOffset = 1.2; + vec3 grain = vec3( + grainSource(vec3(uv, floor(grainTimeFactor*time)), grainStrength, grainLift), + grainSource(vec3(uv, floor(grainTimeFactor*time + timeColorOffset)), grainStrength, grainLift), + grainSource(vec3(uv, floor(grainTimeFactor*time - timeColorOffset)), grainStrength, grainLift)); + + return c * grain; +} + +vec2 computeDistortedUV(vec2 uv, float aspectRatio){ + uv = uv * 2 - 1; + float r2 = dot(uv, uv); + float k1 = 0.02f; + + float maxR2 = dot(vec2(1), vec2(1)); + float maxFactor = maxR2 * k1; + + // correction only needed for pincushion distortion + maxFactor = min(maxFactor, 0); + + uv /= 1 + r2*k1; + + // correction to avoid going out of [-1, 1] range when using barrel distortion + uv *= 1 + maxFactor; + + return uv * 0.5 + 0.5; +} + +float computeLocalContrast(vec2 uv){ + float lumaMin = 100; + float lumaMax = 0; + + vec2 pixelSize = vec2(1) / textureSize(sampler2D(inTexture, textureSampler), 0); + + for(int x = -1; x <= 1; x++){ + for(int y = -1; y <= 1; y++){ + vec3 c = texture(sampler2D(inTexture, textureSampler), uv + vec2(x, y) * pixelSize).rgb; + float luma = computeLuma(c); + lumaMin = min(lumaMin, luma); + lumaMax = max(lumaMax, luma); + } + } + + return lumaMax - lumaMin; +} + +vec3 computeChromaticAberrationScale(vec2 uv){ + float localContrast = computeLocalContrast(uv); + vec3 colorScales = vec3(-1, 0, 1); + float aberrationScale = 0.004; + vec3 maxScaleFactors = colorScales * aberrationScale; + float factor = clamp(localContrast, 0, 1); + return mix(vec3(0), maxScaleFactors, factor); +} + +vec3 sampleColorChromaticAberration(vec2 uv){ + vec2 toCenter = (vec2(0.5) - uv); + + vec3 scaleFactors = computeChromaticAberrationScale(uv); + + float r = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.r).r; + float g = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.g).g; + float b = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.b).b; + return vec3(r, g, b); +} + +void main(){ + + if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage)))){ + return; + } + ivec2 textureRes = textureSize(sampler2D(inTexture, textureSampler), 0); + ivec2 coord = ivec2(gl_GlobalInvocationID.xy); + vec2 uv = vec2(coord) / textureRes; + float aspectRatio = float(textureRes.x) / textureRes.y; + uv = computeDistortedUV(uv, aspectRatio); + + vec3 tonemapped = sampleColorChromaticAberration(uv); + tonemapped = applyGrain(coord, tonemapped); + + vec3 gammaCorrected = pow(tonemapped, vec3(1.f / 2.2f)); + imageStore(outImage, coord, vec4(gammaCorrected, 0.f)); +} \ No newline at end of file diff --git a/projects/voxelization/resources/shaders/tonemapping.comp b/projects/voxelization/resources/shaders/tonemapping.comp index 8fa07d39ebb56eab857cdccb755a6558f5ae1ec3..ffadc9a71e207f97fec9a8815aa1c61bc709c369 100644 --- a/projects/voxelization/resources/shaders/tonemapping.comp +++ b/projects/voxelization/resources/shaders/tonemapping.comp @@ -1,18 +1,12 @@ #version 440 #extension GL_GOOGLE_include_directive : enable -#include "luma.inc" - layout(set=0, binding=0) uniform texture2D inTexture; layout(set=0, binding=1) uniform sampler textureSampler; layout(set=0, binding=2, rgba8) uniform image2D outImage; layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; -layout( push_constant ) uniform constants{ - float time; -}; - // from: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/ vec3 ACESFilm(vec3 x) { @@ -24,112 +18,6 @@ vec3 ACESFilm(vec3 x) return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0, 1); } -// From Dave Hoskins: https://www.shadertoy.com/view/4djSRW. -float hash(vec3 p3){ - p3 = fract(p3 * 0.1031); - p3 += dot(p3,p3.yzx + 19.19); - return fract((p3.x + p3.y) * p3.z); -} - -// From iq: https://www.shadertoy.com/view/4sfGzS. -float noise(vec3 x){ - vec3 i = floor(x); - vec3 f = fract(x); - f = f*f*(3.0-2.0*f); - return mix(mix(mix(hash(i+vec3(0, 0, 0)), - hash(i+vec3(1, 0, 0)),f.x), - mix(hash(i+vec3(0, 1, 0)), - hash(i+vec3(1, 1, 0)),f.x),f.y), - mix(mix(hash(i+vec3(0, 0, 1)), - hash(i+vec3(1, 0, 1)),f.x), - mix(hash(i+vec3(0, 1, 1)), - hash(i+vec3(1, 1, 1)),f.x),f.y),f.z); -} - -// From: https://www.shadertoy.com/view/3sGSWVF -// Slightly high-passed continuous value-noise. -float grainSource(vec3 x, float strength, float pitch){ - float center = noise(x); - float v1 = center - noise(vec3( 1, 0, 0)/pitch + x) + 0.5; - float v2 = center - noise(vec3( 0, 1, 0)/pitch + x) + 0.5; - float v3 = center - noise(vec3(-1, 0, 0)/pitch + x) + 0.5; - float v4 = center - noise(vec3( 0,-1, 0)/pitch + x) + 0.5; - - float total = (v1 + v2 + v3 + v4) / 4.0; - return mix(1, 0.5 + total, strength); -} - -vec3 applyGrain(ivec2 uv, vec3 c){ - float grainLift = 0.6; - float grainStrength = 0.4; - float grainTimeFactor = 0.1; - - float timeColorOffset = 1.2; - vec3 grain = vec3( - grainSource(vec3(uv, floor(grainTimeFactor*time)), grainStrength, grainLift), - grainSource(vec3(uv, floor(grainTimeFactor*time + timeColorOffset)), grainStrength, grainLift), - grainSource(vec3(uv, floor(grainTimeFactor*time - timeColorOffset)), grainStrength, grainLift)); - - return c * grain; -} - -vec2 computeDistortedUV(vec2 uv, float aspectRatio){ - uv = uv * 2 - 1; - float r2 = dot(uv, uv); - float k1 = 0.02f; - - float maxR2 = dot(vec2(1), vec2(1)); - float maxFactor = maxR2 * k1; - - // correction only needed for pincushion distortion - maxFactor = min(maxFactor, 0); - - uv /= 1 + r2*k1; - - // correction to avoid going out of [-1, 1] range when using barrel distortion - uv *= 1 + maxFactor; - - return uv * 0.5 + 0.5; -} - -float computeLocalContrast(vec2 uv){ - float lumaMin = 100; - float lumaMax = 0; - - vec2 pixelSize = vec2(1) / textureSize(sampler2D(inTexture, textureSampler), 0); - - for(int x = -1; x <= 1; x++){ - for(int y = -1; y <= 1; y++){ - vec3 c = texture(sampler2D(inTexture, textureSampler), uv + vec2(x, y) * pixelSize).rgb; - float luma = computeLuma(c); - lumaMin = min(lumaMin, luma); - lumaMax = max(lumaMax, luma); - } - } - - return lumaMax - lumaMin; -} - -vec3 computeChromaticAberrationScale(vec2 uv){ - float localContrast = computeLocalContrast(uv); - vec3 colorScales = vec3(-1, 0, 1); - float aberrationScale = 0.004; - vec3 maxScaleFactors = colorScales * aberrationScale; - float factor = clamp(localContrast, 0, 1); - return mix(vec3(0), maxScaleFactors, factor); -} - -vec3 sampleColorChromaticAberration(vec2 uv){ - vec2 toCenter = (vec2(0.5) - uv); - - vec3 scaleFactors = computeChromaticAberrationScale(uv); - - float r = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.r).r; - float g = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.g).g; - float b = texture(sampler2D(inTexture, textureSampler), uv + toCenter * scaleFactors.b).b; - return vec3(r, g, b); -} - void main(){ if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage)))){ @@ -138,12 +26,9 @@ void main(){ ivec2 textureRes = textureSize(sampler2D(inTexture, textureSampler), 0); ivec2 coord = ivec2(gl_GlobalInvocationID.xy); vec2 uv = vec2(coord) / textureRes; - float aspectRatio = float(textureRes.x) / textureRes.y; - uv = computeDistortedUV(uv, aspectRatio); - vec3 linearColor = sampleColorChromaticAberration(uv); + + vec3 linearColor = texture(sampler2D(inTexture, textureSampler), uv).rgb; vec3 tonemapped = ACESFilm(linearColor); - tonemapped = applyGrain(coord, tonemapped); - - vec3 gammaCorrected = pow(tonemapped, vec3(1.f / 2.2f)); - imageStore(outImage, coord, vec4(gammaCorrected, 0.f)); + + imageStore(outImage, coord, vec4(tonemapped, 0.f)); } \ No newline at end of file diff --git a/projects/voxelization/src/BloomAndFlares.cpp b/projects/voxelization/src/BloomAndFlares.cpp index fac57735a6544c197f880f78e1f512382607d048..6cb02e9035daf7abebc047d26137d0ba973bb4f1 100644 --- a/projects/voxelization/src/BloomAndFlares.cpp +++ b/projects/voxelization/src/BloomAndFlares.cpp @@ -128,7 +128,7 @@ void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStre m_DownsamplePipe, initialDispatchCount, {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[0]).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); // downsample dispatches of blur buffer's mip maps float mipDispatchCountX = dispatchCountX; @@ -163,7 +163,7 @@ void BloomAndFlares::execDownsamplePipe(const vkcv::CommandStreamHandle &cmdStre m_DownsamplePipe, mipDispatchCount, {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_DownsampleDescSets[mipLevel]).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); // image barrier between mips p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle()); @@ -208,7 +208,7 @@ void BloomAndFlares::execUpsamplePipe(const vkcv::CommandStreamHandle &cmdStream m_UpsamplePipe, upsampleDispatchCount, {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_UpsampleDescSets[mipLevel]).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0) + vkcv::PushConstants(0) ); // image barrier between mips p_Core->recordImageMemoryBarrier(cmdStream, m_Blur.getHandle()); @@ -243,7 +243,7 @@ void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStr m_LensFlarePipe, lensFeatureDispatchCount, {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_LensFlareDescSet).vulkanHandle)}, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); // upsample dispatch p_Core->prepareImageForStorage(cmdStream, m_LensFeatures.getHandle()); @@ -276,7 +276,7 @@ void BloomAndFlares::execLensFeaturePipe(const vkcv::CommandStreamHandle &cmdStr m_UpsamplePipe, upsampleDispatchCount, { vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_UpsampleLensFlareDescSets[i]).vulkanHandle) }, - vkcv::PushConstantData(nullptr, 0) + vkcv::PushConstants(0) ); // image barrier between mips p_Core->recordImageMemoryBarrier(cmdStream, m_LensFeatures.getHandle()); @@ -309,6 +309,9 @@ void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStrea static_cast<uint32_t>(glm::ceil(dispatchCountY)), 1 }; + + vkcv::PushConstants pushConstants (sizeof(cameraForward)); + pushConstants.appendDrawcall(cameraForward); // bloom composite dispatch p_Core->recordComputeDispatchToCmdStream( @@ -316,7 +319,7 @@ void BloomAndFlares::execCompositePipe(const vkcv::CommandStreamHandle &cmdStrea m_CompositePipe, compositeDispatchCount, {vkcv::DescriptorSetUsage(0, p_Core->getDescriptorSet(m_CompositeDescSet).vulkanHandle)}, - vkcv::PushConstantData((void*)&cameraForward, sizeof(cameraForward))); + pushConstants); } void BloomAndFlares::execWholePipeline(const vkcv::CommandStreamHandle &cmdStream, const vkcv::ImageHandle &colorAttachment, diff --git a/projects/voxelization/src/ShadowMapping.cpp b/projects/voxelization/src/ShadowMapping.cpp index a330394b7bd7ff2a4b8c347bd79e676dbc70f846..32dd5457541f8f09f4d2711ea831e3c78de2303a 100644 --- a/projects/voxelization/src/ShadowMapping.cpp +++ b/projects/voxelization/src/ShadowMapping.cpp @@ -248,12 +248,13 @@ void ShadowMapping::recordShadowMapRendering( voxelVolumeOffset, voxelVolumeExtent); m_lightInfoBuffer.fill({ lightInfo }); - - std::vector<glm::mat4> mvpLight; + + vkcv::PushConstants shadowPushConstants (sizeof(glm::mat4)); + for (const auto& m : modelMatrices) { - mvpLight.push_back(lightInfo.lightMatrix * m); + shadowPushConstants.appendDrawcall(lightInfo.lightMatrix * m); } - const vkcv::PushConstantData shadowPushConstantData((void*)mvpLight.data(), sizeof(glm::mat4)); + std::vector<vkcv::DrawcallInfo> drawcalls; for (const auto& mesh : meshes) { @@ -264,7 +265,7 @@ void ShadowMapping::recordShadowMapRendering( cmdStream, m_shadowMapPass, m_shadowMapPipe, - shadowPushConstantData, + shadowPushConstants, drawcalls, { m_shadowMapDepth.getHandle() }); m_corePtr->prepareImageForSampling(cmdStream, m_shadowMapDepth.getHandle()); @@ -276,6 +277,9 @@ void ShadowMapping::recordShadowMapRendering( dispatchCount[2] = 1; const uint32_t msaaSampleCount = msaaToSampleCount(msaa); + + vkcv::PushConstants msaaPushConstants (sizeof(msaaSampleCount)); + msaaPushConstants.appendDrawcall(msaaSampleCount); m_corePtr->prepareImageForStorage(cmdStream, m_shadowMap.getHandle()); m_corePtr->recordComputeDispatchToCmdStream( @@ -283,7 +287,7 @@ void ShadowMapping::recordShadowMapRendering( m_depthToMomentsPipe, dispatchCount, { vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_depthToMomentsDescriptorSet).vulkanHandle) }, - vkcv::PushConstantData((void*)&msaaSampleCount, sizeof(msaaSampleCount))); + msaaPushConstants); m_corePtr->prepareImageForSampling(cmdStream, m_shadowMap.getHandle()); // blur X @@ -293,7 +297,7 @@ void ShadowMapping::recordShadowMapRendering( m_shadowBlurXPipe, dispatchCount, { vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_shadowBlurXDescriptorSet).vulkanHandle) }, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); m_corePtr->prepareImageForSampling(cmdStream, m_shadowMapIntermediate.getHandle()); // blur Y @@ -303,7 +307,7 @@ void ShadowMapping::recordShadowMapRendering( m_shadowBlurYPipe, dispatchCount, { vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_shadowBlurYDescriptorSet).vulkanHandle) }, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); m_shadowMap.recordMipChainGeneration(cmdStream); m_corePtr->prepareImageForSampling(cmdStream, m_shadowMap.getHandle()); } diff --git a/projects/voxelization/src/Voxelization.cpp b/projects/voxelization/src/Voxelization.cpp index c117b4b9e6b896fbf51aae83343f30281061be9f..f7e03709c6423ef0e3c43251afb28e887b9be61f 100644 --- a/projects/voxelization/src/Voxelization.cpp +++ b/projects/voxelization/src/Voxelization.cpp @@ -119,10 +119,10 @@ Voxelization::Voxelization( m_voxelizationPipe = m_corePtr->createGraphicsPipeline(voxelizationPipeConfig); vkcv::DescriptorWrites voxelizationDescriptorWrites; - voxelizationDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) }; + voxelizationDescriptorWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0, m_voxelBuffer.getHandle()) }; voxelizationDescriptorWrites.uniformBufferWrites = { - vkcv::UniformBufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()), - vkcv::UniformBufferDescriptorWrite(3, lightInfoBuffer) + vkcv::BufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()), + vkcv::BufferDescriptorWrite(3, lightInfoBuffer) }; voxelizationDescriptorWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(4, shadowMap) }; voxelizationDescriptorWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(5, shadowSampler) }; @@ -180,7 +180,7 @@ Voxelization::Voxelization( { m_corePtr->getDescriptorSet(m_voxelResetDescriptorSet).layout }); vkcv::DescriptorWrites resetVoxelWrites; - resetVoxelWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) }; + resetVoxelWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0, m_voxelBuffer.getHandle()) }; m_corePtr->writeDescriptorSet(m_voxelResetDescriptorSet, resetVoxelWrites); // buffer to image @@ -192,7 +192,7 @@ Voxelization::Voxelization( { m_corePtr->getDescriptorSet(m_bufferToImageDescriptorSet).layout }); vkcv::DescriptorWrites bufferToImageDescriptorWrites; - bufferToImageDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) }; + bufferToImageDescriptorWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0, m_voxelBuffer.getHandle()) }; bufferToImageDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(1, m_voxelImageIntermediate.getHandle()) }; m_corePtr->writeDescriptorSet(m_bufferToImageDescriptorSet, bufferToImageDescriptorWrites); @@ -205,11 +205,11 @@ Voxelization::Voxelization( { m_corePtr->getDescriptorSet(m_secondaryBounceDescriptorSet).layout }); vkcv::DescriptorWrites secondaryBounceDescriptorWrites; - secondaryBounceDescriptorWrites.storageBufferWrites = { vkcv::StorageBufferDescriptorWrite(0, m_voxelBuffer.getHandle()) }; + secondaryBounceDescriptorWrites.storageBufferWrites = { vkcv::BufferDescriptorWrite(0, m_voxelBuffer.getHandle()) }; secondaryBounceDescriptorWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(1, m_voxelImageIntermediate.getHandle()) }; secondaryBounceDescriptorWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(2, voxelSampler) }; secondaryBounceDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(3, m_voxelImage.getHandle()) }; - secondaryBounceDescriptorWrites.uniformBufferWrites = { vkcv::UniformBufferDescriptorWrite(4, m_voxelInfoBuffer.getHandle()) }; + secondaryBounceDescriptorWrites.uniformBufferWrites = { vkcv::BufferDescriptorWrite(4, m_voxelInfoBuffer.getHandle()) }; m_corePtr->writeDescriptorSet(m_secondaryBounceDescriptorSet, secondaryBounceDescriptorWrites); } @@ -232,34 +232,36 @@ void Voxelization::voxelizeMeshes( const glm::mat4 voxelizationView = glm::translate(glm::mat4(1.f), -m_voxelInfo.offset); const glm::mat4 voxelizationViewProjection = voxelizationProjection * voxelizationView; - - std::vector<std::array<glm::mat4, 2>> voxelizationMatrices; + + vkcv::PushConstants voxelizationPushConstants (2 * sizeof(glm::mat4)); + for (const auto& m : modelMatrices) { - voxelizationMatrices.push_back({ voxelizationViewProjection * m, m }); + voxelizationPushConstants.appendDrawcall(std::array<glm::mat4, 2>{ voxelizationViewProjection * m, m }); } - const vkcv::PushConstantData voxelizationPushConstantData((void*)voxelizationMatrices.data(), 2 * sizeof(glm::mat4)); - // reset voxels const uint32_t resetVoxelGroupSize = 64; uint32_t resetVoxelDispatchCount[3]; resetVoxelDispatchCount[0] = glm::ceil(voxelCount / float(resetVoxelGroupSize)); resetVoxelDispatchCount[1] = 1; resetVoxelDispatchCount[2] = 1; + + vkcv::PushConstants voxelCountPushConstants (sizeof(voxelCount)); + voxelCountPushConstants.appendDrawcall(voxelCount); m_corePtr->recordComputeDispatchToCmdStream( cmdStream, m_voxelResetPipe, resetVoxelDispatchCount, { vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_voxelResetDescriptorSet).vulkanHandle) }, - vkcv::PushConstantData(&voxelCount, sizeof(voxelCount))); + voxelCountPushConstants); m_corePtr->recordBufferMemoryBarrier(cmdStream, m_voxelBuffer.getHandle()); // voxelization std::vector<vkcv::DrawcallInfo> drawcalls; - for (int i = 0; i < meshes.size(); i++) { + for (size_t i = 0; i < meshes.size(); i++) { drawcalls.push_back(vkcv::DrawcallInfo( - meshes[i], + meshes[i], { vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_voxelizationDescriptorSet).vulkanHandle), vkcv::DescriptorSetUsage(1, m_corePtr->getDescriptorSet(perMeshDescriptorSets[i]).vulkanHandle) @@ -271,7 +273,7 @@ void Voxelization::voxelizeMeshes( cmdStream, m_voxelizationPass, m_voxelizationPipe, - voxelizationPushConstantData, + voxelizationPushConstants, drawcalls, { m_dummyRenderTarget.getHandle() }); @@ -287,7 +289,7 @@ void Voxelization::voxelizeMeshes( m_bufferToImagePipe, bufferToImageDispatchCount, { vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_bufferToImageDescriptorSet).vulkanHandle) }, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImageIntermediate.getHandle()); @@ -303,7 +305,7 @@ void Voxelization::voxelizeMeshes( m_secondaryBouncePipe, bufferToImageDispatchCount, { vkcv::DescriptorSetUsage(0, m_corePtr->getDescriptorSet(m_secondaryBounceDescriptorSet).vulkanHandle) }, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); m_voxelImage.recordMipChainGeneration(cmdStream); m_corePtr->recordImageMemoryBarrier(cmdStream, m_voxelImage.getHandle()); @@ -319,7 +321,8 @@ void Voxelization::renderVoxelVisualisation( const std::vector<vkcv::ImageHandle>& renderTargets, uint32_t mipLevel) { - const vkcv::PushConstantData voxelVisualisationPushConstantData((void*)&viewProjectin, sizeof(glm::mat4)); + vkcv::PushConstants voxelVisualisationPushConstants (sizeof(glm::mat4)); + voxelVisualisationPushConstants.appendDrawcall(viewProjectin); mipLevel = std::clamp(mipLevel, (uint32_t)0, m_voxelImage.getMipCount()-1); @@ -328,7 +331,7 @@ void Voxelization::renderVoxelVisualisation( voxelVisualisationDescriptorWrite.storageImageWrites = { vkcv::StorageImageDescriptorWrite(0, m_voxelImage.getHandle(), mipLevel) }; voxelVisualisationDescriptorWrite.uniformBufferWrites = - { vkcv::UniformBufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()) }; + { vkcv::BufferDescriptorWrite(1, m_voxelInfoBuffer.getHandle()) }; m_corePtr->writeDescriptorSet(m_visualisationDescriptorSet, voxelVisualisationDescriptorWrite); uint32_t drawVoxelCount = voxelCount / exp2(mipLevel); @@ -342,7 +345,7 @@ void Voxelization::renderVoxelVisualisation( cmdStream, m_visualisationPass, m_visualisationPipe, - voxelVisualisationPushConstantData, + voxelVisualisationPushConstants, { drawcall }, renderTargets); } diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp index edc50c554b6c73bd2f06914eba6dd7adf9e43483..e7f9caa493714d30f13f64c292f1b6e51e5170b1 100644 --- a/projects/voxelization/src/main.cpp +++ b/projects/voxelization/src/main.cpp @@ -11,6 +11,8 @@ #include "vkcv/gui/GUI.hpp" #include "ShadowMapping.hpp" #include "BloomAndFlares.hpp" +#include <vkcv/upscaling/FSRUpscaling.hpp> +#include <vkcv/upscaling/BilinearUpscaling.hpp> int main(int argc, const char** argv) { const char* applicationName = "Voxelization"; @@ -27,11 +29,11 @@ int main(int argc, const char** argv) { true ); - bool isFullscreen = false; - int windowedWidthBackup = windowWidth; - int windowedHeightBackup = windowHeight; - int windowedPosXBackup; - int windowedPosYBackup; + bool isFullscreen = false; + uint32_t windowedWidthBackup = windowWidth; + uint32_t windowedHeightBackup = windowHeight; + int windowedPosXBackup; + int windowedPosYBackup; glfwGetWindowPos(window.getWindow(), &windowedPosXBackup, &windowedPosYBackup); window.e_key.add([&](int key, int scancode, int action, int mods) { @@ -85,7 +87,7 @@ int main(int argc, const char** argv) { VK_MAKE_VERSION(0, 0, 1), { vk::QueueFlagBits::eTransfer,vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute }, {}, - { "VK_KHR_swapchain" } + { "VK_KHR_swapchain", "VK_KHR_shader_float16_int8", "VK_KHR_16bit_storage" } ); vkcv::asset::Scene mesh; @@ -111,7 +113,7 @@ int main(int argc, const char** argv) { std::vector<std::vector<vkcv::VertexBufferBinding>> vertexBufferBindings; std::vector<vkcv::asset::VertexAttribute> vAttributes; - for (int i = 0; i < scene.vertexGroups.size(); i++) { + for (size_t i = 0; i < scene.vertexGroups.size(); i++) { vBuffers.push_back(scene.vertexGroups[i].vertexBuffer.data); iBuffers.push_back(scene.vertexGroups[i].indexBuffer.data); @@ -393,6 +395,9 @@ int main(int argc, const char** argv) { else { resolvedColorBuffer = colorBuffer; } + + vkcv::ImageHandle swapBuffer = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true).getHandle(); + vkcv::ImageHandle swapBuffer2 = core.createImage(colorBufferFormat, windowWidth, windowHeight, 1, false, true).getHandle(); const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle(); @@ -421,6 +426,18 @@ int main(int argc, const char** argv) { vkcv::PipelineHandle tonemappingPipeline = core.createComputePipeline( tonemappingProgram, { core.getDescriptorSet(tonemappingDescriptorSet).layout }); + + // tonemapping compute shader + vkcv::ShaderProgram postEffectsProgram; + compiler.compile(vkcv::ShaderStage::COMPUTE, "resources/shaders/postEffects.comp", + [&](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) { + postEffectsProgram.addShader(shaderStage, path); + }); + vkcv::DescriptorSetHandle postEffectsDescriptorSet = core.createDescriptorSet( + postEffectsProgram.getReflectedDescriptors()[0]); + vkcv::PipelineHandle postEffectsPipeline = core.createComputePipeline( + postEffectsProgram, + { core.getDescriptorSet(postEffectsDescriptorSet).layout }); // resolve compute shader vkcv::ShaderProgram resolveProgram; @@ -438,7 +455,8 @@ int main(int argc, const char** argv) { vkcv::SamplerFilterType::NEAREST, vkcv::SamplerFilterType::NEAREST, vkcv::SamplerMipmapMode::NEAREST, - vkcv::SamplerAddressMode::CLAMP_TO_EDGE); + vkcv::SamplerAddressMode::CLAMP_TO_EDGE + ); // model matrices per mesh std::vector<glm::mat4> modelMatrices; @@ -452,14 +470,14 @@ int main(int argc, const char** argv) { // prepare meshes std::vector<vkcv::Mesh> meshes; - for (int i = 0; i < scene.vertexGroups.size(); i++) { + for (size_t i = 0; i < scene.vertexGroups.size(); i++) { vkcv::Mesh mesh(vertexBufferBindings[i], indexBuffers[i].getVulkanHandle(), scene.vertexGroups[i].numIndices); meshes.push_back(mesh); } std::vector<vkcv::DrawcallInfo> drawcalls; std::vector<vkcv::DrawcallInfo> prepassDrawcalls; - for (int i = 0; i < meshes.size(); i++) { + for (size_t i = 0; i < meshes.size(); i++) { drawcalls.push_back(vkcv::DrawcallInfo(meshes[i], { vkcv::DescriptorSetUsage(0, core.getDescriptorSet(forwardShadingDescriptorSet).vulkanHandle), @@ -473,7 +491,8 @@ int main(int argc, const char** argv) { vkcv::SamplerFilterType::LINEAR, vkcv::SamplerFilterType::LINEAR, vkcv::SamplerMipmapMode::LINEAR, - vkcv::SamplerAddressMode::CLAMP_TO_EDGE); + vkcv::SamplerAddressMode::CLAMP_TO_EDGE + ); ShadowMapping shadowMapping(&core, vertexLayout); @@ -511,10 +530,10 @@ int main(int argc, const char** argv) { // write forward pass descriptor set vkcv::DescriptorWrites forwardDescriptorWrites; forwardDescriptorWrites.uniformBufferWrites = { - vkcv::UniformBufferDescriptorWrite(0, shadowMapping.getLightInfoBuffer()), - vkcv::UniformBufferDescriptorWrite(3, cameraPosBuffer.getHandle()), - vkcv::UniformBufferDescriptorWrite(6, voxelization.getVoxelInfoBufferHandle()), - vkcv::UniformBufferDescriptorWrite(7, volumetricSettingsBuffer.getHandle())}; + vkcv::BufferDescriptorWrite(0, shadowMapping.getLightInfoBuffer()), + vkcv::BufferDescriptorWrite(3, cameraPosBuffer.getHandle()), + vkcv::BufferDescriptorWrite(6, voxelization.getVoxelInfoBufferHandle()), + vkcv::BufferDescriptorWrite(7, volumetricSettingsBuffer.getHandle())}; forwardDescriptorWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(1, shadowMapping.getShadowMap()), vkcv::SampledImageDescriptorWrite(4, voxelization.getVoxelImageHandle()) }; @@ -523,6 +542,27 @@ int main(int argc, const char** argv) { vkcv::SamplerDescriptorWrite(5, voxelSampler) }; core.writeDescriptorSet(forwardShadingDescriptorSet, forwardDescriptorWrites); + vkcv::upscaling::FSRUpscaling upscaling (core); + uint32_t fsrWidth = windowWidth, fsrHeight = windowHeight; + + vkcv::upscaling::FSRQualityMode fsrMode = vkcv::upscaling::FSRQualityMode::NONE; + int fsrModeIndex = static_cast<int>(fsrMode); + + const std::vector<const char*> fsrModeNames = { + "None", + "Ultra Quality", + "Quality", + "Balanced", + "Performance" + }; + + bool fsrMipLoadBiasFlag = true; + bool fsrMipLoadBiasFlagBackup = fsrMipLoadBiasFlag; + + vkcv::upscaling::BilinearUpscaling upscaling1 (core); + + bool bilinearUpscaling = false; + vkcv::gui::GUI gui(core, window); glm::vec2 lightAnglesDegree = glm::vec2(90.f, 0.f); @@ -550,22 +590,72 @@ int main(int argc, const char** argv) { if (!core.beginFrame(swapchainWidth, swapchainHeight)) { continue; } - - if ((swapchainWidth != windowWidth) || ((swapchainHeight != windowHeight))) { - depthBuffer = core.createImage(depthBufferFormat, swapchainWidth, swapchainHeight, 1, false, false, false, msaa).getHandle(); - colorBuffer = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, false, colorBufferRequiresStorage, true, msaa).getHandle(); + + uint32_t width, height; + vkcv::upscaling::getFSRResolution( + fsrMode, + swapchainWidth, swapchainHeight, + width, height + ); + + if ((width != fsrWidth) || ((height != fsrHeight)) || (fsrMipLoadBiasFlagBackup != fsrMipLoadBiasFlag)) { + fsrWidth = width; + fsrHeight = height; + fsrMipLoadBiasFlagBackup = fsrMipLoadBiasFlag; + + colorSampler = core.createSampler( + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerFilterType::LINEAR, + vkcv::SamplerMipmapMode::LINEAR, + vkcv::SamplerAddressMode::REPEAT, + fsrMipLoadBiasFlag? vkcv::upscaling::getFSRLodBias(fsrMode) : 0.0f + ); + + for (size_t i = 0; i < scene.materials.size(); i++) { + vkcv::DescriptorWrites setWrites; + setWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(1, colorSampler), + }; + core.writeDescriptorSet(materialDescriptorSets[i], setWrites); + } + + depthBuffer = core.createImage( + depthBufferFormat, + fsrWidth, fsrHeight, 1, + false, false, false, + msaa + ).getHandle(); + + colorBuffer = core.createImage( + colorBufferFormat, + fsrWidth, fsrHeight, 1, + false, colorBufferRequiresStorage, true, + msaa + ).getHandle(); if (usingMsaa) { - resolvedColorBuffer = core.createImage(colorBufferFormat, swapchainWidth, swapchainHeight, 1, false, true, true).getHandle(); - } - else { + resolvedColorBuffer = core.createImage( + colorBufferFormat, + fsrWidth, fsrHeight, 1, + false, true, true + ).getHandle(); + } else { resolvedColorBuffer = colorBuffer; } - - windowWidth = swapchainWidth; - windowHeight = swapchainHeight; - - bloomFlares.updateImageDimensions(windowWidth, windowHeight); + + swapBuffer = core.createImage( + colorBufferFormat, + fsrWidth, fsrHeight, 1, + false, true + ).getHandle(); + + swapBuffer2 = core.createImage( + colorBufferFormat, + swapchainWidth, swapchainHeight, 1, + false, true + ).getHandle(); + + bloomFlares.updateImageDimensions(swapchainWidth, swapchainHeight); } auto end = std::chrono::system_clock::now(); @@ -575,9 +665,17 @@ int main(int argc, const char** argv) { vkcv::DescriptorWrites tonemappingDescriptorWrites; tonemappingDescriptorWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(0, resolvedColorBuffer) }; tonemappingDescriptorWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(1, colorSampler) }; - tonemappingDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(2, swapchainInput) }; + tonemappingDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(2, swapBuffer) }; core.writeDescriptorSet(tonemappingDescriptorSet, tonemappingDescriptorWrites); + + // update descriptor sets which use swapchain image + vkcv::DescriptorWrites postEffectsDescriptorWrites; + postEffectsDescriptorWrites.sampledImageWrites = { vkcv::SampledImageDescriptorWrite(0, swapBuffer2) }; + postEffectsDescriptorWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(1, colorSampler) }; + postEffectsDescriptorWrites.storageImageWrites = { vkcv::StorageImageDescriptorWrite(2, swapchainInput) }; + + core.writeDescriptorSet(postEffectsDescriptorSet, postEffectsDescriptorWrites); // update resolve descriptor, color images could be changed vkcv::DescriptorWrites resolveDescriptorWrites; @@ -618,29 +716,32 @@ int main(int argc, const char** argv) { // depth prepass const glm::mat4 viewProjectionCamera = cameraManager.getActiveCamera().getMVP(); - + + vkcv::PushConstants prepassPushConstants (sizeof(glm::mat4)); + std::vector<glm::mat4> prepassMatrices; for (const auto& m : modelMatrices) { - prepassMatrices.push_back(viewProjectionCamera * m); + prepassPushConstants.appendDrawcall(viewProjectionCamera * m); } - const vkcv::PushConstantData prepassPushConstantData((void*)prepassMatrices.data(), sizeof(glm::mat4)); + const std::vector<vkcv::ImageHandle> prepassRenderTargets = { depthBuffer }; core.recordDrawcallsToCmdStream( cmdStream, prepassPass, prepassPipeline, - prepassPushConstantData, + prepassPushConstants, prepassDrawcalls, prepassRenderTargets); core.recordImageMemoryBarrier(cmdStream, depthBuffer); - + + vkcv::PushConstants pushConstants (2 * sizeof(glm::mat4)); + // main pass - std::vector<std::array<glm::mat4, 2>> mainPassMatrices; for (const auto& m : modelMatrices) { - mainPassMatrices.push_back({ viewProjectionCamera * m, m }); + pushConstants.appendDrawcall(std::array<glm::mat4, 2>{ viewProjectionCamera * m, m }); } VolumetricSettings volumeSettings; @@ -648,37 +749,46 @@ int main(int argc, const char** argv) { volumeSettings.absorptionCoefficient = absorptionColor * absorptionDensity; volumeSettings.ambientLight = volumetricAmbient; volumetricSettingsBuffer.fill({ volumeSettings }); - - const vkcv::PushConstantData pushConstantData((void*)mainPassMatrices.data(), 2 * sizeof(glm::mat4)); + const std::vector<vkcv::ImageHandle> renderTargets = { colorBuffer, depthBuffer }; core.recordDrawcallsToCmdStream( cmdStream, forwardPass, forwardPipeline, - pushConstantData, + pushConstants, drawcalls, renderTargets); if (renderVoxelVis) { voxelization.renderVoxelVisualisation(cmdStream, viewProjectionCamera, renderTargets, voxelVisualisationMip); } + + vkcv::PushConstants skySettingsPushConstants (sizeof(skySettings)); + skySettingsPushConstants.appendDrawcall(skySettings); // sky core.recordDrawcallsToCmdStream( cmdStream, skyPass, skyPipe, - vkcv::PushConstantData((void*)&skySettings, sizeof(skySettings)), + skySettingsPushConstants, { vkcv::DrawcallInfo(vkcv::Mesh({}, nullptr, 3), {}) }, renderTargets); const uint32_t fullscreenLocalGroupSize = 8; - const uint32_t fulsscreenDispatchCount[3] = { - static_cast<uint32_t>(glm::ceil(windowWidth / static_cast<float>(fullscreenLocalGroupSize))), - static_cast<uint32_t>(glm::ceil(windowHeight / static_cast<float>(fullscreenLocalGroupSize))), - 1 - }; + + uint32_t fulsscreenDispatchCount [3]; + + fulsscreenDispatchCount[0] = static_cast<uint32_t>( + glm::ceil(fsrWidth / static_cast<float>(fullscreenLocalGroupSize)) + ); + + fulsscreenDispatchCount[1] = static_cast<uint32_t>( + glm::ceil(fsrHeight / static_cast<float>(fullscreenLocalGroupSize)) + ); + + fulsscreenDispatchCount[2] = 1; if (usingMsaa) { if (msaaCustomResolve) { @@ -692,7 +802,7 @@ int main(int argc, const char** argv) { resolvePipeline, fulsscreenDispatchCount, { vkcv::DescriptorSetUsage(0, core.getDescriptorSet(resolveDescriptorSet).vulkanHandle) }, - vkcv::PushConstantData(nullptr, 0)); + vkcv::PushConstants(0)); core.recordImageMemoryBarrier(cmdStream, resolvedColorBuffer); } @@ -701,21 +811,58 @@ int main(int argc, const char** argv) { } } - bloomFlares.execWholePipeline(cmdStream, resolvedColorBuffer, windowWidth, windowHeight, - glm::normalize(cameraManager.getActiveCamera().getFront())); + bloomFlares.execWholePipeline(cmdStream, resolvedColorBuffer, fsrWidth, fsrHeight, + glm::normalize(cameraManager.getActiveCamera().getFront()) + ); - core.prepareImageForStorage(cmdStream, swapchainInput); + core.prepareImageForStorage(cmdStream, swapBuffer); core.prepareImageForSampling(cmdStream, resolvedColorBuffer); - - auto timeSinceStart = std::chrono::duration_cast<std::chrono::microseconds>(end - appStartTime); - float timeF = static_cast<float>(timeSinceStart.count()) * 0.01; - + core.recordComputeDispatchToCmdStream( cmdStream, tonemappingPipeline, fulsscreenDispatchCount, - { vkcv::DescriptorSetUsage(0, core.getDescriptorSet(tonemappingDescriptorSet).vulkanHandle) }, - vkcv::PushConstantData(&timeF, sizeof(timeF))); + { vkcv::DescriptorSetUsage(0, core.getDescriptorSet( + tonemappingDescriptorSet + ).vulkanHandle) }, + vkcv::PushConstants(0) + ); + + core.prepareImageForStorage(cmdStream, swapBuffer2); + core.prepareImageForSampling(cmdStream, swapBuffer); + + if (bilinearUpscaling) { + upscaling1.recordUpscaling(cmdStream, swapBuffer, swapBuffer2); + } else { + upscaling.recordUpscaling(cmdStream, swapBuffer, swapBuffer2); + } + + core.prepareImageForStorage(cmdStream, swapchainInput); + core.prepareImageForSampling(cmdStream, swapBuffer2); + + auto timeSinceStart = std::chrono::duration_cast<std::chrono::microseconds>(end - appStartTime); + float timeF = static_cast<float>(timeSinceStart.count()) * 0.01f; + + vkcv::PushConstants timePushConstants (sizeof(timeF)); + timePushConstants.appendDrawcall(timeF); + + fulsscreenDispatchCount[0] = static_cast<uint32_t>( + glm::ceil(swapchainWidth / static_cast<float>(fullscreenLocalGroupSize)) + ); + + fulsscreenDispatchCount[1] = static_cast<uint32_t>( + glm::ceil(swapchainHeight / static_cast<float>(fullscreenLocalGroupSize)) + ); + + core.recordComputeDispatchToCmdStream( + cmdStream, + postEffectsPipeline, + fulsscreenDispatchCount, + { vkcv::DescriptorSetUsage(0, core.getDescriptorSet( + postEffectsDescriptorSet + ).vulkanHandle) }, + timePushConstants + ); // present and end core.prepareSwapchainImageForPresent(cmdStream); @@ -743,12 +890,25 @@ int main(int argc, const char** argv) { ImGui::DragFloat("Voxelization extent", &voxelizationExtent, 1.f, 0.f); voxelizationExtent = std::max(voxelizationExtent, 1.f); voxelVisualisationMip = std::max(voxelVisualisationMip, 0); - + ImGui::ColorEdit3("Scattering color", &scatteringColor.x); ImGui::DragFloat("Scattering density", &scatteringDensity, 0.0001); ImGui::ColorEdit3("Absorption color", &absorptionColor.x); ImGui::DragFloat("Absorption density", &absorptionDensity, 0.0001); ImGui::DragFloat("Volumetric ambient", &volumetricAmbient, 0.002); + + float fsrSharpness = upscaling.getSharpness(); + + ImGui::Combo("FSR Quality Mode", &fsrModeIndex, fsrModeNames.data(), fsrModeNames.size()); + ImGui::DragFloat("FSR Sharpness", &fsrSharpness, 0.001, 0.0f, 1.0f); + ImGui::Checkbox("FSR Mip Lod Bias", &fsrMipLoadBiasFlag); + ImGui::Checkbox("Bilinear Upscaling", &bilinearUpscaling); + + if ((fsrModeIndex >= 0) && (fsrModeIndex <= 4)) { + fsrMode = static_cast<vkcv::upscaling::FSRQualityMode>(fsrModeIndex); + } + + upscaling.setSharpness(fsrSharpness); if (ImGui::Button("Reload forward pass")) { diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp index aec96411c5d9e07f200b24fbdcf9fa69e2af53d5..f22d56650654f66dd1fea4141a449004dcad88cc 100644 --- a/src/vkcv/BufferManager.cpp +++ b/src/vkcv/BufferManager.cpp @@ -19,7 +19,7 @@ namespace vkcv { return; } - m_stagingBuffer = createBuffer(BufferType::STAGING, 1024 * 1024, BufferMemoryType::HOST_VISIBLE); + m_stagingBuffer = createBuffer(BufferType::STAGING, 1024 * 1024, BufferMemoryType::HOST_VISIBLE, false); } BufferManager::~BufferManager() noexcept { @@ -28,33 +28,7 @@ namespace vkcv { } } - /** - * @brief searches memory type index for buffer allocation, combines requirements of buffer and application - * @param physicalMemoryProperties Memory Properties of physical device - * @param typeBits Bit field for suitable memory types - * @param requirements Property flags that are required - * @return memory type index for Buffer - */ - uint32_t searchBufferMemoryType(const vk::PhysicalDeviceMemoryProperties& physicalMemoryProperties, uint32_t typeBits, vk::MemoryPropertyFlags requirements) { - const uint32_t memoryCount = physicalMemoryProperties.memoryTypeCount; - for (uint32_t memoryIndex = 0; memoryIndex < memoryCount; ++memoryIndex) { - const uint32_t memoryTypeBits = (1 << memoryIndex); - const bool isRequiredMemoryType = typeBits & memoryTypeBits; - - const vk::MemoryPropertyFlags properties = - physicalMemoryProperties.memoryTypes[memoryIndex].propertyFlags; - const bool hasRequiredProperties = - (properties & requirements) == requirements; - - if (isRequiredMemoryType && hasRequiredProperties) - return static_cast<int32_t>(memoryIndex); - } - - // failed to find memory type - return -1; - } - - BufferHandle BufferManager::createBuffer(BufferType type, size_t size, BufferMemoryType memoryType) { + BufferHandle BufferManager::createBuffer(BufferType type, size_t size, BufferMemoryType memoryType, bool supportIndirect) { vk::BufferCreateFlags createFlags; vk::BufferUsageFlags usageFlags; @@ -75,51 +49,62 @@ namespace vkcv { usageFlags = vk::BufferUsageFlagBits::eIndexBuffer; break; default: - // TODO: maybe an issue + vkcv_log(LogLevel::WARNING, "Unknown buffer type"); break; } if (memoryType == BufferMemoryType::DEVICE_LOCAL) { usageFlags |= vk::BufferUsageFlagBits::eTransferDst; } + if (supportIndirect) + usageFlags |= vk::BufferUsageFlagBits::eIndirectBuffer; - const vk::Device& device = m_core->getContext().getDevice(); - - vk::Buffer buffer = device.createBuffer( - vk::BufferCreateInfo(createFlags, size, usageFlags) - ); - - const vk::MemoryRequirements requirements = device.getBufferMemoryRequirements(buffer); - const vk::PhysicalDevice& physicalDevice = m_core->getContext().getPhysicalDevice(); + const vma::Allocator& allocator = m_core->getContext().getAllocator(); vk::MemoryPropertyFlags memoryTypeFlags; + vma::MemoryUsage memoryUsage; bool mappable = false; switch (memoryType) { case BufferMemoryType::DEVICE_LOCAL: memoryTypeFlags = vk::MemoryPropertyFlagBits::eDeviceLocal; + memoryUsage = vma::MemoryUsage::eGpuOnly; + mappable = false; break; case BufferMemoryType::HOST_VISIBLE: memoryTypeFlags = vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent; + memoryUsage = vma::MemoryUsage::eCpuOnly; mappable = true; break; default: - // TODO: maybe an issue + vkcv_log(LogLevel::WARNING, "Unknown buffer memory type"); + memoryUsage = vma::MemoryUsage::eUnknown; + mappable = false; break; } - const uint32_t memoryTypeIndex = searchBufferMemoryType( - physicalDevice.getMemoryProperties(), - requirements.memoryTypeBits, - memoryTypeFlags - ); + if (type == BufferType::STAGING) { + memoryUsage = vma::MemoryUsage::eCpuToGpu; + } - vk::DeviceMemory memory = device.allocateMemory(vk::MemoryAllocateInfo(requirements.size, memoryTypeIndex)); + auto bufferAllocation = allocator.createBuffer( + vk::BufferCreateInfo(createFlags, size, usageFlags), + vma::AllocationCreateInfo( + vma::AllocationCreateFlags(), + memoryUsage, + memoryTypeFlags, + memoryTypeFlags, + 0, + vma::Pool(), + nullptr + ) + ); - device.bindBufferMemory(buffer, memory, 0); + vk::Buffer buffer = bufferAllocation.first; + vma::Allocation allocation = bufferAllocation.second; const uint64_t id = m_buffers.size(); - m_buffers.push_back({ buffer, memory, size, nullptr, mappable }); + m_buffers.push_back({ buffer, allocation, size, mappable }); return BufferHandle(id, [&](uint64_t id) { destroyBufferById(id); }); } @@ -130,7 +115,7 @@ namespace vkcv { vk::Buffer buffer; vk::Buffer stagingBuffer; - vk::DeviceMemory stagingMemory; + vma::Allocation stagingAllocation; size_t stagingLimit; size_t stagingPosition; @@ -150,11 +135,11 @@ namespace vkcv { const size_t remaining = info.size - info.stagingPosition; const size_t mapped_size = std::min(remaining, info.stagingLimit); - const vk::Device& device = core->getContext().getDevice(); + const vma::Allocator& allocator = core->getContext().getAllocator(); - void* mapped = device.mapMemory(info.stagingMemory, 0, mapped_size); + void* mapped = allocator.mapMemory(info.stagingAllocation); memcpy(mapped, reinterpret_cast<const char*>(info.data) + info.stagingPosition, mapped_size); - device.unmapMemory(info.stagingMemory); + allocator.unmapMemory(info.stagingAllocation); SubmitInfo submitInfo; submitInfo.queueType = QueueType::Transfer; @@ -216,7 +201,13 @@ namespace vkcv { auto& buffer = m_buffers[id]; - return buffer.m_memory; + const vma::Allocator& allocator = m_core->getContext().getAllocator(); + + auto info = allocator.getAllocationInfo( + buffer.m_allocation + ); + + return info.deviceMemory; } void BufferManager::fillBuffer(const BufferHandle& handle, const void *data, size_t size, size_t offset) { @@ -232,11 +223,7 @@ namespace vkcv { auto& buffer = m_buffers[id]; - if (buffer.m_mapped) { - return; - } - - const vk::Device& device = m_core->getContext().getDevice(); + const vma::Allocator& allocator = m_core->getContext().getAllocator(); if (offset > buffer.m_size) { return; @@ -245,9 +232,9 @@ namespace vkcv { const size_t max_size = std::min(size, buffer.m_size - offset); if (buffer.m_mappable) { - void* mapped = device.mapMemory(buffer.m_memory, offset, max_size); - memcpy(mapped, data, max_size); - device.unmapMemory(buffer.m_memory); + void* mapped = allocator.mapMemory(buffer.m_allocation); + memcpy(reinterpret_cast<char*>(mapped) + offset, data, max_size); + allocator.unmapMemory(buffer.m_allocation); } else { auto& stagingBuffer = m_buffers[ m_stagingBuffer.getId() ]; @@ -258,11 +245,9 @@ namespace vkcv { info.buffer = buffer.m_handle; info.stagingBuffer = stagingBuffer.m_handle; - info.stagingMemory = stagingBuffer.m_memory; + info.stagingAllocation = stagingBuffer.m_allocation; - const vk::MemoryRequirements stagingRequirements = device.getBufferMemoryRequirements(stagingBuffer.m_handle); - - info.stagingLimit = stagingRequirements.size; + info.stagingLimit = stagingBuffer.m_size; info.stagingPosition = 0; copyFromStagingBuffer(m_core, info); @@ -282,19 +267,13 @@ namespace vkcv { auto& buffer = m_buffers[id]; - if (buffer.m_mapped) { - return nullptr; - } - - const vk::Device& device = m_core->getContext().getDevice(); + const vma::Allocator& allocator = m_core->getContext().getAllocator(); if (offset > buffer.m_size) { return nullptr; } - const size_t max_size = std::min(size, buffer.m_size - offset); - buffer.m_mapped = device.mapMemory(buffer.m_memory, offset, max_size); - return buffer.m_mapped; + return reinterpret_cast<char*>(allocator.mapMemory(buffer.m_allocation)) + offset; } void BufferManager::unmapBuffer(const BufferHandle& handle) { @@ -306,14 +285,9 @@ namespace vkcv { auto& buffer = m_buffers[id]; - if (buffer.m_mapped == nullptr) { - return; - } - - const vk::Device& device = m_core->getContext().getDevice(); + const vma::Allocator& allocator = m_core->getContext().getAllocator(); - device.unmapMemory(buffer.m_memory); - buffer.m_mapped = nullptr; + allocator.unmapMemory(buffer.m_allocation); } void BufferManager::destroyBufferById(uint64_t id) { @@ -323,16 +297,13 @@ namespace vkcv { auto& buffer = m_buffers[id]; - const vk::Device& device = m_core->getContext().getDevice(); - - if (buffer.m_memory) { - device.freeMemory(buffer.m_memory); - buffer.m_memory = nullptr; - } + const vma::Allocator& allocator = m_core->getContext().getAllocator(); if (buffer.m_handle) { - device.destroyBuffer(buffer.m_handle); + allocator.destroyBuffer(buffer.m_handle, buffer.m_allocation); + buffer.m_handle = nullptr; + buffer.m_allocation = nullptr; } } diff --git a/src/vkcv/CommandStreamManager.cpp b/src/vkcv/CommandStreamManager.cpp index 5a5b359b912d9cef36e0b03379d7f0f6f0951381..52b73213dbc5837f6be4a2aa25c28615dccf5969 100644 --- a/src/vkcv/CommandStreamManager.cpp +++ b/src/vkcv/CommandStreamManager.cpp @@ -32,11 +32,10 @@ namespace vkcv { // find unused stream int unusedStreamIndex = -1; - for (int i = 0; i < m_commandStreams.size(); i++) { + for (size_t i = 0; i < m_commandStreams.size(); i++) { if (m_commandStreams[i].cmdBuffer) { // still in use - } - else { + } else { unusedStreamIndex = i; break; } diff --git a/src/vkcv/Context.cpp b/src/vkcv/Context.cpp index e23213b41a3c9a289b679652b66bbe2e75cf0340..2e30fb961d0b0931e4ff8796dd92b2cbd0b5f734 100644 --- a/src/vkcv/Context.cpp +++ b/src/vkcv/Context.cpp @@ -9,11 +9,13 @@ namespace vkcv m_Instance(other.m_Instance), m_PhysicalDevice(other.m_PhysicalDevice), m_Device(other.m_Device), - m_QueueManager(other.m_QueueManager) + m_QueueManager(other.m_QueueManager), + m_Allocator(other.m_Allocator) { other.m_Instance = nullptr; other.m_PhysicalDevice = nullptr; other.m_Device = nullptr; + other.m_Allocator = nullptr; } Context & Context::operator=(Context &&other) noexcept @@ -22,10 +24,12 @@ namespace vkcv m_PhysicalDevice = other.m_PhysicalDevice; m_Device = other.m_Device; m_QueueManager = other.m_QueueManager; + m_Allocator = other.m_Allocator; other.m_Instance = nullptr; other.m_PhysicalDevice = nullptr; other.m_Device = nullptr; + other.m_Allocator = nullptr; return *this; } @@ -33,15 +37,18 @@ namespace vkcv Context::Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device, - QueueManager&& queueManager) noexcept : - m_Instance{instance}, - m_PhysicalDevice{physicalDevice}, - m_Device{device}, - m_QueueManager{queueManager} + QueueManager&& queueManager, + vma::Allocator&& allocator) noexcept : + m_Instance(instance), + m_PhysicalDevice(physicalDevice), + m_Device(device), + m_QueueManager(queueManager), + m_Allocator(allocator) {} Context::~Context() noexcept { + m_Allocator.destroy(); m_Device.destroy(); m_Instance.destroy(); } @@ -64,6 +71,10 @@ namespace vkcv const QueueManager& Context::getQueueManager() const { return m_QueueManager; } + + const vma::Allocator& Context::getAllocator() const { + return m_Allocator; + } /** * @brief The physical device is evaluated by three categories: @@ -140,7 +151,7 @@ namespace vkcv * @param check The elements to be checked * @return True, if all elements in "check" are supported */ - bool checkSupport(std::vector<const char*>& supported, std::vector<const char*>& check) + bool checkSupport(const std::vector<const char*>& supported, const std::vector<const char*>& check) { for (auto checkElem : check) { bool found = false; @@ -169,11 +180,20 @@ namespace vkcv return extensions; } + bool isPresentInCharPtrVector(const std::vector<const char*>& v, const char* term){ + for (const auto& entry : v) { + if (strcmp(entry, term) != 0) { + return true; + } + } + return false; + } + Context Context::create(const char *applicationName, uint32_t applicationVersion, - std::vector<vk::QueueFlagBits> queueFlags, - std::vector<const char *> instanceExtensions, - std::vector<const char *> deviceExtensions) { + const std::vector<vk::QueueFlagBits>& queueFlags, + const std::vector<const char *>& instanceExtensions, + const std::vector<const char *>& deviceExtensions) { // check for layer support const std::vector<vk::LayerProperties>& layerProperties = vk::enumerateInstanceLayerProperties(); @@ -212,7 +232,7 @@ namespace vkcv // for GLFW: get all required extensions std::vector<const char*> requiredExtensions = getRequiredExtensions(); - instanceExtensions.insert(instanceExtensions.end(), requiredExtensions.begin(), requiredExtensions.end()); + requiredExtensions.insert(requiredExtensions.end(), instanceExtensions.begin(), instanceExtensions.end()); const vk::ApplicationInfo applicationInfo( applicationName, @@ -227,8 +247,8 @@ namespace vkcv &applicationInfo, 0, nullptr, - static_cast<uint32_t>(instanceExtensions.size()), - instanceExtensions.data() + static_cast<uint32_t>(requiredExtensions.size()), + requiredExtensions.data() ); #ifndef NDEBUG @@ -275,13 +295,39 @@ namespace vkcv deviceCreateInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); deviceCreateInfo.ppEnabledLayerNames = validationLayers.data(); #endif - + const bool shaderFloat16 = checkSupport(deviceExtensions, { "VK_KHR_shader_float16_int8" }); + const bool storage16bit = checkSupport(deviceExtensions, { "VK_KHR_16bit_storage" }); + // FIXME: check if device feature is supported - vk::PhysicalDeviceFeatures deviceFeatures; - deviceFeatures.fragmentStoresAndAtomics = true; - deviceFeatures.geometryShader = true; - deviceFeatures.depthClamp = true; - deviceCreateInfo.pEnabledFeatures = &deviceFeatures; + vk::PhysicalDeviceShaderFloat16Int8Features deviceShaderFloat16Int8Features; + deviceShaderFloat16Int8Features.shaderFloat16 = shaderFloat16; + + vk::PhysicalDevice16BitStorageFeatures device16BitStorageFeatures; + device16BitStorageFeatures.storageBuffer16BitAccess = storage16bit; + + vk::PhysicalDeviceFeatures2 deviceFeatures2; + deviceFeatures2.features.fragmentStoresAndAtomics = true; + deviceFeatures2.features.geometryShader = true; + deviceFeatures2.features.depthClamp = true; + deviceFeatures2.features.shaderInt16 = true; + + const bool usingMeshShaders = isPresentInCharPtrVector(deviceExtensions, VK_NV_MESH_SHADER_EXTENSION_NAME); + vk::PhysicalDeviceMeshShaderFeaturesNV meshShadingFeatures; + if (usingMeshShaders) { + meshShadingFeatures.taskShader = true; + meshShadingFeatures.meshShader = true; + deviceFeatures2.setPNext(&meshShadingFeatures); + } + + if (shaderFloat16) { + deviceFeatures2.setPNext(&deviceShaderFloat16Int8Features); + } + + if (storage16bit) { + deviceShaderFloat16Int8Features.setPNext(&device16BitStorageFeatures); + } + + deviceCreateInfo.setPNext(&deviceFeatures2); // Ablauf // qCreateInfos erstellen --> braucht das Device @@ -289,10 +335,50 @@ namespace vkcv // jetzt koennen wir mit dem device die queues erstellen vk::Device device = physicalDevice.createDevice(deviceCreateInfo); + + if (usingMeshShaders) + { + InitMeshShaderDrawFunctions(device); + } - QueueManager queueManager = QueueManager::create(device, queuePairsGraphics, queuePairsCompute, queuePairsTransfer); + QueueManager queueManager = QueueManager::create( + device, + queuePairsGraphics, + queuePairsCompute, + queuePairsTransfer + ); - return Context(instance, physicalDevice, device, std::move(queueManager)); + const vma::AllocatorCreateInfo allocatorCreateInfo ( + vma::AllocatorCreateFlags(), + physicalDevice, + device, + 0, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + instance, + + /* Uses default version when set to 0 (currently VK_VERSION_1_0): + * + * The reason for this is that the allocator restricts the allowed version + * to be at maximum VK_VERSION_1_1 which is already less than + * VK_HEADER_VERSION_COMPLETE at most platforms. + * */ + 0 + ); + + vma::Allocator allocator = vma::createAllocator(allocatorCreateInfo); + + return Context( + instance, + physicalDevice, + device, + std::move(queueManager), + std::move(allocator) + ); } } diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp index 352a1cf62eabe55ce1bbf2f53a6b5a4bd6e91753..92e2df4f18f59355868e9dcce7a78c4e1a9c5cb7 100644 --- a/src/vkcv/Core.cpp +++ b/src/vkcv/Core.cpp @@ -53,9 +53,9 @@ namespace vkcv Core Core::create(Window &window, const char *applicationName, uint32_t applicationVersion, - std::vector<vk::QueueFlagBits> queueFlags, - std::vector<const char *> instanceExtensions, - std::vector<const char *> deviceExtensions) + const std::vector<vk::QueueFlagBits>& queueFlags, + const std::vector<const char *>& instanceExtensions, + const std::vector<const char *>& deviceExtensions) { Context context = Context::create( applicationName, applicationVersion, @@ -71,7 +71,6 @@ namespace vkcv const auto& queueManager = context.getQueueManager(); - const int graphicQueueFamilyIndex = queueManager.getGraphicsQueues()[0].familyIndex; const std::unordered_set<int> queueFamilySet = generateQueueFamilyIndexSet(queueManager); const auto commandResources = createCommandResources(context.getDevice(), queueFamilySet); const auto defaultSyncResources = createSyncResources(context.getDevice()); @@ -91,8 +90,8 @@ namespace vkcv Core::Core(Context &&context, Window &window, const Swapchain& swapChain, std::vector<vk::ImageView> swapchainImageViews, const CommandResources& commandResources, const SyncResources& syncResources) noexcept : m_Context(std::move(context)), + m_swapchain(swapChain), m_window(window), - m_swapchain(swapChain), m_PassManager{std::make_unique<PassManager>(m_Context.m_Device)}, m_PipelineManager{std::make_unique<PipelineManager>(m_Context.m_Device)}, m_DescriptorManager(std::make_unique<DescriptorManager>(m_Context.m_Device)), @@ -119,7 +118,8 @@ namespace vkcv swapchainImageViews, swapChain.getExtent().width, swapChain.getExtent().height, - swapChain.getFormat()); + swapChain.getFormat() + ); } Core::~Core() noexcept { @@ -163,9 +163,9 @@ namespace vkcv nullptr, &imageIndex, {} ); - } catch (vk::OutOfDateKHRError e) { + } catch (const vk::OutOfDateKHRError& e) { result = vk::Result::eErrorOutOfDateKHR; - } catch (vk::DeviceLostError e) { + } catch (const vk::DeviceLostError& e) { result = vk::Result::eErrorDeviceLost; } @@ -228,133 +228,246 @@ namespace vkcv return (m_currentSwapchainImageIndex != std::numeric_limits<uint32_t>::max()); } - void Core::recordDrawcallsToCmdStream( - const CommandStreamHandle cmdStreamHandle, - const PassHandle renderpassHandle, - const PipelineHandle pipelineHandle, - const PushConstantData &pushConstantData, - const std::vector<DrawcallInfo> &drawcalls, - const std::vector<ImageHandle> &renderTargets) { + std::array<uint32_t, 2> getWidthHeightFromRenderTargets( + const std::vector<ImageHandle>& renderTargets, + const Swapchain& swapchain, + const ImageManager& imageManager) { - if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) { - return; - } + std::array<uint32_t, 2> widthHeight; - uint32_t width; - uint32_t height; if (renderTargets.size() > 0) { const vkcv::ImageHandle firstImage = renderTargets[0]; if (firstImage.isSwapchainImage()) { - const auto& swapchainExtent = m_swapchain.getExtent(); - width = swapchainExtent.width; - height = swapchainExtent.height; + const auto& swapchainExtent = swapchain.getExtent(); + widthHeight[0] = swapchainExtent.width; + widthHeight[1] = swapchainExtent.height; } else { - width = m_ImageManager->getImageWidth(firstImage); - height = m_ImageManager->getImageHeight(firstImage); + widthHeight[0] = imageManager.getImageWidth(firstImage); + widthHeight[1] = imageManager.getImageHeight(firstImage); } } else { - width = 1; - height = 1; + widthHeight[0] = 1; + widthHeight[1] = 1; } // TODO: validate that width/height match for all attachments + return widthHeight; + } - const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle); - const PassConfig passConfig = m_PassManager->getPassConfig(renderpassHandle); - - const vk::Pipeline pipeline = m_PipelineManager->getVkPipeline(pipelineHandle); - const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle); - const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height)); + vk::Framebuffer createFramebuffer( + const std::vector<ImageHandle>& renderTargets, + const ImageManager& imageManager, + const Swapchain& swapchain, + vk::RenderPass renderpass, + vk::Device device) { std::vector<vk::ImageView> attachmentsViews; for (const ImageHandle handle : renderTargets) { - vk::ImageView targetHandle; - const auto cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle); + vk::ImageView targetHandle = imageManager.getVulkanImageView(handle); + attachmentsViews.push_back(targetHandle); + } + + const std::array<uint32_t, 2> widthHeight = getWidthHeightFromRenderTargets(renderTargets, swapchain, imageManager); + + const vk::FramebufferCreateInfo createInfo( + {}, + renderpass, + static_cast<uint32_t>(attachmentsViews.size()), + attachmentsViews.data(), + widthHeight[0], + widthHeight[1], + 1); + + return device.createFramebuffer(createInfo); + } - targetHandle = m_ImageManager->getVulkanImageView(handle); - const bool isDepthImage = isDepthFormat(m_ImageManager->getImageFormat(handle)); - const vk::ImageLayout targetLayout = + void transitionRendertargetsToAttachmentLayout( + const std::vector<ImageHandle>& renderTargets, + ImageManager& imageManager, + const vk::CommandBuffer cmdBuffer) { + + for (const ImageHandle handle : renderTargets) { + vk::ImageView targetHandle = imageManager.getVulkanImageView(handle); + const bool isDepthImage = isDepthFormat(imageManager.getImageFormat(handle)); + const vk::ImageLayout targetLayout = isDepthImage ? vk::ImageLayout::eDepthStencilAttachmentOptimal : vk::ImageLayout::eColorAttachmentOptimal; - m_ImageManager->recordImageLayoutTransition(handle, targetLayout, cmdBuffer); - attachmentsViews.push_back(targetHandle); + imageManager.recordImageLayoutTransition(handle, targetLayout, cmdBuffer); } - - const vk::FramebufferCreateInfo createInfo( - {}, - renderpass, - static_cast<uint32_t>(attachmentsViews.size()), - attachmentsViews.data(), - width, - height, - 1 + } + + std::vector<vk::ClearValue> createAttachmentClearValues(const std::vector<AttachmentDescription>& attachments) { + std::vector<vk::ClearValue> clearValues; + for (const auto& attachment : attachments) { + if (attachment.load_operation == AttachmentOperation::CLEAR) { + float clear = 0.0f; + + if (isDepthFormat(attachment.format)) { + clear = 1.0f; + } + + clearValues.emplace_back(std::array<float, 4>{ + clear, + clear, + clear, + 1.f + }); + } + } + return clearValues; + } + + void recordDynamicViewport(vk::CommandBuffer cmdBuffer, uint32_t width, uint32_t height) { + vk::Viewport dynamicViewport( + 0.0f, 0.0f, + static_cast<float>(width), static_cast<float>(height), + 0.0f, 1.0f ); - - vk::Framebuffer framebuffer = m_Context.m_Device.createFramebuffer(createInfo); - - if (!framebuffer) { + + vk::Rect2D dynamicScissor({ 0, 0 }, { width, height }); + + cmdBuffer.setViewport(0, 1, &dynamicViewport); + cmdBuffer.setScissor(0, 1, &dynamicScissor); + } + + void Core::recordDrawcallsToCmdStream( + const CommandStreamHandle cmdStreamHandle, + const PassHandle renderpassHandle, + const PipelineHandle pipelineHandle, + const PushConstants &pushConstantData, + const std::vector<DrawcallInfo> &drawcalls, + const std::vector<ImageHandle> &renderTargets) { + + if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) { + return; + } + + const std::array<uint32_t, 2> widthHeight = getWidthHeightFromRenderTargets(renderTargets, m_swapchain, *m_ImageManager); + const auto width = widthHeight[0]; + const auto height = widthHeight[1]; + + const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle); + const PassConfig passConfig = m_PassManager->getPassConfig(renderpassHandle); + + const vk::Pipeline pipeline = m_PipelineManager->getVkPipeline(pipelineHandle); + const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle); + const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height)); + + vk::CommandBuffer cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle); + transitionRendertargetsToAttachmentLayout(renderTargets, *m_ImageManager, cmdBuffer); + + const vk::Framebuffer framebuffer = createFramebuffer(renderTargets, *m_ImageManager, m_swapchain, renderpass, m_Context.m_Device); + + if (!framebuffer) { vkcv_log(LogLevel::ERROR, "Failed to create temporary framebuffer"); - return; - } + return; + } - vk::Viewport dynamicViewport( - 0.0f, 0.0f, - static_cast<float>(width), static_cast<float>(height), - 0.0f, 1.0f - ); + SubmitInfo submitInfo; + submitInfo.queueType = QueueType::Graphics; + submitInfo.signalSemaphores = { m_SyncResources.renderFinished }; + + auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { + + const std::vector<vk::ClearValue> clearValues = createAttachmentClearValues(passConfig.attachments); + + const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, clearValues.size(), clearValues.data()); + cmdBuffer.beginRenderPass(beginInfo, {}, {}); + + cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {}); + + const PipelineConfig &pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle); + if(pipeConfig.m_UseDynamicViewport) + { + recordDynamicViewport(cmdBuffer, width, height); + } + + for (int i = 0; i < drawcalls.size(); i++) { + recordDrawcall(drawcalls[i], cmdBuffer, pipelineLayout, pushConstantData, i); + } vk::Rect2D dynamicScissor({0, 0}, {width, height}); + cmdBuffer.endRenderPass(); + }; + + auto finishFunction = [framebuffer, this]() + { + m_Context.m_Device.destroy(framebuffer); + }; - auto &bufferManager = m_BufferManager; + recordCommandsToStream(cmdStreamHandle, submitFunction, finishFunction); + } + + void Core::recordMeshShaderDrawcalls( + const CommandStreamHandle cmdStreamHandle, + const PassHandle renderpassHandle, + const PipelineHandle pipelineHandle, + const PushConstants& pushConstantData, + const std::vector<MeshShaderDrawcall>& drawcalls, + const std::vector<ImageHandle>& renderTargets) { + + if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) { + return; + } + + const std::array<uint32_t, 2> widthHeight = getWidthHeightFromRenderTargets(renderTargets, m_swapchain, *m_ImageManager); + const auto width = widthHeight[0]; + const auto height = widthHeight[1]; + + const vk::RenderPass renderpass = m_PassManager->getVkPass(renderpassHandle); + const PassConfig passConfig = m_PassManager->getPassConfig(renderpassHandle); + + const vk::Pipeline pipeline = m_PipelineManager->getVkPipeline(pipelineHandle); + const vk::PipelineLayout pipelineLayout = m_PipelineManager->getVkPipelineLayout(pipelineHandle); + const vk::Rect2D renderArea(vk::Offset2D(0, 0), vk::Extent2D(width, height)); + + vk::CommandBuffer cmdBuffer = m_CommandStreamManager->getStreamCommandBuffer(cmdStreamHandle); + transitionRendertargetsToAttachmentLayout(renderTargets, *m_ImageManager, cmdBuffer); + + const vk::Framebuffer framebuffer = createFramebuffer(renderTargets, *m_ImageManager, m_swapchain, renderpass, m_Context.m_Device); + + if (!framebuffer) { + vkcv_log(LogLevel::ERROR, "Failed to create temporary framebuffer"); + return; + } SubmitInfo submitInfo; submitInfo.queueType = QueueType::Graphics; submitInfo.signalSemaphores = { m_SyncResources.renderFinished }; auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { - std::vector<vk::ClearValue> clearValues; - - for (const auto& attachment : passConfig.attachments) { - if (attachment.load_operation == AttachmentOperation::CLEAR) { - float clear = 0.0f; - - if (isDepthFormat(attachment.format)) { - clear = 1.0f; - } - - clearValues.emplace_back(std::array<float, 4>{ - clear, - clear, - clear, - 1.f - }); - } - } - - const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, clearValues.size(), clearValues.data()); - const vk::SubpassContents subpassContents = {}; - cmdBuffer.beginRenderPass(beginInfo, subpassContents, {}); - - cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {}); - - const PipelineConfig &pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle); - if(pipeConfig.m_UseDynamicViewport) - { - cmdBuffer.setViewport(0, 1, &dynamicViewport); - cmdBuffer.setScissor(0, 1, &dynamicScissor); - } - - for (int i = 0; i < drawcalls.size(); i++) { - recordDrawcall(drawcalls[i], cmdBuffer, pipelineLayout, pushConstantData, i); - } - - cmdBuffer.endRenderPass(); - }; - - auto finishFunction = [framebuffer, this]() - { - m_Context.m_Device.destroy(framebuffer); - }; + + const std::vector<vk::ClearValue> clearValues = createAttachmentClearValues(passConfig.attachments); + + const vk::RenderPassBeginInfo beginInfo(renderpass, framebuffer, renderArea, clearValues.size(), clearValues.data()); + cmdBuffer.beginRenderPass(beginInfo, {}, {}); + + cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {}); + + const PipelineConfig& pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle); + if (pipeConfig.m_UseDynamicViewport) + { + recordDynamicViewport(cmdBuffer, width, height); + } + + for (int i = 0; i < drawcalls.size(); i++) { + const uint32_t pushConstantOffset = i * pushConstantData.getSizePerDrawcall(); + recordMeshShaderDrawcall( + cmdBuffer, + pipelineLayout, + pushConstantData, + pushConstantOffset, + drawcalls[i], + 0); + } + + cmdBuffer.endRenderPass(); + }; + + auto finishFunction = [framebuffer, this]() + { + m_Context.m_Device.destroy(framebuffer); + }; recordCommandsToStream(cmdStreamHandle, submitFunction, finishFunction); } @@ -364,7 +477,7 @@ namespace vkcv PipelineHandle computePipeline, const uint32_t dispatchCount[3], const std::vector<DescriptorSetUsage>& descriptorSetUsages, - const PushConstantData& pushConstantData) { + const PushConstants& pushConstants) { auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { @@ -377,15 +490,16 @@ namespace vkcv pipelineLayout, usage.setLocation, { usage.vulkanHandle }, - {}); + usage.dynamicOffsets + ); } - if (pushConstantData.sizePerDrawcall > 0) { + if (pushConstants.getSizePerDrawcall() > 0) { cmdBuffer.pushConstants( pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, - pushConstantData.sizePerDrawcall, - pushConstantData.data); + pushConstants.getSizePerDrawcall(), + pushConstants.getData()); } cmdBuffer.dispatch(dispatchCount[0], dispatchCount[1], dispatchCount[2]); }; @@ -393,6 +507,42 @@ namespace vkcv recordCommandsToStream(cmdStreamHandle, submitFunction, nullptr); } + void Core::recordComputeIndirectDispatchToCmdStream( + const CommandStreamHandle cmdStream, + const PipelineHandle computePipeline, + const vkcv::BufferHandle buffer, + const size_t bufferArgOffset, + const std::vector<DescriptorSetUsage>& descriptorSetUsages, + const PushConstants& pushConstants) { + + auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { + + const auto pipelineLayout = m_PipelineManager->getVkPipelineLayout(computePipeline); + + cmdBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, m_PipelineManager->getVkPipeline(computePipeline)); + for (const auto& usage : descriptorSetUsages) { + cmdBuffer.bindDescriptorSets( + vk::PipelineBindPoint::eCompute, + pipelineLayout, + usage.setLocation, + { usage.vulkanHandle }, + usage.dynamicOffsets + ); + } + if (pushConstants.getSizePerDrawcall() > 0) { + cmdBuffer.pushConstants( + pipelineLayout, + vk::ShaderStageFlagBits::eCompute, + 0, + pushConstants.getSizePerDrawcall(), + pushConstants.getData()); + } + cmdBuffer.dispatchIndirect(m_BufferManager->getBuffer(buffer), bufferArgOffset); + }; + + recordCommandsToStream(cmdStream, submitFunction, nullptr); + } + void Core::endFrame() { if (m_currentSwapchainImageIndex == std::numeric_limits<uint32_t>::max()) { return; @@ -417,9 +567,9 @@ namespace vkcv try { result = queueManager.getPresentQueue().handle.presentKHR(presentInfo); - } catch (vk::OutOfDateKHRError e) { + } catch (const vk::OutOfDateKHRError& e) { result = vk::Result::eErrorOutOfDateKHR; - } catch (vk::DeviceLostError e) { + } catch (const vk::DeviceLostError& e) { result = vk::Result::eErrorDeviceLost; } @@ -463,8 +613,6 @@ namespace vkcv } CommandStreamHandle Core::createCommandStream(QueueType queueType) { - - const vk::Device& device = m_Context.getDevice(); const vkcv::Queue queue = getQueueForSubmit(queueType, m_Context.getQueueManager()); const vk::CommandPool cmdPool = chooseCmdPool(queue, m_CommandResources); @@ -482,7 +630,7 @@ namespace vkcv } } - void Core::submitCommandStream(const CommandStreamHandle handle) { + void Core::submitCommandStream(const CommandStreamHandle& handle) { std::vector<vk::Semaphore> waitSemaphores; // FIXME: add proper user controllable sync std::vector<vk::Semaphore> signalSemaphores = { m_SyncResources.renderFinished }; @@ -490,8 +638,9 @@ namespace vkcv } SamplerHandle Core::createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter, - SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode) { - return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode); + SamplerMipmapMode mipmapMode, SamplerAddressMode addressMode, + float mipLodBias) { + return m_SamplerManager->createSampler(magFilter, minFilter, mipmapMode, addressMode, mipLodBias); } Image Core::createImage( @@ -522,14 +671,18 @@ namespace vkcv multisampling); } - const uint32_t Core::getImageWidth(ImageHandle imageHandle) + uint32_t Core::getImageWidth(const ImageHandle& image) { - return m_ImageManager->getImageWidth(imageHandle); + return m_ImageManager->getImageWidth(image); } - const uint32_t Core::getImageHeight(ImageHandle imageHandle) + uint32_t Core::getImageHeight(const ImageHandle& image) { - return m_ImageManager->getImageHeight(imageHandle); + return m_ImageManager->getImageHeight(image); + } + + vk::Format Core::getImageFormat(const ImageHandle& image) { + return m_ImageManager->getImageFormat(image); } DescriptorSetHandle Core::createDescriptorSet(const std::vector<DescriptorBinding>& bindings) @@ -550,38 +703,38 @@ namespace vkcv return m_DescriptorManager->getDescriptorSet(handle); } - void Core::prepareSwapchainImageForPresent(const CommandStreamHandle cmdStream) { + void Core::prepareSwapchainImageForPresent(const CommandStreamHandle& cmdStream) { auto swapchainHandle = ImageHandle::createSwapchainImageHandle(); recordCommandsToStream(cmdStream, [swapchainHandle, this](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordImageLayoutTransition(swapchainHandle, vk::ImageLayout::ePresentSrcKHR, cmdBuffer); }, nullptr); } - void Core::prepareImageForSampling(const CommandStreamHandle cmdStream, const ImageHandle image) { + void Core::prepareImageForSampling(const CommandStreamHandle& cmdStream, const ImageHandle& image) { recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordImageLayoutTransition(image, vk::ImageLayout::eShaderReadOnlyOptimal, cmdBuffer); }, nullptr); } - void Core::prepareImageForStorage(const CommandStreamHandle cmdStream, const ImageHandle image) { + void Core::prepareImageForStorage(const CommandStreamHandle& cmdStream, const ImageHandle& image) { recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordImageLayoutTransition(image, vk::ImageLayout::eGeneral, cmdBuffer); }, nullptr); } - void Core::recordImageMemoryBarrier(const CommandStreamHandle cmdStream, const ImageHandle image) { + void Core::recordImageMemoryBarrier(const CommandStreamHandle& cmdStream, const ImageHandle& image) { recordCommandsToStream(cmdStream, [image, this](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordImageMemoryBarrier(image, cmdBuffer); }, nullptr); } - void Core::recordBufferMemoryBarrier(const CommandStreamHandle cmdStream, const BufferHandle buffer) { + void Core::recordBufferMemoryBarrier(const CommandStreamHandle& cmdStream, const BufferHandle& buffer) { recordCommandsToStream(cmdStream, [buffer, this](const vk::CommandBuffer cmdBuffer) { m_BufferManager->recordBufferMemoryBarrier(buffer, cmdBuffer); }, nullptr); } - void Core::resolveMSAAImage(CommandStreamHandle cmdStream, ImageHandle src, ImageHandle dst) { + void Core::resolveMSAAImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst) { recordCommandsToStream(cmdStream, [src, dst, this](const vk::CommandBuffer cmdBuffer) { m_ImageManager->recordMSAAResolve(cmdBuffer, src, dst); }, nullptr); @@ -590,5 +743,86 @@ namespace vkcv vk::ImageView Core::getSwapchainImageView() const { return m_ImageManager->getVulkanImageView(vkcv::ImageHandle::createSwapchainImageHandle()); } + + void Core::recordMemoryBarrier(const CommandStreamHandle& cmdStream) { + recordCommandsToStream(cmdStream, [](const vk::CommandBuffer cmdBuffer) { + vk::MemoryBarrier barrier ( + vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead, + vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead + ); + + cmdBuffer.pipelineBarrier( + vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eAllCommands, + vk::DependencyFlags(), + 1, &barrier, + 0, nullptr, + 0, nullptr + ); + }, nullptr); + } + + void Core::recordBlitImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst, + SamplerFilterType filterType) { + recordCommandsToStream(cmdStream, [&](const vk::CommandBuffer cmdBuffer) { + m_ImageManager->recordImageLayoutTransition( + src, vk::ImageLayout::eTransferSrcOptimal, cmdBuffer + ); + + m_ImageManager->recordImageLayoutTransition( + dst, vk::ImageLayout::eTransferDstOptimal, cmdBuffer + ); + + const std::array<vk::Offset3D, 2> srcOffsets = { + vk::Offset3D(0, 0, 0), + vk::Offset3D( + m_ImageManager->getImageWidth(src), + m_ImageManager->getImageHeight(src), + 1 + ) + }; + + const std::array<vk::Offset3D, 2> dstOffsets = { + vk::Offset3D(0, 0, 0), + vk::Offset3D( + m_ImageManager->getImageWidth(dst), + m_ImageManager->getImageHeight(dst), + 1 + ) + }; + + const bool srcDepth = isDepthFormat(m_ImageManager->getImageFormat(src)); + const bool dstDepth = isDepthFormat(m_ImageManager->getImageFormat(dst)); + + const vk::ImageBlit blit = vk::ImageBlit( + vk::ImageSubresourceLayers( + srcDepth? + vk::ImageAspectFlagBits::eDepth : + vk::ImageAspectFlagBits::eColor, + 0, 0, 1 + ), + srcOffsets, + vk::ImageSubresourceLayers( + dstDepth? + vk::ImageAspectFlagBits::eDepth : + vk::ImageAspectFlagBits::eColor, + 0, 0, 1 + ), + dstOffsets + ); + + cmdBuffer.blitImage( + m_ImageManager->getVulkanImage(src), + vk::ImageLayout::eTransferSrcOptimal, + m_ImageManager->getVulkanImage(dst), + vk::ImageLayout::eTransferDstOptimal, + 1, + &blit, + filterType == SamplerFilterType::LINEAR? + vk::Filter::eLinear : + vk::Filter::eNearest + ); + }, nullptr); + } } diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp index d28dd9d137240ba923b55c9be9da9059d3a9ab31..76f2dae74420804c9ce168bea76e7c1bdef0fbc0 100644 --- a/src/vkcv/DescriptorManager.cpp +++ b/src/vkcv/DescriptorManager.cpp @@ -11,12 +11,17 @@ namespace vkcv * Allocate the set size for the descriptor pools, namely 1000 units of each descriptor type below. * Finally, create an initial pool. */ - m_PoolSizes = { vk::DescriptorPoolSize(vk::DescriptorType::eSampler, 1000), - vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, 1000), - vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, 1000), - vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 1000) }; + m_PoolSizes = { + vk::DescriptorPoolSize(vk::DescriptorType::eSampler, 1000), + vk::DescriptorPoolSize(vk::DescriptorType::eSampledImage, 1000), + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, 1000), + vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, 1000), + vk::DescriptorPoolSize(vk::DescriptorType::eUniformBufferDynamic, 1000), + vk::DescriptorPoolSize(vk::DescriptorType::eStorageBufferDynamic, 1000) + }; - m_PoolInfo = vk::DescriptorPoolCreateInfo({}, + m_PoolInfo = vk::DescriptorPoolCreateInfo( + vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, 1000, static_cast<uint32_t>(m_PoolSizes.size()), m_PoolSizes.data()); @@ -29,9 +34,13 @@ namespace vkcv for (uint64_t id = 0; id < m_DescriptorSets.size(); id++) { destroyDescriptorSetById(id); } + m_DescriptorSets.clear(); + for (const auto &pool : m_Pools) { - m_Device.destroy(pool); + if (pool) { + m_Device.destroy(pool); + } } } @@ -40,12 +49,12 @@ namespace vkcv std::vector<vk::DescriptorSetLayoutBinding> setBindings = {}; //create each set's binding - for (uint32_t i = 0; i < bindings.size(); i++) { + for (auto binding : bindings) { vk::DescriptorSetLayoutBinding descriptorSetLayoutBinding( - bindings[i].bindingID, - convertDescriptorTypeFlag(bindings[i].descriptorType), - bindings[i].descriptorCount, - getShaderStageFlags(bindings[i].shaderStages)); + binding.bindingID, + convertDescriptorTypeFlag(binding.descriptorType), + binding.descriptorCount, + getShaderStageFlags(binding.shaderStages)); setBindings.push_back(descriptorSetLayoutBinding); } @@ -53,8 +62,7 @@ namespace vkcv //create the descriptor set's layout from the bindings gathered above vk::DescriptorSetLayoutCreateInfo layoutInfo({}, setBindings); - if(m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &set.layout) != vk::Result::eSuccess) - { + if (m_Device.createDescriptorSetLayout(&layoutInfo, nullptr, &set.layout) != vk::Result::eSuccess) { vkcv_log(LogLevel::ERROR, "Failed to create descriptor set layout"); return DescriptorSetHandle(); }; @@ -70,6 +78,7 @@ namespace vkcv allocInfo.setDescriptorPool(m_Pools.back()); result = m_Device.allocateDescriptorSets(&allocInfo, &set.vulkanHandle); } + if (result != vk::Result::eSuccess) { vkcv_log(LogLevel::ERROR, "Failed to create descriptor set (%s)", vk::to_string(result).c_str()); @@ -78,6 +87,8 @@ namespace vkcv return DescriptorSetHandle(); } }; + + set.poolIndex = (m_Pools.size() - 1); const uint64_t id = m_DescriptorSets.size(); @@ -98,7 +109,6 @@ namespace vkcv const ImageManager &imageManager, const BufferManager &bufferManager, const SamplerManager &samplerManager) { - vk::DescriptorSet set = m_DescriptorSets[handle.getId()].vulkanHandle; std::vector<vk::DescriptorImageInfo> imageInfos; @@ -146,10 +156,15 @@ namespace vkcv } for (const auto& write : writes.uniformBufferWrites) { + const size_t size = bufferManager.getBufferSize(write.buffer); + const uint32_t offset = std::clamp<uint32_t>(write.offset, 0, size); + const vk::DescriptorBufferInfo bufferInfo( bufferManager.getBuffer(write.buffer), - static_cast<uint32_t>(0), - bufferManager.getBufferSize(write.buffer) + offset, + write.size == 0? size : std::min<uint32_t>( + write.size, size - offset + ) ); bufferInfos.push_back(bufferInfo); @@ -158,6 +173,8 @@ namespace vkcv 0, bufferInfos.size(), write.binding, + write.dynamic? + vk::DescriptorType::eUniformBufferDynamic : vk::DescriptorType::eUniformBuffer }; @@ -165,10 +182,15 @@ namespace vkcv } for (const auto& write : writes.storageBufferWrites) { + const size_t size = bufferManager.getBufferSize(write.buffer); + const uint32_t offset = std::clamp<uint32_t>(write.offset, 0, size); + const vk::DescriptorBufferInfo bufferInfo( bufferManager.getBuffer(write.buffer), - static_cast<uint32_t>(0), - bufferManager.getBufferSize(write.buffer) + offset, + write.size == 0? size : std::min<uint32_t>( + write.size, size - offset + ) ); bufferInfos.push_back(bufferInfo); @@ -177,6 +199,8 @@ namespace vkcv 0, bufferInfos.size(), write.binding, + write.dynamic? + vk::DescriptorType::eStorageBufferDynamic : vk::DescriptorType::eStorageBuffer }; @@ -232,8 +256,12 @@ namespace vkcv { case DescriptorType::UNIFORM_BUFFER: return vk::DescriptorType::eUniformBuffer; + case DescriptorType::UNIFORM_BUFFER_DYNAMIC: + return vk::DescriptorType::eUniformBufferDynamic; case DescriptorType::STORAGE_BUFFER: return vk::DescriptorType::eStorageBuffer; + case DescriptorType::STORAGE_BUFFER_DYNAMIC: + return vk::DescriptorType::eStorageBufferDynamic; case DescriptorType::SAMPLER: return vk::DescriptorType::eSampler; case DescriptorType::IMAGE_SAMPLED: @@ -257,17 +285,22 @@ namespace vkcv m_Device.destroyDescriptorSetLayout(set.layout); set.layout = nullptr; } - // FIXME: descriptor set itself not destroyed + + if (set.vulkanHandle) { + m_Device.freeDescriptorSets(m_Pools[set.poolIndex], 1, &(set.vulkanHandle)); + set.vulkanHandle = nullptr; + } } vk::DescriptorPool DescriptorManager::allocateDescriptorPool() { vk::DescriptorPool pool; - if (m_Device.createDescriptorPool(&m_PoolInfo, nullptr, &pool) != vk::Result::eSuccess) - { + if (m_Device.createDescriptorPool(&m_PoolInfo, nullptr, &pool) != vk::Result::eSuccess) { vkcv_log(LogLevel::WARNING, "Failed to allocate descriptor pool"); pool = nullptr; - }; - m_Pools.push_back(pool); + } else { + m_Pools.push_back(pool); + } + return pool; } diff --git a/src/vkcv/DrawcallRecording.cpp b/src/vkcv/DrawcallRecording.cpp index e6ea18588c251b5e49f454618a5ac9962cc8a264..d89ace3859717f753534402507a713a78bfb6876 100644 --- a/src/vkcv/DrawcallRecording.cpp +++ b/src/vkcv/DrawcallRecording.cpp @@ -1,12 +1,23 @@ #include <vkcv/DrawcallRecording.hpp> +#include <vkcv/Logger.hpp> namespace vkcv { + vk::IndexType getIndexType(IndexBitCount indexByteCount){ + switch (indexByteCount) { + case IndexBitCount::Bit16: return vk::IndexType::eUint16; + case IndexBitCount::Bit32: return vk::IndexType::eUint32; + default: + vkcv_log(LogLevel::ERROR, "unknown Enum"); + return vk::IndexType::eUint16; + } + } + void recordDrawcall( const DrawcallInfo &drawcall, vk::CommandBuffer cmdBuffer, vk::PipelineLayout pipelineLayout, - const PushConstantData &pushConstantData, + const PushConstants &pushConstants, const size_t drawcallIndex) { for (uint32_t i = 0; i < drawcall.mesh.vertexBufferBindings.size(); i++) { @@ -23,25 +34,69 @@ namespace vkcv { nullptr); } - const size_t drawcallPushConstantOffset = drawcallIndex * pushConstantData.sizePerDrawcall; - // char* cast because void* does not support pointer arithmetic - const void* drawcallPushConstantData = drawcallPushConstantOffset + (char*)pushConstantData.data; - - if (pushConstantData.data && pushConstantData.sizePerDrawcall > 0) { + if (pushConstants.getSizePerDrawcall() > 0) { cmdBuffer.pushConstants( pipelineLayout, vk::ShaderStageFlagBits::eAll, 0, - pushConstantData.sizePerDrawcall, - drawcallPushConstantData); + pushConstants.getSizePerDrawcall(), + pushConstants.getDrawcallData(drawcallIndex)); } if (drawcall.mesh.indexBuffer) { - cmdBuffer.bindIndexBuffer(drawcall.mesh.indexBuffer, 0, vk::IndexType::eUint16); //FIXME: choose proper size + cmdBuffer.bindIndexBuffer(drawcall.mesh.indexBuffer, 0, getIndexType(drawcall.mesh.indexBitCount)); cmdBuffer.drawIndexed(drawcall.mesh.indexCount, drawcall.instanceCount, 0, 0, {}); } else { - cmdBuffer.draw(drawcall.mesh.indexCount, 1, 0, 0, {}); + cmdBuffer.draw(drawcall.mesh.indexCount, drawcall.instanceCount, 0, 0, {}); + } + } + + + + struct MeshShaderFunctions + { + PFN_vkCmdDrawMeshTasksNV cmdDrawMeshTasks = nullptr; + PFN_vkCmdDrawMeshTasksIndirectNV cmdDrawMeshTasksIndirect = nullptr; + PFN_vkCmdDrawMeshTasksIndirectCountNV cmdDrawMeshTasksIndirectCount = nullptr; + } MeshShaderFunctions; + + void InitMeshShaderDrawFunctions(vk::Device device) + { + MeshShaderFunctions.cmdDrawMeshTasks = PFN_vkCmdDrawMeshTasksNV(device.getProcAddr("vkCmdDrawMeshTasksNV")); + MeshShaderFunctions.cmdDrawMeshTasksIndirect = PFN_vkCmdDrawMeshTasksIndirectNV(device.getProcAddr("vkCmdDrawMeshTasksIndirectNV")); + MeshShaderFunctions.cmdDrawMeshTasksIndirectCount = PFN_vkCmdDrawMeshTasksIndirectCountNV (device.getProcAddr( "vkCmdDrawMeshTasksIndirectCountNV")); + } + + void recordMeshShaderDrawcall( + vk::CommandBuffer cmdBuffer, + vk::PipelineLayout pipelineLayout, + const PushConstants& pushConstantData, + const uint32_t pushConstantOffset, + const MeshShaderDrawcall& drawcall, + const uint32_t firstTask) { + + for (const auto& descriptorUsage : drawcall.descriptorSets) { + cmdBuffer.bindDescriptorSets( + vk::PipelineBindPoint::eGraphics, + pipelineLayout, + descriptorUsage.setLocation, + descriptorUsage.vulkanHandle, + nullptr); + } + + // char* cast because void* does not support pointer arithmetic + const void* drawcallPushConstantData = pushConstantOffset + (char*)pushConstantData.getData(); + + if (pushConstantData.getData()) { + cmdBuffer.pushConstants( + pipelineLayout, + vk::ShaderStageFlagBits::eAll, + 0, + pushConstantData.getSizePerDrawcall(), + drawcallPushConstantData); } + + MeshShaderFunctions.cmdDrawMeshTasks(VkCommandBuffer(cmdBuffer), drawcall.taskCount, firstTask); } } diff --git a/src/vkcv/File.cpp b/src/vkcv/File.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6006b90f74e0a41f83483f2a1efbe5bda4c4e9f8 --- /dev/null +++ b/src/vkcv/File.cpp @@ -0,0 +1,60 @@ + +#include "vkcv/File.hpp" + +#include <stdlib.h> + +#ifdef _WIN32 +#include <io.h> +#else +#include <unistd.h> +#endif + +#include "vkcv/Logger.hpp" + +namespace vkcv { + + std::filesystem::path generateTemporaryFilePath() { + std::filesystem::path tmp = generateTemporaryDirectoryPath(); + + if (std::filesystem::is_directory(tmp)) { + return std::filesystem::path(tmp.string() + "W"); // add W for Wambo + } else { + return tmp; + } + } + + std::filesystem::path generateTemporaryDirectoryPath() { + std::error_code code; + auto tmp = std::filesystem::temp_directory_path(code); + + if (tmp.empty()) { + tmp = std::filesystem::current_path(); + } + + char name [16] = "vkcv_tmp_XXXXXX"; + +#ifdef _WIN32 + int err = _mktemp_s(name, 16); + + if (err != 0) { + vkcv_log(LogLevel::ERROR, "Temporary file path could not be generated"); + return ""; + } +#else + int fd = mkstemp(name); // creates a file locally + + if (fd == -1) { + vkcv_log(LogLevel::ERROR, "Temporary file path could not be generated"); + return ""; + } + + close(fd); + remove(name); // removes the local file again +#endif + + return tmp / name; + } + + + +} diff --git a/src/vkcv/Handles.cpp b/src/vkcv/Handles.cpp index 020489418c8e2db6ce2062d6fd20f06f90a05c37..65fc02dedeba39953c173103efe9b228f49e5d7f 100644 --- a/src/vkcv/Handles.cpp +++ b/src/vkcv/Handles.cpp @@ -1,5 +1,7 @@ #include "vkcv/Handles.hpp" +#include <iostream> + namespace vkcv { Handle::Handle() : @@ -11,7 +13,7 @@ namespace vkcv { {} Handle::~Handle() { - if ((m_rc) && (--(*m_rc) == 0)) { + if ((m_rc) && (*m_rc > 0) && (--(*m_rc) == 0)) { if (m_destroy) { m_destroy(m_id); } @@ -82,9 +84,9 @@ namespace vkcv { std::ostream& operator << (std::ostream& out, const Handle& handle) { if (handle) { - return out << "[Handle: " << handle.getId() << ":" << handle.getRC() << "]"; + return out << "[" << typeid(handle).name() << ": " << handle.getId() << ":" << handle.getRC() << "]"; } else { - return out << "[Handle: none]"; + return out << "[" << typeid(handle).name() << ": none]"; } } diff --git a/src/vkcv/Image.cpp b/src/vkcv/Image.cpp index f8d94b734599cbf1f55aad7b590ab4796501d951..15a2fc5240176742f50141407a3c72b531757ee9 100644 --- a/src/vkcv/Image.cpp +++ b/src/vkcv/Image.cpp @@ -56,7 +56,7 @@ namespace vkcv{ m_manager->switchImageLayoutImmediate(m_handle, newLayout); } - vkcv::ImageHandle Image::getHandle() const { + const vkcv::ImageHandle& Image::getHandle() const { return m_handle; } @@ -64,7 +64,7 @@ namespace vkcv{ return m_manager->getImageMipCount(m_handle); } - void Image::fill(void *data, size_t size) { + void Image::fill(const void *data, size_t size) { m_manager->fillImage(m_handle, data, size); } diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp index ae554e6babdd2b2f42c352515c02a34e45182fec..4ddd7f8c44c6023a80831bc8b4b092692e84ec86 100644 --- a/src/vkcv/ImageManager.cpp +++ b/src/vkcv/ImageManager.cpp @@ -12,26 +12,6 @@ namespace vkcv { - ImageManager::Image::Image( - vk::Image handle, - vk::DeviceMemory memory, - std::vector<vk::ImageView> views, - uint32_t width, - uint32_t height, - uint32_t depth, - vk::Format format, - uint32_t layers) - : - m_handle(handle), - m_memory(memory), - m_viewPerMip(views), - m_width(width), - m_height(height), - m_depth(depth), - m_format(format), - m_layers(layers) - {} - /** * @brief searches memory type index for image allocation, combines requirements of image and application * @param physicalMemoryProperties Memory Properties of physical device @@ -67,7 +47,8 @@ namespace vkcv { for (uint64_t id = 0; id < m_images.size(); id++) { destroyImageById(id); } - for (const auto swapchainImage : m_swapchainImages) { + + for (const auto& swapchainImage : m_swapchainImages) { for (const auto view : swapchainImage.m_viewPerMip) { m_core->getContext().getDevice().destroy(view); } @@ -123,7 +104,7 @@ namespace vkcv { imageUsageFlags |= vk::ImageUsageFlagBits::eDepthStencilAttachment; } - const vk::Device& device = m_core->getContext().getDevice(); + const vma::Allocator& allocator = m_core->getContext().getAllocator(); vk::ImageType imageType = vk::ImageType::e3D; vk::ImageViewType imageViewType = vk::ImageViewType::e3D; @@ -157,7 +138,7 @@ namespace vkcv { vk::SampleCountFlagBits sampleCountFlag = msaaToVkSampleCountFlag(msaa); - const vk::ImageCreateInfo imageCreateInfo( + const vk::ImageCreateInfo imageCreateInfo ( createFlags, imageType, format, @@ -171,21 +152,22 @@ namespace vkcv { {}, vk::ImageLayout::eUndefined ); - - vk::Image image = device.createImage(imageCreateInfo); - const vk::MemoryRequirements requirements = device.getImageMemoryRequirements(image); - - vk::MemoryPropertyFlags memoryTypeFlags = vk::MemoryPropertyFlagBits::eDeviceLocal; - - const uint32_t memoryTypeIndex = searchImageMemoryType( - physicalDevice.getMemoryProperties(), - requirements.memoryTypeBits, - memoryTypeFlags + auto imageAllocation = allocator.createImage( + imageCreateInfo, + vma::AllocationCreateInfo( + vma::AllocationCreateFlags(), + vma::MemoryUsage::eGpuOnly, + vk::MemoryPropertyFlagBits::eDeviceLocal, + vk::MemoryPropertyFlagBits::eDeviceLocal, + 0, + vma::Pool(), + nullptr + ) ); - vk::DeviceMemory memory = device.allocateMemory(vk::MemoryAllocateInfo(requirements.size, memoryTypeIndex)); - device.bindImageMemory(image, memory, 0); + vk::Image image = imageAllocation.first; + vma::Allocation allocation = imageAllocation.second; vk::ImageAspectFlags aspectFlags; @@ -195,8 +177,10 @@ namespace vkcv { aspectFlags = vk::ImageAspectFlagBits::eColor; } + const vk::Device& device = m_core->getContext().getDevice(); + std::vector<vk::ImageView> views; - for (int mip = 0; mip < mipCount; mip++) { + for (uint32_t mip = 0; mip < mipCount; mip++) { const vk::ImageViewCreateInfo imageViewCreateInfo( {}, image, @@ -221,11 +205,11 @@ namespace vkcv { } const uint64_t id = m_images.size(); - m_images.push_back(Image(image, memory, views, width, height, depth, format, arrayLayers)); + m_images.push_back({ image, allocation, views, width, height, depth, format, arrayLayers, vk::ImageLayout::eUndefined }); return ImageHandle(id, [&](uint64_t id) { destroyImageById(id); }); } - ImageHandle ImageManager::createSwapchainImage() { + ImageHandle ImageManager::createSwapchainImage() const { return ImageHandle::createSwapchainImageHandle(); } @@ -261,10 +245,16 @@ namespace vkcv { auto& image = m_images[id]; - return image.m_memory; + const vma::Allocator& allocator = m_core->getContext().getAllocator(); + + auto info = allocator.getAllocationInfo( + image.m_allocation + ); + + return info.deviceMemory; } - vk::ImageView ImageManager::getVulkanImageView(const ImageHandle &handle, const size_t mipLevel) const { + vk::ImageView ImageManager::getVulkanImageView(const ImageHandle &handle, size_t mipLevel) const { if (handle.isSwapchainImage()) { return m_swapchainImages[m_currentSwapchainInputImage].m_viewPerMip[0]; @@ -369,7 +359,7 @@ namespace vkcv { } } - void ImageManager::fillImage(const ImageHandle& handle, void* data, size_t size) + void ImageManager::fillImage(const ImageHandle& handle, const void* data, size_t size) { const uint64_t id = handle.getId(); @@ -397,7 +387,7 @@ namespace vkcv { const size_t max_size = std::min(size, image_size); BufferHandle bufferHandle = m_bufferManager.createBuffer( - BufferType::STAGING, max_size, BufferMemoryType::HOST_VISIBLE + BufferType::STAGING, max_size, BufferMemoryType::HOST_VISIBLE, false ); m_bufferManager.fillBuffer(bufferHandle, data, max_size, 0); @@ -504,9 +494,6 @@ namespace vkcv { } void ImageManager::generateImageMipChainImmediate(const ImageHandle& handle) { - - const auto& device = m_core->getContext().getDevice(); - SubmitInfo submitInfo; submitInfo.queueType = QueueType::Graphics; @@ -628,15 +615,14 @@ namespace vkcv { view = nullptr; } } - - if (image.m_memory) { - device.freeMemory(image.m_memory); - image.m_memory = nullptr; - } + + const vma::Allocator& allocator = m_core->getContext().getAllocator(); if (image.m_handle) { - device.destroyImage(image.m_handle); + allocator.destroyImage(image.m_handle, image.m_allocation); + image.m_handle = nullptr; + image.m_allocation = nullptr; } } @@ -655,7 +641,6 @@ namespace vkcv { uint32_t ImageManager::getImageMipCount(const ImageHandle& handle) const { const uint64_t id = handle.getId(); - const bool isSwapchainFormat = handle.isSwapchainImage(); if (handle.isSwapchainImage()) { return 1; @@ -673,11 +658,11 @@ namespace vkcv { m_currentSwapchainInputImage = index; } - void ImageManager::setSwapchainImages(const std::vector<vk::Image>& images, std::vector<vk::ImageView> views, + void ImageManager::setSwapchainImages(const std::vector<vk::Image>& images, const std::vector<vk::ImageView>& views, uint32_t width, uint32_t height, vk::Format format) { // destroy old views - for (auto image : m_swapchainImages) { + for (const auto& image : m_swapchainImages) { for (const auto& view : image.m_viewPerMip) { m_core->getContext().getDevice().destroyImageView(view); } @@ -685,8 +670,18 @@ namespace vkcv { assert(images.size() == views.size()); m_swapchainImages.clear(); - for (int i = 0; i < images.size(); i++) { - m_swapchainImages.push_back(Image(images[i], nullptr, { views[i] }, width, height, 1, format, 1)); + for (size_t i = 0; i < images.size(); i++) { + m_swapchainImages.push_back({ + images[i], + nullptr, + { views[i] }, + width, + height, + 1, + format, + 1, + vk::ImageLayout::eUndefined + }); } } diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp index 1d8ce207b645e30cee291816eac3c934ed40e92a..4d99422118e8d464ea75d9f013b471f3dd40fd8c 100644 --- a/src/vkcv/ImageManager.hpp +++ b/src/vkcv/ImageManager.hpp @@ -6,12 +6,15 @@ */ #include <vector> #include <vulkan/vulkan.hpp> +#include <vk_mem_alloc.hpp> #include "vkcv/BufferManager.hpp" #include "vkcv/Handles.hpp" #include "vkcv/ImageConfig.hpp" namespace vkcv { + + bool isDepthImageFormat(vk::Format format); class ImageManager { @@ -20,28 +23,16 @@ namespace vkcv { struct Image { vk::Image m_handle; - vk::DeviceMemory m_memory; + vma::Allocation m_allocation; std::vector<vk::ImageView> m_viewPerMip; - uint32_t m_width = 0; - uint32_t m_height = 0; - uint32_t m_depth = 0; + uint32_t m_width; + uint32_t m_height; + uint32_t m_depth; vk::Format m_format; - uint32_t m_layers = 1; - vk::ImageLayout m_layout = vk::ImageLayout::eUndefined; + uint32_t m_layers; + vk::ImageLayout m_layout; private: - // struct is public so utility functions can access members, but only ImageManager can create Image friend ImageManager; - Image( - vk::Image handle, - vk::DeviceMemory memory, - std::vector<vk::ImageView> views, - uint32_t width, - uint32_t height, - uint32_t depth, - vk::Format format, - uint32_t layers); - - Image(); }; private: @@ -52,7 +43,7 @@ namespace vkcv { std::vector<Image> m_swapchainImages; int m_currentSwapchainInputImage; - ImageManager(BufferManager& bufferManager) noexcept; + explicit ImageManager(BufferManager& bufferManager) noexcept; /** * Destroys and deallocates image represented by a given @@ -82,7 +73,8 @@ namespace vkcv { bool supportColorAttachment, Multisampling msaa); - ImageHandle createSwapchainImage(); + [[nodiscard]] + ImageHandle createSwapchainImage() const; [[nodiscard]] vk::Image getVulkanImage(const ImageHandle& handle) const; @@ -91,7 +83,7 @@ namespace vkcv { vk::DeviceMemory getVulkanDeviceMemory(const ImageHandle& handle) const; [[nodiscard]] - vk::ImageView getVulkanImageView(const ImageHandle& handle, const size_t mipLevel = 0) const; + vk::ImageView getVulkanImageView(const ImageHandle& handle, size_t mipLevel = 0) const; void switchImageLayoutImmediate(const ImageHandle& handle, vk::ImageLayout newLayout); void recordImageLayoutTransition( @@ -103,7 +95,7 @@ namespace vkcv { const ImageHandle& handle, vk::CommandBuffer cmdBuffer); - void fillImage(const ImageHandle& handle, void* data, size_t size); + void fillImage(const ImageHandle& handle, const void* data, size_t size); void generateImageMipChainImmediate(const ImageHandle& handle); void recordImageMipChainGenerationToCmdStream(const vkcv::CommandStreamHandle& cmdStream, const ImageHandle& handle); void recordMSAAResolve(vk::CommandBuffer cmdBuffer, ImageHandle src, ImageHandle dst); @@ -124,8 +116,9 @@ namespace vkcv { uint32_t getImageMipCount(const ImageHandle& handle) const; void setCurrentSwapchainImageIndex(int index); - void setSwapchainImages(const std::vector<vk::Image>& images, std::vector<vk::ImageView> views, - uint32_t width, uint32_t height, vk::Format format); + + void setSwapchainImages(const std::vector<vk::Image>& images, const std::vector<vk::ImageView>& views, + uint32_t width, uint32_t height, vk::Format format); }; } \ No newline at end of file diff --git a/src/vkcv/PipelineManager.cpp b/src/vkcv/PipelineManager.cpp index 8b1f0b68be3a72f60103ca0dd8136f2c923513a5..244f6723f70e5ea938c005b74b286e192d68443c 100644 --- a/src/vkcv/PipelineManager.cpp +++ b/src/vkcv/PipelineManager.cpp @@ -44,95 +44,190 @@ namespace vkcv vk::PrimitiveTopology primitiveTopologyToVulkanPrimitiveTopology(const PrimitiveTopology topology) { switch (topology) { - case(PrimitiveTopology::PointList): return vk::PrimitiveTopology::ePointList; - case(PrimitiveTopology::LineList): return vk::PrimitiveTopology::eLineList; - case(PrimitiveTopology::TriangleList): return vk::PrimitiveTopology::eTriangleList; - default: std::cout << "Error: Unknown primitive topology type" << std::endl; return vk::PrimitiveTopology::eTriangleList; + case(PrimitiveTopology::PointList): return vk::PrimitiveTopology::ePointList; + case(PrimitiveTopology::LineList): return vk::PrimitiveTopology::eLineList; + case(PrimitiveTopology::TriangleList): return vk::PrimitiveTopology::eTriangleList; + default: std::cout << "Error: Unknown primitive topology type" << std::endl; return vk::PrimitiveTopology::eTriangleList; } } vk::CompareOp depthTestToVkCompareOp(DepthTest depthTest) { switch (depthTest) { - case(DepthTest::None): return vk::CompareOp::eAlways; - case(DepthTest::Less): return vk::CompareOp::eLess; - case(DepthTest::LessEqual): return vk::CompareOp::eLessOrEqual; - case(DepthTest::Greater): return vk::CompareOp::eGreater; - case(DepthTest::GreatherEqual): return vk::CompareOp::eGreaterOrEqual; - case(DepthTest::Equal): return vk::CompareOp::eEqual; - default: vkcv_log(vkcv::LogLevel::ERROR, "Unknown depth test enum"); return vk::CompareOp::eAlways; + case(DepthTest::None): return vk::CompareOp::eAlways; + case(DepthTest::Less): return vk::CompareOp::eLess; + case(DepthTest::LessEqual): return vk::CompareOp::eLessOrEqual; + case(DepthTest::Greater): return vk::CompareOp::eGreater; + case(DepthTest::GreatherEqual): return vk::CompareOp::eGreaterOrEqual; + case(DepthTest::Equal): return vk::CompareOp::eEqual; + default: vkcv_log(vkcv::LogLevel::ERROR, "Unknown depth test enum"); return vk::CompareOp::eAlways; } } + + vk::ShaderStageFlagBits shaderStageToVkShaderStage(vkcv::ShaderStage stage) { + switch (stage) { + case vkcv::ShaderStage::VERTEX: return vk::ShaderStageFlagBits::eVertex; + case vkcv::ShaderStage::FRAGMENT: return vk::ShaderStageFlagBits::eFragment; + case vkcv::ShaderStage::GEOMETRY: return vk::ShaderStageFlagBits::eGeometry; + case vkcv::ShaderStage::TESS_CONTROL: return vk::ShaderStageFlagBits::eTessellationControl; + case vkcv::ShaderStage::TESS_EVAL: return vk::ShaderStageFlagBits::eTessellationEvaluation; + case vkcv::ShaderStage::COMPUTE: return vk::ShaderStageFlagBits::eCompute; + case vkcv::ShaderStage::TASK: return vk::ShaderStageFlagBits::eTaskNV; + case vkcv::ShaderStage::MESH: return vk::ShaderStageFlagBits::eMeshNV; + default: vkcv_log(vkcv::LogLevel::ERROR, "Unknown shader stage"); return vk::ShaderStageFlagBits::eAll; + } + } + + bool createPipelineShaderStageCreateInfo( + const vkcv::ShaderProgram& shaderProgram, + ShaderStage stage, + vk::Device device, + vk::PipelineShaderStageCreateInfo* outCreateInfo) { + + assert(outCreateInfo); + std::vector<char> code = shaderProgram.getShader(stage).shaderCode; + vk::ShaderModuleCreateInfo vertexModuleInfo({}, code.size(), reinterpret_cast<uint32_t*>(code.data())); + vk::ShaderModule shaderModule; + if (device.createShaderModule(&vertexModuleInfo, nullptr, &shaderModule) != vk::Result::eSuccess) + return false; + + const static auto entryName = "main"; + + *outCreateInfo = vk::PipelineShaderStageCreateInfo( + {}, + shaderStageToVkShaderStage(stage), + shaderModule, + entryName, + nullptr); + return true; + } PipelineHandle PipelineManager::createPipeline(const PipelineConfig &config, PassManager& passManager) { const vk::RenderPass &pass = passManager.getVkPass(config.m_PassHandle); + const bool existsTaskShader = config.m_ShaderProgram.existsShader(ShaderStage::TASK); + const bool existsMeshShader = config.m_ShaderProgram.existsShader(ShaderStage::MESH); + const bool existsVertexShader = config.m_ShaderProgram.existsShader(ShaderStage::VERTEX); + + const bool validGeometryStages = existsVertexShader || (existsTaskShader && existsMeshShader); + const bool existsFragmentShader = config.m_ShaderProgram.existsShader(ShaderStage::FRAGMENT); - if (!(existsVertexShader && existsFragmentShader)) + if (!validGeometryStages) { - vkcv_log(LogLevel::ERROR, "Requires vertex and fragment shader code"); + vkcv_log(LogLevel::ERROR, "Requires vertex or task and mesh shader"); return PipelineHandle(); } - - // vertex shader stage - std::vector<char> vertexCode = config.m_ShaderProgram.getShader(ShaderStage::VERTEX).shaderCode; - vk::ShaderModuleCreateInfo vertexModuleInfo({}, vertexCode.size(), reinterpret_cast<uint32_t*>(vertexCode.data())); - vk::ShaderModule vertexModule{}; - if (m_Device.createShaderModule(&vertexModuleInfo, nullptr, &vertexModule) != vk::Result::eSuccess) + if (!existsFragmentShader) { + vkcv_log(LogLevel::ERROR, "Requires fragment shader code"); return PipelineHandle(); + } - vk::PipelineShaderStageCreateInfo pipelineVertexShaderStageInfo( - {}, - vk::ShaderStageFlagBits::eVertex, - vertexModule, - "main", - nullptr - ); + std::vector<vk::PipelineShaderStageCreateInfo> shaderStages; + auto destroyShaderModules = [&shaderStages, this] { + for (auto stage : shaderStages) { + m_Device.destroyShaderModule(stage.module); + } + shaderStages.clear(); + }; + + if (existsVertexShader) { + vk::PipelineShaderStageCreateInfo createInfo; + const bool success = createPipelineShaderStageCreateInfo( + config.m_ShaderProgram, + vkcv::ShaderStage::VERTEX, + m_Device, + &createInfo); + + if (success) { + shaderStages.push_back(createInfo); + } + else { + destroyShaderModules(); + return PipelineHandle(); + } + } + + if (existsTaskShader) { + vk::PipelineShaderStageCreateInfo createInfo; + const bool success = createPipelineShaderStageCreateInfo( + config.m_ShaderProgram, + vkcv::ShaderStage::TASK, + m_Device, + &createInfo); + + if (success) { + shaderStages.push_back(createInfo); + } + else { + destroyShaderModules(); + return PipelineHandle(); + } + } + + if (existsMeshShader) { + vk::PipelineShaderStageCreateInfo createInfo; + const bool success = createPipelineShaderStageCreateInfo( + config.m_ShaderProgram, + vkcv::ShaderStage::MESH, + m_Device, + &createInfo); + + if (success) { + shaderStages.push_back(createInfo); + } + else { + destroyShaderModules(); + return PipelineHandle(); + } + } // fragment shader stage - std::vector<char> fragCode = config.m_ShaderProgram.getShader(ShaderStage::FRAGMENT).shaderCode; - vk::ShaderModuleCreateInfo fragmentModuleInfo({}, fragCode.size(), reinterpret_cast<uint32_t*>(fragCode.data())); - vk::ShaderModule fragmentModule{}; - if (m_Device.createShaderModule(&fragmentModuleInfo, nullptr, &fragmentModule) != vk::Result::eSuccess) { - m_Device.destroy(vertexModule); - return PipelineHandle(); + vk::PipelineShaderStageCreateInfo createInfo; + const bool success = createPipelineShaderStageCreateInfo( + config.m_ShaderProgram, + vkcv::ShaderStage::FRAGMENT, + m_Device, + &createInfo); + + if (success) { + shaderStages.push_back(createInfo); + } + else { + destroyShaderModules(); + return PipelineHandle(); + } } - vk::PipelineShaderStageCreateInfo pipelineFragmentShaderStageInfo( - {}, - vk::ShaderStageFlagBits::eFragment, - fragmentModule, - "main", - nullptr - ); - // vertex input state // Fill up VertexInputBindingDescription and VertexInputAttributeDescription Containers std::vector<vk::VertexInputAttributeDescription> vertexAttributeDescriptions; std::vector<vk::VertexInputBindingDescription> vertexBindingDescriptions; - const VertexLayout &layout = config.m_VertexLayout; - - // iterate over the layout's specified, mutually exclusive buffer bindings that make up a vertex buffer - for (const auto &vertexBinding : layout.vertexBindings) - { - vertexBindingDescriptions.emplace_back(vertexBinding.bindingLocation, - vertexBinding.stride, - vk::VertexInputRate::eVertex); - - // iterate over the bindings' specified, mutually exclusive vertex input attachments that make up a vertex - for(const auto &vertexAttachment: vertexBinding.vertexAttachments) - { - vertexAttributeDescriptions.emplace_back(vertexAttachment.inputLocation, - vertexBinding.bindingLocation, - vertexFormatToVulkanFormat(vertexAttachment.format), - vertexAttachment.offset % vertexBinding.stride); + if (existsVertexShader) { + const VertexLayout& layout = config.m_VertexLayout; + + // iterate over the layout's specified, mutually exclusive buffer bindings that make up a vertex buffer + for (const auto& vertexBinding : layout.vertexBindings) + { + vertexBindingDescriptions.emplace_back(vertexBinding.bindingLocation, + vertexBinding.stride, + vk::VertexInputRate::eVertex); + + // iterate over the bindings' specified, mutually exclusive vertex input attachments that make up a vertex + for (const auto& vertexAttachment : vertexBinding.vertexAttachments) + { + vertexAttributeDescriptions.emplace_back(vertexAttachment.inputLocation, + vertexBinding.bindingLocation, + vertexFormatToVulkanFormat(vertexAttachment.format), + vertexAttachment.offset % vertexBinding.stride); + + } + } - } - } + } // Handover Containers to PipelineVertexInputStateCreateIngo Struct vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo( @@ -240,8 +335,7 @@ namespace vkcv vk::PipelineLayout vkPipelineLayout{}; if (m_Device.createPipelineLayout(&pipelineLayoutCreateInfo, nullptr, &vkPipelineLayout) != vk::Result::eSuccess) { - m_Device.destroy(vertexModule); - m_Device.destroy(fragmentModule); + destroyShaderModules(); return PipelineHandle(); } @@ -276,25 +370,28 @@ namespace vkcv dynamicStates.push_back(vk::DynamicState::eScissor); } - vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo({}, - static_cast<uint32_t>(dynamicStates.size()), - dynamicStates.data()); - - // graphics pipeline create - std::vector<vk::PipelineShaderStageCreateInfo> shaderStages = { pipelineVertexShaderStageInfo, pipelineFragmentShaderStageInfo }; - - const char *geometryShaderName = "main"; // outside of if to make sure it stays in scope - vk::ShaderModule geometryModule; - if (config.m_ShaderProgram.existsShader(ShaderStage::GEOMETRY)) { - const vkcv::Shader geometryShader = config.m_ShaderProgram.getShader(ShaderStage::GEOMETRY); - const auto& geometryCode = geometryShader.shaderCode; - const vk::ShaderModuleCreateInfo geometryModuleInfo({}, geometryCode.size(), reinterpret_cast<const uint32_t*>(geometryCode.data())); - if (m_Device.createShaderModule(&geometryModuleInfo, nullptr, &geometryModule) != vk::Result::eSuccess) { - return PipelineHandle(); - } - vk::PipelineShaderStageCreateInfo geometryStage({}, vk::ShaderStageFlagBits::eGeometry, geometryModule, geometryShaderName); - shaderStages.push_back(geometryStage); - } + vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo( + {}, + static_cast<uint32_t>(dynamicStates.size()), + dynamicStates.data()); + + const bool existsGeometryShader = config.m_ShaderProgram.existsShader(vkcv::ShaderStage::GEOMETRY); + if (existsGeometryShader) { + vk::PipelineShaderStageCreateInfo createInfo; + const bool success = createPipelineShaderStageCreateInfo( + config.m_ShaderProgram, + vkcv::ShaderStage::GEOMETRY, + m_Device, + &createInfo); + + if (success) { + shaderStages.push_back(createInfo); + } + else { + destroyShaderModules(); + return PipelineHandle(); + } + } const vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo( {}, @@ -319,20 +416,11 @@ namespace vkcv vk::Pipeline vkPipeline{}; if (m_Device.createGraphicsPipelines(nullptr, 1, &graphicsPipelineCreateInfo, nullptr, &vkPipeline) != vk::Result::eSuccess) { - m_Device.destroy(vertexModule); - m_Device.destroy(fragmentModule); - if (geometryModule) { - m_Device.destroy(geometryModule); - } - m_Device.destroy(); + destroyShaderModules(); return PipelineHandle(); } - m_Device.destroy(vertexModule); - m_Device.destroy(fragmentModule); - if (geometryModule) { - m_Device.destroy(geometryModule); - } + destroyShaderModules(); const uint64_t id = m_Pipelines.size(); m_Pipelines.push_back({ vkPipeline, vkPipelineLayout, config }); @@ -457,4 +545,4 @@ namespace vkcv vk::ShaderModuleCreateInfo moduleInfo({}, code.size(), reinterpret_cast<uint32_t*>(code.data())); return m_Device.createShaderModule(&moduleInfo, nullptr, &module); } -} \ No newline at end of file +} diff --git a/src/vkcv/QueueManager.cpp b/src/vkcv/QueueManager.cpp index df6c74cccf6c4652adc6a4c78802f282ea6ae293..15e958b0de929e53170324ade27a9b3663a15d6a 100644 --- a/src/vkcv/QueueManager.cpp +++ b/src/vkcv/QueueManager.cpp @@ -27,8 +27,8 @@ namespace vkcv { * @throws std::runtime_error If the requested queues from @p queueFlags are not creatable due to insufficient availability. */ void QueueManager::queueCreateInfosQueueHandles(vk::PhysicalDevice &physicalDevice, - std::vector<float> &queuePriorities, - std::vector<vk::QueueFlagBits> &queueFlags, + const std::vector<float> &queuePriorities, + const std::vector<vk::QueueFlagBits> &queueFlags, std::vector<vk::DeviceQueueCreateInfo> &queueCreateInfos, std::vector<std::pair<int, int>> &queuePairsGraphics, std::vector<std::pair<int, int>> &queuePairsCompute, @@ -51,7 +51,7 @@ namespace vkcv { } //resort flags with heighest priority before allocating the queues std::vector<vk::QueueFlagBits> newFlags; - for(int i = 0; i < prios.size(); i++) { + for(size_t i = 0; i < prios.size(); i++) { auto minElem = std::min_element(prios.begin(), prios.end()); int index = minElem - prios.begin(); newFlags.push_back(queueFlags[index]); @@ -79,7 +79,7 @@ namespace vkcv { switch (qFlag) { case vk::QueueFlagBits::eGraphics: found = false; - for (int i = 0; i < queueFamilyStatus.size() && !found; i++) { + for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) { if (queueFamilyStatus[i][0] > 0) { queuePairsGraphics.push_back(std::pair(i, initialQueueFamilyStatus[i][0] - queueFamilyStatus[i][0])); queueFamilyStatus[i][0]--; @@ -89,7 +89,7 @@ namespace vkcv { } } if (!found) { - for (int i = 0; i < queueFamilyStatus.size() && !found; i++) { + for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) { if (initialQueueFamilyStatus[i][0] > 0) { queuePairsGraphics.push_back(std::pair(i, 0)); found = true; @@ -101,7 +101,7 @@ namespace vkcv { break; case vk::QueueFlagBits::eCompute: found = false; - for (int i = 0; i < queueFamilyStatus.size() && !found; i++) { + for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) { if (queueFamilyStatus[i][1] > 0) { queuePairsCompute.push_back(std::pair(i, initialQueueFamilyStatus[i][1] - queueFamilyStatus[i][1])); queueFamilyStatus[i][0]--; @@ -111,7 +111,7 @@ namespace vkcv { } } if (!found) { - for (int i = 0; i < queueFamilyStatus.size() && !found; i++) { + for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) { if (initialQueueFamilyStatus[i][1] > 0) { queuePairsCompute.push_back(std::pair(i, 0)); found = true; @@ -123,7 +123,7 @@ namespace vkcv { break; case vk::QueueFlagBits::eTransfer: found = false; - for (int i = 0; i < queueFamilyStatus.size() && !found; i++) { + for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) { if (queueFamilyStatus[i][2] > 0) { queuePairsTransfer.push_back(std::pair(i, initialQueueFamilyStatus[i][2] - queueFamilyStatus[i][2])); queueFamilyStatus[i][0]--; @@ -133,7 +133,7 @@ namespace vkcv { } } if (!found) { - for (int i = 0; i < queueFamilyStatus.size() && !found; i++) { + for (size_t i = 0; i < queueFamilyStatus.size() && !found; i++) { if (initialQueueFamilyStatus[i][2] > 0) { queuePairsTransfer.push_back(std::pair(i, 0)); found = true; @@ -149,7 +149,7 @@ namespace vkcv { } // create all requested queues - for (int i = 0; i < qFamilyProperties.size(); i++) { + for (size_t i = 0; i < qFamilyProperties.size(); i++) { uint32_t create = std::abs(initialQueueFamilyStatus[i][0] - queueFamilyStatus[i][0]); if (create > 0) { vk::DeviceQueueCreateInfo qCreateInfo( diff --git a/src/vkcv/SamplerManager.cpp b/src/vkcv/SamplerManager.cpp index a6ebb95b5e237dcd06ed8041b3f16489f7339d6a..792e6f16b4a05af41a164a1eda9dd7423594857e 100644 --- a/src/vkcv/SamplerManager.cpp +++ b/src/vkcv/SamplerManager.cpp @@ -17,7 +17,8 @@ namespace vkcv { SamplerHandle SamplerManager::createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter, SamplerMipmapMode mipmapMode, - SamplerAddressMode addressMode) { + SamplerAddressMode addressMode, + float mipLodBias) { vk::Filter vkMagFilter; vk::Filter vkMinFilter; vk::SamplerMipmapMode vkMipmapMode; @@ -81,13 +82,13 @@ namespace vkcv { vkAddressMode, vkAddressMode, vkAddressMode, - 0.0f, + mipLodBias, false, 16.0f, false, vk::CompareOp::eAlways, - 0.0f, - 16.0f, + -1000.0f, + 1000.0f, vk::BorderColor::eIntOpaqueBlack, false ); diff --git a/src/vkcv/SamplerManager.hpp b/src/vkcv/SamplerManager.hpp index 511176d4f87633a8691ca730ecc383e2588d8cf0..aea47a03714b417314a09dfc0be855df31fbb557 100644 --- a/src/vkcv/SamplerManager.hpp +++ b/src/vkcv/SamplerManager.hpp @@ -32,7 +32,8 @@ namespace vkcv { SamplerHandle createSampler(SamplerFilterType magFilter, SamplerFilterType minFilter, SamplerMipmapMode mipmapMode, - SamplerAddressMode addressMode); + SamplerAddressMode addressMode, + float mipLodBias); [[nodiscard]] vk::Sampler getVulkanSampler(const SamplerHandle& handle) const; diff --git a/src/vkcv/Swapchain.cpp b/src/vkcv/Swapchain.cpp index 94e7301d66bfcc513434ef6d22520d1b95f98161..d0aa26db9c661ea40caf06349a72cc9188e791a9 100644 --- a/src/vkcv/Swapchain.cpp +++ b/src/vkcv/Swapchain.cpp @@ -100,18 +100,14 @@ namespace vkcv * @return available Format */ vk::SurfaceFormatKHR chooseSurfaceFormat(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) { - uint32_t formatCount; - physicalDevice.getSurfaceFormatsKHR(surface, &formatCount, nullptr); - std::vector<vk::SurfaceFormatKHR> availableFormats(formatCount); - if (physicalDevice.getSurfaceFormatsKHR(surface, &formatCount, &availableFormats[0]) != vk::Result::eSuccess) { - throw std::runtime_error("Failed to get surface formats"); - } + std::vector<vk::SurfaceFormatKHR> availableFormats = physicalDevice.getSurfaceFormatsKHR(surface); for (const auto& availableFormat : availableFormats) { if (availableFormat.format == vk::Format::eB8G8R8A8Unorm && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { return availableFormat; } } + return availableFormats[0]; } @@ -122,12 +118,7 @@ namespace vkcv * @return available PresentationMode */ vk::PresentModeKHR choosePresentMode(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) { - uint32_t modeCount; - physicalDevice.getSurfacePresentModesKHR( surface, &modeCount, nullptr ); - std::vector<vk::PresentModeKHR> availablePresentModes(modeCount); - if (physicalDevice.getSurfacePresentModesKHR(surface, &modeCount, &availablePresentModes[0]) != vk::Result::eSuccess) { - throw std::runtime_error("Failed to get presentation modes"); - } + std::vector<vk::PresentModeKHR> availablePresentModes = physicalDevice.getSurfacePresentModesKHR(surface); for (const auto& availablePresentMode : availablePresentModes) { if (availablePresentMode == vk::PresentModeKHR::eMailbox) { @@ -145,12 +136,11 @@ namespace vkcv * @return available ImageCount */ uint32_t chooseImageCount(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR surface) { - vk::SurfaceCapabilitiesKHR surfaceCapabilities; - if(physicalDevice.getSurfaceCapabilitiesKHR(surface, &surfaceCapabilities) != vk::Result::eSuccess){ - throw std::runtime_error("cannot get surface capabilities. There is an issue with the surface."); - } - - uint32_t imageCount = surfaceCapabilities.minImageCount + 1; // minImageCount should always be at least 2; set to 3 for triple buffering + vk::SurfaceCapabilitiesKHR surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface); + + // minImageCount should always be at least 2; set to 3 for triple buffering + uint32_t imageCount = surfaceCapabilities.minImageCount + 1; + // check if requested image count is supported if (surfaceCapabilities.maxImageCount > 0 && imageCount > surfaceCapabilities.maxImageCount) { imageCount = surfaceCapabilities.maxImageCount; @@ -215,8 +205,9 @@ namespace vkcv } void Swapchain::updateSwapchain(const Context &context, const Window &window) { - if (!m_RecreationRequired.exchange(false)) - return; + if (!m_RecreationRequired.exchange(false)) { + return; + } vk::SwapchainKHR oldSwapchain = m_Swapchain; vk::Extent2D extent2D = chooseExtent(context.getPhysicalDevice(), m_Surface.handle, window); diff --git a/src/vkcv/Window.cpp b/src/vkcv/Window.cpp index ea72582d67d5350e5fbf3f3c0fa2aae2ba407b0e..072efcd00eb6520fa4f20379721b559668339f6e 100644 --- a/src/vkcv/Window.cpp +++ b/src/vkcv/Window.cpp @@ -4,7 +4,10 @@ * @brief Window class to handle a basic rendering surface and input */ +#include <thread> +#include <vector> #include <GLFW/glfw3.h> + #include "vkcv/Window.hpp" namespace vkcv { @@ -78,12 +81,17 @@ namespace vkcv { window->e_key.unlock(); window->e_char.unlock(); window->e_gamepad.unlock(); - } + } + + glfwPollEvents(); - glfwPollEvents(); - - for (int gamepadIndex = GLFW_JOYSTICK_1; gamepadIndex <= GLFW_JOYSTICK_LAST; gamepadIndex++) { - if (glfwJoystickPresent(gamepadIndex)) { + // fixes subtle mouse stutter, which is made visible by motion blur + // FIXME: proper solution + // probably caused by main thread locking events before glfw callbacks are executed + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + for (int gamepadIndex = GLFW_JOYSTICK_1; gamepadIndex <= GLFW_JOYSTICK_LAST; gamepadIndex++) { + if (glfwJoystickPresent(gamepadIndex)) { onGamepadEvent(gamepadIndex); } } @@ -150,11 +158,15 @@ namespace vkcv { } void Window::onGamepadEvent(int gamepadIndex) { - int activeWindowIndex = std::find_if(s_Windows.begin(), - s_Windows.end(), - [](GLFWwindow* window){return glfwGetWindowAttrib(window, GLFW_FOCUSED);}) - - s_Windows.begin(); - activeWindowIndex *= (activeWindowIndex < s_Windows.size()); // fixes index getting out of bounds (e.g. if there is no focused window) + size_t activeWindowIndex = std::find_if( + s_Windows.begin(), + s_Windows.end(), + [](GLFWwindow* window){return glfwGetWindowAttrib(window, GLFW_FOCUSED);} + ) - s_Windows.begin(); + + // fixes index getting out of bounds (e.g. if there is no focused window) + activeWindowIndex *= (activeWindowIndex < s_Windows.size()); + auto window = static_cast<Window *>(glfwGetWindowUserPointer(s_Windows[activeWindowIndex])); if (window != nullptr) {