diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 84a1e902ace668fbee40346cf71e3c4c7a519f2e..63fda17212cf97f2857ac1891dfad9dd052cbe6a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,7 +23,7 @@ build_ubuntu_gcc: - mkdir debug - cd debug - cmake -DCMAKE_BUILD_TYPE=Debug .. - - cmake --build . + - cmake --build . -j 4 artifacts: name: "Documentation - $CI_PIPELINE_ID" paths: @@ -49,7 +49,7 @@ build_win10_msvc: - mkdir debug - cd debug - cmake -DCMAKE_BUILD_TYPE=Debug .. - - cmake --build . + - cmake --build . -j 4 build_win10_mingw: only: @@ -66,7 +66,7 @@ build_win10_mingw: - 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 8 + - cmake --build . -j 4 build_mac_clang: only: @@ -85,7 +85,7 @@ build_mac_clang: - 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 . + - cmake --build . -j 4 deploy_doc_develop: only: diff --git a/.gitmodules b/.gitmodules index 2b2337f667c695a14989e220dffb1b5b7440a579..4e2dfae87f214566b12c2d92b046c94cdea6b302 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,9 +22,6 @@ [submodule "modules/gui/lib/imgui"] path = modules/gui/lib/imgui url = https://github.com/ocornut/imgui.git -[submodule "lib/VulkanMemoryAllocator"] - path = lib/VulkanMemoryAllocator - url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git [submodule "lib/VulkanMemoryAllocator-Hpp"] path = lib/VulkanMemoryAllocator-Hpp url = https://github.com/malte-v/VulkanMemoryAllocator-Hpp.git diff --git a/CMakeLists.txt b/CMakeLists.txt index dfafe1cd084d4b324c233d502e301c24a5ee95e1..da150fcbeafec3be555d4bbefdab37dbdedf277f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ add_library(vkcv STATIC ${vkcv_sources}) if(MSVC) #enable multicore compilation on visual studio - target_compile_options(vkcv PRIVATE "/MP" "/openmp") + target_compile_options(vkcv PRIVATE "/MP" "/openmp" "/Zc:offsetof-") #set source groups to create proper filters in visual studio source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${vkcv_sources}) diff --git a/config/Sources.cmake b/config/Sources.cmake index 41cd0c20f2106dc02700d9b23227f3e6c34a057a..ea95d152280fde1dcfdd93e7e48d1f8b58d3bb9e 100644 --- a/config/Sources.cmake +++ b/config/Sources.cmake @@ -1,6 +1,12 @@ # adding all source files and header files of the framework: set(vkcv_sources + ${vkcv_include}/vkcv/Features.hpp + ${vkcv_source}/vkcv/Features.cpp + + ${vkcv_include}/vkcv/FeatureManager.hpp + ${vkcv_source}/vkcv/FeatureManager.cpp + ${vkcv_include}/vkcv/Context.hpp ${vkcv_source}/vkcv/Context.cpp 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 1f10c8dc4bbdf3142ffffcd6e1061fc83d52c0f6..cdea90ecbb96c8ff591b879e51548f92514ef230 100644 --- a/include/vkcv/BufferManager.hpp +++ b/include/vkcv/BufferManager.hpp @@ -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 824713fd1e29cbb8b7e60b22768c0019daaa9938..1160857cb2cdbbb0815390ff1f8405dda0f83796 100644 --- a/include/vkcv/Context.hpp +++ b/include/vkcv/Context.hpp @@ -5,6 +5,7 @@ #include "QueueManager.hpp" #include "DrawcallRecording.hpp" +#include "Features.hpp" namespace vkcv { @@ -32,6 +33,9 @@ namespace vkcv [[nodiscard]] const vk::Device &getDevice() const; + [[nodiscard]] + const FeatureManager& getFeatureManager() const; + [[nodiscard]] const QueueManager& getQueueManager() const; @@ -41,8 +45,8 @@ namespace vkcv static Context create(const char *applicationName, uint32_t applicationVersion, const std::vector<vk::QueueFlagBits>& queueFlags, - const std::vector<const char *>& instanceExtensions, - const std::vector<const char *>& deviceExtensions); + const Features& features, + const std::vector<const char*>& instanceExtensions = {}); private: /** @@ -53,11 +57,12 @@ namespace vkcv * @param device Vulkan-Device */ Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device, - QueueManager&& queueManager, vma::Allocator&& allocator) noexcept; + FeatureManager&& featureManager, QueueManager&& queueManager, vma::Allocator&& allocator) noexcept; vk::Instance m_Instance; vk::PhysicalDevice m_PhysicalDevice; vk::Device m_Device; + FeatureManager m_FeatureManager; QueueManager m_QueueManager; vma::Allocator m_Allocator; diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp index 50a5a8a452a930a772533fe7cf7c627540442461..caeab7bc7ad37c0f5abbfea64ce691d0367f0edb 100644 --- a/include/vkcv/Core.hpp +++ b/include/vkcv/Core.hpp @@ -7,14 +7,14 @@ #include <memory> #include <vulkan/vulkan.hpp> -#include "vkcv/Context.hpp" -#include "vkcv/Swapchain.hpp" -#include "vkcv/Window.hpp" -#include "vkcv/PassConfig.hpp" -#include "vkcv/Handles.hpp" -#include "vkcv/Buffer.hpp" -#include "vkcv/Image.hpp" -#include "vkcv/PipelineConfig.hpp" +#include "Context.hpp" +#include "Swapchain.hpp" +#include "Window.hpp" +#include "PassConfig.hpp" +#include "Handles.hpp" +#include "Buffer.hpp" +#include "Image.hpp" +#include "PipelineConfig.hpp" #include "CommandResources.hpp" #include "SyncResources.hpp" #include "Result.hpp" @@ -139,8 +139,8 @@ namespace vkcv const char *applicationName, uint32_t applicationVersion, const std::vector<vk::QueueFlagBits>& queueFlags = {}, - const std::vector<const char*>& instanceExtensions = {}, - const std::vector<const char*>& deviceExtensions = {}); + const Features& features = {}, + const std::vector<const char *>& instanceExtensions = {}); /** * Creates a basic vulkan graphics pipeline using @p config from the pipeline config class and returns it using the @p handle. @@ -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); } /** @@ -249,16 +249,16 @@ namespace vkcv bool beginFrame(uint32_t& width, uint32_t& height); void recordDrawcallsToCmdStream( - const CommandStreamHandle cmdStreamHandle, - const PassHandle renderpassHandle, + const CommandStreamHandle& cmdStreamHandle, + const PassHandle& renderpassHandle, const PipelineHandle pipelineHandle, const PushConstants &pushConstants, const std::vector<DrawcallInfo> &drawcalls, const std::vector<ImageHandle> &renderTargets); void recordMeshShaderDrawcalls( - const CommandStreamHandle cmdStreamHandle, - const PassHandle renderpassHandle, + const CommandStreamHandle& cmdStreamHandle, + const PassHandle& renderpassHandle, const PipelineHandle pipelineHandle, const PushConstants& pushConstantData, const std::vector<MeshShaderDrawcall>& drawcalls, @@ -270,6 +270,20 @@ namespace vkcv const uint32_t dispatchCount[3], const std::vector<DescriptorSetUsage> &descriptorSetUsages, const PushConstants& pushConstants); + + void recordBeginDebugLabel(const CommandStreamHandle &cmdStream, + const std::string& label, + const std::array<float, 4>& color); + + void recordEndDebugLabel(const CommandStreamHandle &cmdStream); + + 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 @@ -313,6 +327,14 @@ namespace vkcv void recordBlitImage(const CommandStreamHandle& cmdStream, const ImageHandle& src, const ImageHandle& dst, SamplerFilterType filterType); + + void setDebugLabel(const BufferHandle &handle, const std::string &label); + void setDebugLabel(const PassHandle &handle, const std::string &label); + void setDebugLabel(const PipelineHandle &handle, const std::string &label); + void setDebugLabel(const DescriptorSetHandle &handle, const std::string &label); + void setDebugLabel(const SamplerHandle &handle, const std::string &label); + void setDebugLabel(const ImageHandle &handle, const std::string &label); + void setDebugLabel(const CommandStreamHandle &handle, const std::string &label); }; } diff --git a/include/vkcv/DescriptorConfig.hpp b/include/vkcv/DescriptorConfig.hpp index 767492eb2b27bd8dff56ef2aeb4769c08eed7200..f2a089fd624c02c57db4a65c4a101c4acff371b1 100644 --- a/include/vkcv/DescriptorConfig.hpp +++ b/include/vkcv/DescriptorConfig.hpp @@ -1,7 +1,5 @@ #pragma once -#include <vulkan/vulkan.hpp> - #include "vkcv/Handles.hpp" #include "vkcv/ShaderStage.hpp" @@ -41,12 +39,12 @@ namespace vkcv uint32_t bindingID, DescriptorType descriptorType, uint32_t descriptorCount, - ShaderStage shaderStage + ShaderStages shaderStages ) noexcept; uint32_t bindingID; DescriptorType descriptorType; uint32_t descriptorCount; - ShaderStage shaderStage; + ShaderStages shaderStages; }; } diff --git a/include/vkcv/DrawcallRecording.hpp b/include/vkcv/DrawcallRecording.hpp index 260fbbc6a2a577d0d333656a1eff4f7f3f88cd69..37cf02d9102fcab5abd10ada711f67b721bcb52b 100644 --- a/include/vkcv/DrawcallRecording.hpp +++ b/include/vkcv/DrawcallRecording.hpp @@ -29,8 +29,19 @@ namespace vkcv { }; struct 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){} + + 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; diff --git a/include/vkcv/FeatureManager.hpp b/include/vkcv/FeatureManager.hpp new file mode 100644 index 0000000000000000000000000000000000000000..cf945d7498fec83f0b128294caa3ba267b69a6b3 --- /dev/null +++ b/include/vkcv/FeatureManager.hpp @@ -0,0 +1,150 @@ +#pragma once + +#include "Logger.hpp" + +#include <functional> +#include <unordered_set> +#include <vector> +#include <vulkan/vulkan.hpp> + +namespace vkcv { + + class FeatureManager { + private: + vk::PhysicalDevice& m_physicalDevice; + + std::vector<const char*> m_supportedExtensions; + std::vector<const char*> m_activeExtensions; + + vk::PhysicalDeviceFeatures2 m_featuresBase; + std::vector<vk::BaseOutStructure*> m_featuresExtensions; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceFeatures& features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDevice16BitStorageFeatures& features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDevice8BitStorageFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceBufferDeviceAddressFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceDescriptorIndexingFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceHostQueryResetFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceImagelessFramebufferFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceMultiviewFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceProtectedMemoryFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceSamplerYcbcrConversionFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceScalarBlockLayoutFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceSeparateDepthStencilLayoutsFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceShaderAtomicInt64Features &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceShaderFloat16Int8Features& features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceTimelineSemaphoreFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceUniformBufferStandardLayoutFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceVariablePointersFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceVulkanMemoryModelFeatures &features, bool required) const; + + [[nodiscard]] + bool checkSupport(const vk::PhysicalDeviceMeshShaderFeaturesNV& features, bool required) const; + + vk::BaseOutStructure* findFeatureStructure(vk::StructureType type) const; + + public: + explicit FeatureManager(vk::PhysicalDevice& physicalDevice); + + FeatureManager(const FeatureManager& other) = delete; + FeatureManager(FeatureManager&& other) noexcept; + + ~FeatureManager(); + + FeatureManager& operator=(const FeatureManager& other) = delete; + FeatureManager& operator=(FeatureManager&& other) noexcept; + + [[nodiscard]] + bool isExtensionSupported(const std::string& extension) const; + + bool useExtension(const std::string& extension, bool required = true); + + [[nodiscard]] + bool isExtensionActive(const std::string& extension) const; + + [[nodiscard]] + const std::vector<const char*>& getActiveExtensions() const; + + bool useFeatures(const std::function<void(vk::PhysicalDeviceFeatures&)>& featureFunction, bool required = true); + + template<typename T> + bool useFeatures(const std::function<void(T&)>& featureFunction, bool required = true) { + T features; + T* features_ptr = reinterpret_cast<T*>(findFeatureStructure(features.sType)); + + if (features_ptr) { + features = *features_ptr; + } + + featureFunction(features); + + if (!checkSupport(features, required)) { + return false; + } + + if (features_ptr) { + *features_ptr = features; + return true; + } + + features_ptr = new T(features); + + if (m_featuresExtensions.empty()) { + m_featuresBase.setPNext(features_ptr); + } else { + m_featuresExtensions.back()->setPNext( + reinterpret_cast<vk::BaseOutStructure*>(features_ptr) + ); + } + + m_featuresExtensions.push_back( + reinterpret_cast<vk::BaseOutStructure*>(features_ptr) + ); + + return true; + } + + [[nodiscard]] + const vk::PhysicalDeviceFeatures2& getFeatures() const; + + }; + +} diff --git a/include/vkcv/Features.hpp b/include/vkcv/Features.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6ef3fa28be912627b4495c66427336dfaa51beff --- /dev/null +++ b/include/vkcv/Features.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include <functional> +#include <vector> +#include <initializer_list> + +#include "FeatureManager.hpp" + +namespace vkcv { + + typedef std::function<bool(FeatureManager&)> Feature; + + class Features { + private: + std::vector<Feature> m_features; + + public: + Features() = default; + + Features(const std::initializer_list<std::string>& list); + + Features(const Features& other) = default; + Features(Features&& other) = default; + + ~Features() = default; + + Features& operator=(const Features& other) = default; + Features& operator=(Features&& other) = default; + + void requireExtension(const std::string& extension); + + void requireExtensionFeature(const std::string& extension, + const std::function<void(vk::PhysicalDeviceFeatures&)>& featureFunction); + + template<typename T> + void requireExtensionFeature(const std::string& extension, const std::function<void(T&)>& featureFunction) { + m_features.emplace_back([extension, featureFunction](FeatureManager& featureManager) { + if (featureManager.useExtension(extension, true)) { + return featureManager.template useFeatures<T>(featureFunction, true); + } else { + return false; + } + }); + } + + void requireFeature(const std::function<void(vk::PhysicalDeviceFeatures&)>& featureFunction); + + template<typename T> + void requireFeature(const std::function<void(T&)>& featureFunction) { + m_features.emplace_back([featureFunction](FeatureManager& featureManager) { + return featureManager.template useFeatures<T>(featureFunction, true); + }); + } + + void tryExtension(const std::string& extension); + + void tryExtensionFeature(const std::string& extension, + const std::function<void(vk::PhysicalDeviceFeatures&)>& featureFunction); + + template<typename T> + void tryExtensionFeature(const std::string& extension, const std::function<void(T&)>& featureFunction) { + m_features.emplace_back([extension, featureFunction](FeatureManager& featureManager) { + if (featureManager.useExtension(extension, false)) { + return featureManager.template useFeatures<T>(featureFunction, false); + } else { + return false; + } + }); + } + + void tryFeature(const std::function<void(vk::PhysicalDeviceFeatures&)>& featureFunction); + + template<typename T> + void tryFeature(const std::function<void(T&)>& featureFunction) { + m_features.emplace_back([featureFunction](FeatureManager& featureManager) { + return featureManager.template useFeatures<T>(featureFunction, false); + }); + } + + [[nodiscard]] + const std::vector<Feature>& getList() const; + + }; + +} diff --git a/include/vkcv/Logger.hpp b/include/vkcv/Logger.hpp index 1ae0f211e1a3255d624cf78985b0797e9d90c634..bb60561e80baadfcac4956223d9313893547068f 100644 --- a/include/vkcv/Logger.hpp +++ b/include/vkcv/Logger.hpp @@ -53,9 +53,10 @@ namespace vkcv { VKCV_DEBUG_MESSAGE_LEN, \ __VA_ARGS__ \ ); \ + auto output = getLogOutput(level); \ if (level != vkcv::LogLevel::RAW_INFO) { \ fprintf( \ - getLogOutput(level), \ + output, \ "[%s]: %s [%s, line %d: %s]\n", \ vkcv::getLogName(level), \ output_message, \ @@ -65,12 +66,13 @@ namespace vkcv { ); \ } else { \ fprintf( \ - getLogOutput(level), \ + output, \ "[%s]: %s\n", \ vkcv::getLogName(level), \ output_message \ ); \ } \ + fflush(output); \ } #else diff --git a/include/vkcv/ShaderProgram.hpp b/include/vkcv/ShaderProgram.hpp index 78b1f02169fe630427b9f66150e32078d42b7b3f..c7d67b19148b3c9ec19ce1b539f9661797d1b38f 100644 --- a/include/vkcv/ShaderProgram.hpp +++ b/include/vkcv/ShaderProgram.hpp @@ -13,8 +13,8 @@ #include <vulkan/vulkan.hpp> #include <spirv_cross.hpp> #include "VertexLayout.hpp" -#include "ShaderStage.hpp" #include "DescriptorConfig.hpp" +#include "ShaderStage.hpp" namespace vkcv { diff --git a/include/vkcv/ShaderStage.hpp b/include/vkcv/ShaderStage.hpp index 3893bdf5f73408847ceb2b076abfb7d0902bb2f9..52ba5a7e56ed40efda96c27dfd92734880b88f18 100644 --- a/include/vkcv/ShaderStage.hpp +++ b/include/vkcv/ShaderStage.hpp @@ -1,19 +1,39 @@ #pragma once -namespace vkcv { - - enum class ShaderStage - { - VERTEX, - TESS_CONTROL, - TESS_EVAL, - GEOMETRY, - FRAGMENT, - COMPUTE, - TASK, - MESH - }; - +#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), + TESS_EVAL = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eTessellationEvaluation), + GEOMETRY = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eGeometry), + FRAGMENT = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eFragment), + COMPUTE = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eCompute), + TASK = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eTaskNV), + MESH = static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eMeshNV) + }; + + using ShaderStages = vk::Flags<ShaderStage>; + + constexpr vk::ShaderStageFlags getShaderStageFlags(ShaderStages shaderStages) noexcept { + return vk::ShaderStageFlags(static_cast<VkShaderStageFlags>(shaderStages)); + } + + constexpr ShaderStages operator|(ShaderStage stage0, ShaderStage stage1) noexcept { + return ShaderStages(stage0) | stage1; + } + + constexpr ShaderStages operator&(ShaderStage stage0, ShaderStage stage1) noexcept { + return ShaderStages(stage0) & stage1; + } + + constexpr ShaderStages operator^(ShaderStage stage0, ShaderStage stage1) noexcept { + return ShaderStages(stage0) ^ stage1; + } + + constexpr ShaderStages operator~(ShaderStage stage) noexcept { + return ~(ShaderStages(stage)); + } } 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..2ca35bdab791ea18a67f33e53ab17fc485cbad02 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: %d", + 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: %d", + 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)", + 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/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/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 28ef7c6943428078589047497fc2d3b44fde5fd7..1c7bb12679e57c9221465452f2fc41a539b6b2a0 100644 --- a/modules/camera/src/vkcv/camera/PilotCameraController.cpp +++ b/modules/camera/src/vkcv/camera/PilotCameraController.cpp @@ -50,11 +50,11 @@ namespace vkcv::camera { } // handle yaw rotation - float yaw = camera.getYaw() + static_cast<float>(xOffset) * m_cameraSpeed; + 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) * m_cameraSpeed; + float pitch = camera.getPitch() - static_cast<float>(yOffset) * 90.0f * m_cameraSpeed; pitch = glm::clamp(pitch, -89.0f, 89.0f); camera.setPitch(pitch); } @@ -82,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; @@ -109,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 @@ -162,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 b149a168f061125c08103ba63fcd7a97fa13ccc3..8de2beb87d8f29415db611bfe0d17c5efd57a2a3 100644 --- a/modules/camera/src/vkcv/camera/TrackballCameraController.cpp +++ b/modules/camera/src/vkcv/camera/TrackballCameraController.cpp @@ -23,10 +23,10 @@ namespace vkcv::camera { } // handle yaw rotation - m_yaw = m_yaw + static_cast<float>(xOffset) * m_cameraSpeed; + m_yaw = m_yaw + static_cast<float>(xOffset) * 90.0f * m_cameraSpeed; // handle pitch rotation - m_pitch = m_pitch + static_cast<float>(yOffset) * m_cameraSpeed; + m_pitch = m_pitch + static_cast<float>(yOffset) * 90.0f * m_cameraSpeed; } void TrackballCameraController::updateRadius(double offset, Camera &camera) { @@ -67,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) { @@ -91,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/scene/src/vkcv/scene/Scene.cpp b/modules/scene/src/vkcv/scene/Scene.cpp index d6fa2a40a494ef57386e52a306e962a460c66dd6..c0065af5928d9ad2e2c9afd1a1ea44c35d94d799 100644 --- a/modules/scene/src/vkcv/scene/Scene.cpp +++ b/modules/scene/src/vkcv/scene/Scene.cpp @@ -116,6 +116,10 @@ namespace vkcv::scene { size_t pushConstantsSizePerDrawcall, const RecordMeshDrawcallFunction &record, const std::vector<ImageHandle> &renderTargets) { + m_core->recordBeginDebugLabel(cmdStream, "vkcv::scene::Scene", { + 0.0f, 1.0f, 0.0f, 1.0f + }); + PushConstants pushConstants (pushConstantsSizePerDrawcall); std::vector<DrawcallInfo> drawcalls; size_t count = 0; @@ -137,6 +141,8 @@ namespace vkcv::scene { drawcalls, renderTargets ); + + m_core->recordEndDebugLabel(cmdStream); } Scene Scene::create(Core& core) { diff --git a/modules/shader_compiler/CMakeLists.txt b/modules/shader_compiler/CMakeLists.txt index 6fee42bfb571168cd2371e21e231ce417efa41f0..11c1e460575709dd9c9c16fdd02b6b923cc33045 100644 --- a/modules/shader_compiler/CMakeLists.txt +++ b/modules/shader_compiler/CMakeLists.txt @@ -31,7 +31,7 @@ include(config/GLSLANG.cmake) target_link_libraries(vkcv_shader_compiler ${vkcv_shader_compiler_libraries} vkcv) # including headers of dependencies and the VkCV framework -target_include_directories(vkcv_shader_compiler SYSTEM BEFORE PRIVATE ${vkcv_shader_compiler_includes} ${vkcv_include}) +target_include_directories(vkcv_shader_compiler SYSTEM BEFORE PRIVATE ${vkcv_shader_compiler_includes} ${vkcv_include} ${vkcv_includes}) # add the own include directory for public headers target_include_directories(vkcv_shader_compiler BEFORE PUBLIC ${vkcv_shader_compiler_include}) diff --git a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp index c8878513bf99054e357f1b076dfe12664be763b3..16067aebedfda8793a0096803ba5344275bcbbcd 100644 --- a/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp +++ b/modules/shader_compiler/src/vkcv/shader/GLSLCompiler.cpp @@ -2,7 +2,7 @@ #include "vkcv/shader/GLSLCompiler.hpp" #include <fstream> -#include <strstream> +#include <sstream> #include <glslang/SPIRV/GlslangToSpv.h> #include <glslang/StandAlone/DirStackFileIncluder.h> @@ -219,12 +219,10 @@ namespace vkcv::shader { std::string source (shaderSource); if (!m_defines.empty()) { - std::strstream defines; + std::ostringstream defines; for (const auto& define : m_defines) { defines << "#define " << define.first << " " << define.second << std::endl; } - - defines << '\0'; size_t pos = source.find("#version") + 8; if (pos >= source.length()) { @@ -236,8 +234,10 @@ namespace vkcv::shader { pos = epos; } + const auto defines_str = defines.str(); + pos = source.find('\n', pos) + 1; - source = source.insert(pos, defines.str()); + source = source.insert(pos, defines_str); } const char *shaderStrings [1]; diff --git a/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp index 9c36acf5d050e3f4f19223020357b6c32534a2de..54df1829006964b30cc1831dc7115e9d5d222a51 100644 --- a/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp +++ b/modules/upscaling/src/vkcv/upscaling/BilinearUpscaling.cpp @@ -7,7 +7,13 @@ namespace vkcv::upscaling { void BilinearUpscaling::recordUpscaling(const CommandStreamHandle &cmdStream, const ImageHandle &input, const ImageHandle &output) { + m_core.recordBeginDebugLabel(cmdStream, "vkcv::upscaling::BilinearUpscaling", { + 0.0f, 0.0f, 1.0f, 1.0f + }); + m_core.recordBlitImage(cmdStream, input, output, SamplerFilterType::LINEAR); + + m_core.recordEndDebugLabel(cmdStream); } } diff --git a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp index 460a6d0b459fe7d1d2a917a62138fea2e5a40908..b11051273ba6f9e56d3a537931f9d33fff657e43 100644 --- a/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp +++ b/modules/upscaling/src/vkcv/upscaling/FSRUpscaling.cpp @@ -177,7 +177,7 @@ namespace vkcv::upscaling { vkcv::shader::GLSLCompiler easuCompiler; vkcv::shader::GLSLCompiler rcasCompiler; - const auto& features = m_core.getContext().getPhysicalDevice().getFeatures2(); + const auto& features = m_core.getContext().getFeatureManager().getFeatures(); const bool float16Support = ( checkFeatures<vk::PhysicalDeviceFloat16Int8FeaturesKHR>( reinterpret_cast<const vk::BaseInStructure*>(&features), @@ -189,7 +189,7 @@ namespace vkcv::upscaling { vk::StructureType::ePhysicalDevice16BitStorageFeaturesKHR, check16Storage ) - ) || (true); // check doesn't work because chain is empty + ); if (!float16Support) { easuCompiler.setDefine("SAMPLE_SLOW_FALLBACK", "1"); @@ -245,6 +245,10 @@ namespace vkcv::upscaling { void FSRUpscaling::recordUpscaling(const CommandStreamHandle& cmdStream, const ImageHandle& input, const ImageHandle& output) { + m_core.recordBeginDebugLabel(cmdStream, "vkcv::upscaling::FSRUpscaling", { + 1.0f, 0.0f, 0.0f, 1.0f + }); + const uint32_t inputWidth = m_core.getImageWidth(input); const uint32_t inputHeight = m_core.getImageHeight(input); @@ -361,6 +365,8 @@ namespace vkcv::upscaling { PushConstants(0) ); } + + m_core.recordEndDebugLabel(cmdStream); } bool FSRUpscaling::isHdrEnabled() const { diff --git a/projects/CMakeLists.txt b/projects/CMakeLists.txt index 6f179ebae0dedc1fc10060b14ce11133665a1538..bb26584a38e5b5c05ba2e883fe4b801377eaf3c8 100644 --- a/projects/CMakeLists.txt +++ b/projects/CMakeLists.txt @@ -7,3 +7,4 @@ add_subdirectory(particle_simulation) add_subdirectory(voxelization) add_subdirectory(mesh_shader) add_subdirectory(neural_network) +add_subdirectory(indirect_dispatch) diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp index 731d3e56975ff0cd2d8e6d503a19d56de1b922fe..2ad76e8f78c5870f6b582a1970caac306026166f 100644 --- a/projects/first_mesh/src/main.cpp +++ b/projects/first_mesh/src/main.cpp @@ -18,14 +18,13 @@ int main(int argc, const char** argv) { windowHeight, true ); - + vkcv::Core core = vkcv::Core::create( window, applicationName, VK_MAKE_VERSION(0, 0, 1), { vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer }, - {}, - { "VK_KHR_swapchain" } + { VK_KHR_SWAPCHAIN_EXTENSION_NAME } ); vkcv::asset::Scene mesh; @@ -166,7 +165,6 @@ 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)); diff --git a/projects/first_scene/src/main.cpp b/projects/first_scene/src/main.cpp index 527eba8c3a1e020e14d92f5d305e2ddced936333..21f8deacdb3f81fcd29c5f14cbe74485f36d13cd 100644 --- a/projects/first_scene/src/main.cpp +++ b/projects/first_scene/src/main.cpp @@ -28,14 +28,13 @@ int main(int argc, const char** argv) { cameraManager.getCamera(camIndex0).setNearFar(0.1f, 30.0f); cameraManager.getCamera(camIndex1).setNearFar(0.1f, 30.0f); - + vkcv::Core core = vkcv::Core::create( window, applicationName, VK_MAKE_VERSION(0, 0, 1), { vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer }, - {}, - { "VK_KHR_swapchain" } + { VK_KHR_SWAPCHAIN_EXTENSION_NAME } ); vkcv::scene::Scene scene = vkcv::scene::Scene::load(core, std::filesystem::path( diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp index 253efad491e6e320ba5e5e8b270b187e2e79da82..d1d856b8863badef8a1b01f6277979cde019c4e0 100644 --- a/projects/first_triangle/src/main.cpp +++ b/projects/first_triangle/src/main.cpp @@ -16,22 +16,21 @@ int main(int argc, const char** argv) { 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_KHR_SWAPCHAIN_EXTENSION_NAME } ); - const auto& context = core.getContext(); - 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)); + core.setDebugLabel(triangleIndexBuffer.getHandle(), "Triangle Index Buffer"); + // an example attachment for passes that output to the window const vkcv::AttachmentDescription present_color_attachment( vkcv::AttachmentOperation::STORE, @@ -46,6 +45,8 @@ int main(int argc, const char** argv) { std::cout << "Error. Could not create renderpass. Exiting." << std::endl; return EXIT_FAILURE; } + + core.setDebugLabel(trianglePass, "Triangle Pass"); vkcv::ShaderProgram triangleShaderProgram; vkcv::shader::GLSLCompiler compiler; @@ -78,12 +79,15 @@ int main(int argc, const char** argv) { return EXIT_FAILURE; } + core.setDebugLabel(trianglePipeline, "Triangle Pipeline"); + auto start = std::chrono::system_clock::now(); const vkcv::Mesh renderMesh({}, triangleIndexBuffer.getVulkanHandle(), 3); vkcv::DrawcallInfo drawcall(renderMesh, {},1); const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle(); + core.setDebugLabel(swapchainInput, "Swapchain Image"); vkcv::camera::CameraManager cameraManager(window); uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); @@ -113,6 +117,7 @@ int main(int argc, const char** argv) { pushConstants.appendDrawcall(mvp); auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics); + core.setDebugLabel(cmdStream, "Render Commands"); core.recordDrawcallsToCmdStream( cmdStream, 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/indirect_dispatch/resources/shaders/mesh.vert b/projects/indirect_dispatch/resources/shaders/mesh.vert new file mode 100644 index 0000000000000000000000000000000000000000..734fd63cdee66e5fbf61cc427ca21fae18a31d82 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/mesh.vert @@ -0,0 +1,20 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec2 inUV; + +layout(location = 0) out vec3 passNormal; +layout(location = 1) out vec2 passUV; + +layout( push_constant ) uniform constants{ + mat4 mvp; + mat4 model; +}; + +void main() { + gl_Position = mvp * vec4(inPosition, 1.0); + passNormal = (model * vec4(inNormal, 0)).xyz; + passUV = inUV; +} \ 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/indirect_dispatch/resources/shaders/sky.frag b/projects/indirect_dispatch/resources/shaders/sky.frag new file mode 100644 index 0000000000000000000000000000000000000000..efc0e03b2d6ee1c71930c866293da66857bd56c7 --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/sky.frag @@ -0,0 +1,8 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +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/indirect_dispatch/resources/shaders/sky.vert b/projects/indirect_dispatch/resources/shaders/sky.vert new file mode 100644 index 0000000000000000000000000000000000000000..44b48cd7f3bfc44e2e43edef0d474581d50608de --- /dev/null +++ b/projects/indirect_dispatch/resources/shaders/sky.vert @@ -0,0 +1,13 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable + +layout(location = 0) in vec3 inPosition; + +layout( push_constant ) uniform constants{ + mat4 viewProjection; +}; + +void main() { + 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..5927970333d63d7e0c3bbbda4b7ccbf321c48a48 --- /dev/null +++ b/projects/indirect_dispatch/src/App.cpp @@ -0,0 +1,368 @@ +#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_EXTENSION_NAME })), + 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 index 7e24fd7b853bfb0a29d8b30879ef1cb95ad141c0..fd009a6281f4b2b6716e193d23829907f4bb5f33 100644 --- a/projects/mesh_shader/.gitignore +++ b/projects/mesh_shader/.gitignore @@ -1 +1 @@ -first_triangle \ No newline at end of file +mesh_shader \ No newline at end of file diff --git a/projects/mesh_shader/src/main.cpp b/projects/mesh_shader/src/main.cpp index 3a94de5842f3e70625729c9755b8c88048ece2ec..15aaf527619b4fb06d89bccc78cffe843cc723b7 100644 --- a/projects/mesh_shader/src/main.cpp +++ b/projects/mesh_shader/src/main.cpp @@ -86,23 +86,25 @@ int main(int argc, const char** argv) { windowHeight, false ); + + vkcv::Features features; + features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + features.requireExtensionFeature<vk::PhysicalDeviceMeshShaderFeaturesNV>( + VK_NV_MESH_SHADER_EXTENSION_NAME, [](vk::PhysicalDeviceMeshShaderFeaturesNV& features) { + features.setTaskShader(true); + features.setMeshShader(true); + }); 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 } + features ); 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(); - vkcv::asset::Scene mesh; const char* path = argc > 1 ? argv[1] : "resources/Bunny/Bunny.glb"; vkcv::asset::loadScene(path, mesh); diff --git a/projects/neural_network/.gitignore b/projects/neural_network/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..350ab6eabed7ec3d27d175e3dc6f0f4e0ba6f15c --- /dev/null +++ b/projects/neural_network/.gitignore @@ -0,0 +1 @@ +neural_network \ No newline at end of file diff --git a/projects/neural_network/src/main.cpp b/projects/neural_network/src/main.cpp index 8958adbb9d6b9eea79f2b5a8e7a95298bb7ffe60..0d28cf38b029c4b4b3cc7c4b11bb589c64b98d3f 100644 --- a/projects/neural_network/src/main.cpp +++ b/projects/neural_network/src/main.cpp @@ -22,7 +22,6 @@ int main(int argc, const char** argv) { applicationName, VK_MAKE_VERSION(0, 0, 1), { vk::QueueFlagBits::eTransfer, vk::QueueFlagBits::eGraphics, vk::QueueFlagBits::eCompute }, - {}, { "VK_KHR_swapchain" } ); diff --git a/projects/particle_simulation/src/main.cpp b/projects/particle_simulation/src/main.cpp index 07ba6b194ce72dbad15a921ca13a4814c6d4f5df..4de016aefd345a4231ea2dae0818b0839b163729 100644 --- a/projects/particle_simulation/src/main.cpp +++ b/projects/particle_simulation/src/main.cpp @@ -23,14 +23,13 @@ int main(int argc, const char **argv) { ); vkcv::camera::CameraManager cameraManager(window); - + 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_KHR_SWAPCHAIN_EXTENSION_NAME } ); auto particleIndexBuffer = core.createBuffer<uint16_t>(vkcv::BufferType::INDEX, 3, diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp index e7f9caa493714d30f13f64c292f1b6e51e5170b1..3c23c7eabbbdc5d83d37bde79b43a2730b1f28b2 100644 --- a/projects/voxelization/src/main.cpp +++ b/projects/voxelization/src/main.cpp @@ -80,14 +80,18 @@ int main(int argc, const char** argv) { cameraManager.getCamera(camIndex).setFov(glm::radians(37.8)); // fov of a 35mm lens cameraManager.getCamera(camIndex2).setNearFar(0.1f, 30.0f); - + + vkcv::Features features; + features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + features.requireExtension(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME); + features.requireExtension(VK_KHR_16BIT_STORAGE_EXTENSION_NAME); + 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_KHR_shader_float16_int8", "VK_KHR_16bit_storage" } + features ); vkcv::asset::Scene mesh; diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp index ea0408094d86d72206eff7d31566e4af7c182d80..28e0a5ca8907061b7b9a0926918d0a544cf0d4b3 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,7 +28,7 @@ namespace vkcv { } } - 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; @@ -49,13 +49,15 @@ 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 vma::Allocator& allocator = m_core->getContext().getAllocator(); @@ -81,6 +83,10 @@ namespace vkcv { break; } + if (type == BufferType::STAGING) { + memoryUsage = vma::MemoryUsage::eCpuToGpu; + } + auto bufferAllocation = allocator.createBuffer( vk::BufferCreateInfo(createFlags, size, usageFlags), vma::AllocationCreateInfo( diff --git a/src/vkcv/Context.cpp b/src/vkcv/Context.cpp index 2e30fb961d0b0931e4ff8796dd92b2cbd0b5f734..f48fad1a9022cbf99f6452c319d629060befbd98 100644 --- a/src/vkcv/Context.cpp +++ b/src/vkcv/Context.cpp @@ -9,8 +9,9 @@ namespace vkcv m_Instance(other.m_Instance), m_PhysicalDevice(other.m_PhysicalDevice), m_Device(other.m_Device), - m_QueueManager(other.m_QueueManager), - m_Allocator(other.m_Allocator) + m_FeatureManager(std::move(other.m_FeatureManager)), + m_QueueManager(std::move(other.m_QueueManager)), + m_Allocator(other.m_Allocator) { other.m_Instance = nullptr; other.m_PhysicalDevice = nullptr; @@ -23,7 +24,8 @@ namespace vkcv m_Instance = other.m_Instance; m_PhysicalDevice = other.m_PhysicalDevice; m_Device = other.m_Device; - m_QueueManager = other.m_QueueManager; + m_FeatureManager = std::move(other.m_FeatureManager); + m_QueueManager = std::move(other.m_QueueManager); m_Allocator = other.m_Allocator; other.m_Instance = nullptr; @@ -37,12 +39,14 @@ namespace vkcv Context::Context(vk::Instance instance, vk::PhysicalDevice physicalDevice, vk::Device device, + FeatureManager&& featureManager, QueueManager&& queueManager, vma::Allocator&& allocator) noexcept : m_Instance(instance), m_PhysicalDevice(physicalDevice), m_Device(device), - m_QueueManager(queueManager), + m_FeatureManager(std::move(featureManager)), + m_QueueManager(std::move(queueManager)), m_Allocator(allocator) {} @@ -68,6 +72,10 @@ namespace vkcv return m_Device; } + const FeatureManager& Context::getFeatureManager() const { + return m_FeatureManager; + } + const QueueManager& Context::getQueueManager() const { return m_QueueManager; } @@ -167,7 +175,6 @@ namespace vkcv return true; } - std::vector<const char*> getRequiredExtensions() { uint32_t glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); @@ -180,20 +187,11 @@ 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, const std::vector<vk::QueueFlagBits>& queueFlags, - const std::vector<const char *>& instanceExtensions, - const std::vector<const char *>& deviceExtensions) { + const Features& features, + const std::vector<const char*>& instanceExtensions) { // check for layer support const std::vector<vk::LayerProperties>& layerProperties = vk::enumerateInstanceLayerProperties(); @@ -226,14 +224,14 @@ namespace vkcv supportedExtensions.push_back(elem.extensionName); } - if (!checkSupport(supportedExtensions, instanceExtensions)) { - throw std::runtime_error("The requested instance extensions are not supported!"); - } - // for GLFW: get all required extensions std::vector<const char*> requiredExtensions = getRequiredExtensions(); requiredExtensions.insert(requiredExtensions.end(), instanceExtensions.begin(), instanceExtensions.end()); + if (!checkSupport(supportedExtensions, requiredExtensions)) { + throw std::runtime_error("The requested instance extensions are not supported!"); + } + const vk::ApplicationInfo applicationInfo( applicationName, applicationVersion, @@ -261,16 +259,35 @@ namespace vkcv std::vector<vk::PhysicalDevice> physicalDevices = instance.enumeratePhysicalDevices(); vk::PhysicalDevice physicalDevice = pickPhysicalDevice(instance); - // check for physical device extension support - std::vector<vk::ExtensionProperties> deviceExtensionProperties = physicalDevice.enumerateDeviceExtensionProperties(); - supportedExtensions.clear(); - for (auto& elem : deviceExtensionProperties) { - supportedExtensions.push_back(elem.extensionName); + FeatureManager featureManager (physicalDevice); + + if (featureManager.useExtension(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, false)) { + featureManager.useFeatures<vk::PhysicalDeviceShaderFloat16Int8Features>( + [](vk::PhysicalDeviceShaderFloat16Int8Features& features) { + features.setShaderFloat16(true); + }, false); } - if (!checkSupport(supportedExtensions, deviceExtensions)) { - throw std::runtime_error("The requested device extensions are not supported by the physical device!"); + + if (featureManager.useExtension(VK_KHR_16BIT_STORAGE_EXTENSION_NAME, false)) { + featureManager.useFeatures<vk::PhysicalDevice16BitStorageFeatures>( + [](vk::PhysicalDevice16BitStorageFeatures& features) { + features.setStorageBuffer16BitAccess(true); + }, false); } + featureManager.useFeatures([](vk::PhysicalDeviceFeatures& features) { + features.setFragmentStoresAndAtomics(true); + features.setGeometryShader(true); + features.setDepthClamp(true); + features.setShaderInt16(true); + }); + + for (const auto& feature : features.getList()) { + feature(featureManager); + } + + const auto& extensions = featureManager.getActiveExtensions(); + std::vector<vk::DeviceQueueCreateInfo> qCreateInfos; // create required queues @@ -286,58 +303,21 @@ namespace vkcv qCreateInfos.data(), 0, nullptr, - deviceExtensions.size(), - deviceExtensions.data(), - nullptr // Should our device use some features??? If yes: TODO + extensions.size(), + extensions.data(), + nullptr ); #ifndef NDEBUG 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::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 - // device erstellen - // jetzt koennen wir mit dem device die queues erstellen + deviceCreateInfo.setPNext(&(featureManager.getFeatures())); vk::Device device = physicalDevice.createDevice(deviceCreateInfo); - if (usingMeshShaders) - { + if (featureManager.isExtensionActive(VK_NV_MESH_SHADER_EXTENSION_NAME)) { InitMeshShaderDrawFunctions(device); } @@ -376,6 +356,7 @@ namespace vkcv instance, physicalDevice, device, + std::move(featureManager), std::move(queueManager), std::move(allocator) ); diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp index 6c3a388b8c0f668b7994b7cd6c7c5106f2ab10dd..7ae330eb655e7572d5135ab3a7055651910228d7 100644 --- a/src/vkcv/Core.cpp +++ b/src/vkcv/Core.cpp @@ -54,14 +54,14 @@ namespace vkcv const char *applicationName, uint32_t applicationVersion, const std::vector<vk::QueueFlagBits>& queueFlags, - const std::vector<const char *>& instanceExtensions, - const std::vector<const char *>& deviceExtensions) + const Features& features, + const std::vector<const char *>& instanceExtensions) { Context context = Context::create( applicationName, applicationVersion, queueFlags, - instanceExtensions, - deviceExtensions + features, + instanceExtensions ); Swapchain swapChain = Swapchain::create(window, context); @@ -263,9 +263,8 @@ namespace vkcv vk::Device device) { std::vector<vk::ImageView> attachmentsViews; - for (const ImageHandle handle : renderTargets) { - vk::ImageView targetHandle = imageManager.getVulkanImageView(handle); - attachmentsViews.push_back(targetHandle); + for (const ImageHandle& handle : renderTargets) { + attachmentsViews.push_back(imageManager.getVulkanImageView(handle)); } const std::array<uint32_t, 2> widthHeight = getWidthHeightFromRenderTargets(renderTargets, swapchain, imageManager); @@ -287,8 +286,7 @@ namespace vkcv ImageManager& imageManager, const vk::CommandBuffer cmdBuffer) { - for (const ImageHandle handle : renderTargets) { - vk::ImageView targetHandle = imageManager.getVulkanImageView(handle); + for (const ImageHandle& handle : renderTargets) { const bool isDepthImage = isDepthFormat(imageManager.getImageFormat(handle)); const vk::ImageLayout targetLayout = isDepthImage ? vk::ImageLayout::eDepthStencilAttachmentOptimal : vk::ImageLayout::eColorAttachmentOptimal; @@ -331,8 +329,8 @@ namespace vkcv } void Core::recordDrawcallsToCmdStream( - const CommandStreamHandle cmdStreamHandle, - const PassHandle renderpassHandle, + const CommandStreamHandle& cmdStreamHandle, + const PassHandle& renderpassHandle, const PipelineHandle pipelineHandle, const PushConstants &pushConstantData, const std::vector<DrawcallInfo> &drawcalls, @@ -368,7 +366,6 @@ namespace vkcv 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()); @@ -377,16 +374,14 @@ namespace vkcv cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {}); const PipelineConfig &pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle); - if(pipeConfig.m_UseDynamicViewport) - { + if (pipeConfig.m_UseDynamicViewport) { recordDynamicViewport(cmdBuffer, width, height); } - for (int i = 0; i < drawcalls.size(); i++) { + for (size_t i = 0; i < drawcalls.size(); i++) { recordDrawcall(drawcalls[i], cmdBuffer, pipelineLayout, pushConstantData, i); } - vk::Rect2D dynamicScissor({0, 0}, {width, height}); cmdBuffer.endRenderPass(); }; @@ -399,8 +394,8 @@ namespace vkcv } void Core::recordMeshShaderDrawcalls( - const CommandStreamHandle cmdStreamHandle, - const PassHandle renderpassHandle, + const CommandStreamHandle& cmdStreamHandle, + const PassHandle& renderpassHandle, const PipelineHandle pipelineHandle, const PushConstants& pushConstantData, const std::vector<MeshShaderDrawcall>& drawcalls, @@ -436,7 +431,6 @@ namespace vkcv 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()); @@ -445,12 +439,11 @@ namespace vkcv cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, {}); const PipelineConfig& pipeConfig = m_PipelineManager->getPipelineConfig(pipelineHandle); - if (pipeConfig.m_UseDynamicViewport) - { + if (pipeConfig.m_UseDynamicViewport) { recordDynamicViewport(cmdBuffer, width, height); } - for (int i = 0; i < drawcalls.size(); i++) { + for (size_t i = 0; i < drawcalls.size(); i++) { const uint32_t pushConstantOffset = i * pushConstantData.getSizePerDrawcall(); recordMeshShaderDrawcall( cmdBuffer, @@ -458,14 +451,14 @@ namespace vkcv pushConstantData, pushConstantOffset, drawcalls[i], - 0); + 0 + ); } cmdBuffer.endRenderPass(); }; - auto finishFunction = [framebuffer, this]() - { + auto finishFunction = [framebuffer, this]() { m_Context.m_Device.destroy(framebuffer); }; @@ -506,6 +499,85 @@ namespace vkcv recordCommandsToStream(cmdStreamHandle, submitFunction, nullptr); } + + void Core::recordBeginDebugLabel(const CommandStreamHandle &cmdStream, + const std::string& label, + const std::array<float, 4>& color) { +#ifndef NDEBUG + static PFN_vkCmdBeginDebugUtilsLabelEXT beginDebugLabel = reinterpret_cast<PFN_vkCmdBeginDebugUtilsLabelEXT>( + m_Context.getDevice().getProcAddr("vkCmdBeginDebugUtilsLabelEXT") + ); + + if (!beginDebugLabel) { + return; + } + + auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { + const vk::DebugUtilsLabelEXT debug ( + label.c_str(), + color + ); + + beginDebugLabel(cmdBuffer, &(static_cast<const VkDebugUtilsLabelEXT&>(debug))); + }; + + recordCommandsToStream(cmdStream, submitFunction, nullptr); +#endif + } + + void Core::recordEndDebugLabel(const CommandStreamHandle &cmdStream) { +#ifndef NDEBUG + static PFN_vkCmdEndDebugUtilsLabelEXT endDebugLabel = reinterpret_cast<PFN_vkCmdEndDebugUtilsLabelEXT>( + m_Context.getDevice().getProcAddr("vkCmdEndDebugUtilsLabelEXT") + ); + + if (!endDebugLabel) { + return; + } + + auto submitFunction = [&](const vk::CommandBuffer& cmdBuffer) { + endDebugLabel(cmdBuffer); + }; + + recordCommandsToStream(cmdStream, submitFunction, nullptr); +#endif + } + + 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()) { @@ -795,4 +867,141 @@ namespace vkcv }, nullptr); } + static void setDebugObjectLabel(const vk::Device& device, const vk::ObjectType& type, + uint64_t handle, const std::string& label) { +#ifndef NDEBUG + static PFN_vkSetDebugUtilsObjectNameEXT setDebugLabel = reinterpret_cast<PFN_vkSetDebugUtilsObjectNameEXT>( + device.getProcAddr("vkSetDebugUtilsObjectNameEXT") + ); + + if (!setDebugLabel) { + return; + } + + const vk::DebugUtilsObjectNameInfoEXT debug ( + type, + handle, + label.c_str() + ); + + setDebugLabel(device, &(static_cast<const VkDebugUtilsObjectNameInfoEXT&>(debug))); +#endif + } + + void Core::setDebugLabel(const BufferHandle &handle, const std::string &label) { + if (!handle) { + vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); + return; + } + + setDebugObjectLabel( + m_Context.getDevice(), + vk::ObjectType::eBuffer, + reinterpret_cast<uint64_t>(static_cast<VkBuffer>( + m_BufferManager->getBuffer(handle) + )), + label + ); + } + + void Core::setDebugLabel(const PassHandle &handle, const std::string &label) { + if (!handle) { + vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); + return; + } + + setDebugObjectLabel( + m_Context.getDevice(), + vk::ObjectType::eRenderPass, + reinterpret_cast<uint64_t>(static_cast<VkRenderPass>( + m_PassManager->getVkPass(handle) + )), + label + ); + } + + void Core::setDebugLabel(const PipelineHandle &handle, const std::string &label) { + if (!handle) { + vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); + return; + } + + setDebugObjectLabel( + m_Context.getDevice(), + vk::ObjectType::ePipeline, + reinterpret_cast<uint64_t>(static_cast<VkPipeline>( + m_PipelineManager->getVkPipeline(handle) + )), + label + ); + } + + void Core::setDebugLabel(const DescriptorSetHandle &handle, const std::string &label) { + if (!handle) { + vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); + return; + } + + setDebugObjectLabel( + m_Context.getDevice(), + vk::ObjectType::eDescriptorSet, + reinterpret_cast<uint64_t>(static_cast<VkDescriptorSet>( + m_DescriptorManager->getDescriptorSet(handle).vulkanHandle + )), + label + ); + } + + void Core::setDebugLabel(const SamplerHandle &handle, const std::string &label) { + if (!handle) { + vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); + return; + } + + setDebugObjectLabel( + m_Context.getDevice(), + vk::ObjectType::eSampler, + reinterpret_cast<uint64_t>(static_cast<VkSampler>( + m_SamplerManager->getVulkanSampler(handle) + )), + label + ); + } + + void Core::setDebugLabel(const ImageHandle &handle, const std::string &label) { + if (!handle) { + vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); + return; + } else + if (handle.isSwapchainImage()) { + vkcv_log(LogLevel::WARNING, "Can't set debug label to swapchain image"); + return; + } + + setDebugObjectLabel( + m_Context.getDevice(), + vk::ObjectType::eImage, + reinterpret_cast<uint64_t>(static_cast<VkImage>( + m_ImageManager->getVulkanImage(handle) + )), + label + ); + } + + void Core::setDebugLabel(const CommandStreamHandle &handle, const std::string &label) { + if (!handle) { + vkcv_log(LogLevel::WARNING, "Can't set debug label to invalid handle"); + return; + } + + setDebugObjectLabel( + m_Context.getDevice(), + vk::ObjectType::eCommandBuffer, + reinterpret_cast<uint64_t>(static_cast<VkCommandBuffer>( + m_CommandStreamManager->getStreamCommandBuffer(handle) + )), + label + ); + } + } diff --git a/src/vkcv/DescriptorConfig.cpp b/src/vkcv/DescriptorConfig.cpp index 54e879ac7e6ec7825a4c003899e3c264454c547f..a9a127fe608472682c1cbc8d32ca466fba860c72 100644 --- a/src/vkcv/DescriptorConfig.cpp +++ b/src/vkcv/DescriptorConfig.cpp @@ -5,11 +5,11 @@ namespace vkcv { uint32_t bindingID, DescriptorType descriptorType, uint32_t descriptorCount, - ShaderStage shaderStage) noexcept + ShaderStages shaderStages) noexcept : bindingID(bindingID), descriptorType(descriptorType), descriptorCount(descriptorCount), - shaderStage(shaderStage) {} + shaderStages(shaderStages) {} } diff --git a/src/vkcv/DescriptorManager.cpp b/src/vkcv/DescriptorManager.cpp index 0df359a15883847c132c429ed2945ac7624fb865..76f2dae74420804c9ce168bea76e7c1bdef0fbc0 100644 --- a/src/vkcv/DescriptorManager.cpp +++ b/src/vkcv/DescriptorManager.cpp @@ -54,7 +54,7 @@ namespace vkcv binding.bindingID, convertDescriptorTypeFlag(binding.descriptorType), binding.descriptorCount, - convertShaderStageFlag(binding.shaderStage)); + getShaderStageFlags(binding.shaderStages)); setBindings.push_back(descriptorSetLayoutBinding); } @@ -273,26 +273,6 @@ namespace vkcv return vk::DescriptorType::eUniformBuffer; } } - - vk::ShaderStageFlagBits DescriptorManager::convertShaderStageFlag(ShaderStage stage) { - switch (stage) - { - case ShaderStage::VERTEX: - return vk::ShaderStageFlagBits::eVertex; - case ShaderStage::FRAGMENT: - return vk::ShaderStageFlagBits::eFragment; - case ShaderStage::TESS_CONTROL: - return vk::ShaderStageFlagBits::eTessellationControl; - case ShaderStage::TESS_EVAL: - return vk::ShaderStageFlagBits::eTessellationEvaluation; - case ShaderStage::GEOMETRY: - return vk::ShaderStageFlagBits::eGeometry; - case ShaderStage::COMPUTE: - return vk::ShaderStageFlagBits::eCompute; - default: - return vk::ShaderStageFlagBits::eAll; - } - } void DescriptorManager::destroyDescriptorSetById(uint64_t id) { if (id >= m_DescriptorSets.size()) { diff --git a/src/vkcv/DescriptorManager.hpp b/src/vkcv/DescriptorManager.hpp index d18be64f3b069af68cecce68f6fa623c81f8dfa4..df58daa56c26a0432f365a4ed0d9df56adc4cd0e 100644 --- a/src/vkcv/DescriptorManager.hpp +++ b/src/vkcv/DescriptorManager.hpp @@ -51,12 +51,6 @@ namespace vkcv * @return vk flag of the DescriptorType */ static vk::DescriptorType convertDescriptorTypeFlag(DescriptorType type); - /** - * Converts the flags of the shader stages from VulkanCV (vkcv) to Vulkan (vk). - * @param[in] vkcv flag of the ShaderStage (see ShaderProgram.hpp) - * @return vk flag of the ShaderStage - */ - static vk::ShaderStageFlagBits convertShaderStageFlag(ShaderStage stage); /** * Destroys a specific resource description diff --git a/src/vkcv/DrawcallRecording.cpp b/src/vkcv/DrawcallRecording.cpp index d89ace3859717f753534402507a713a78bfb6876..ca8b248a06d06c7aed6f8d0e9760645b727a5993 100644 --- a/src/vkcv/DrawcallRecording.cpp +++ b/src/vkcv/DrawcallRecording.cpp @@ -52,8 +52,6 @@ namespace vkcv { } } - - struct MeshShaderFunctions { PFN_vkCmdDrawMeshTasksNV cmdDrawMeshTasks = nullptr; diff --git a/src/vkcv/FeatureManager.cpp b/src/vkcv/FeatureManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..18307999bb28ed60791a801b952619eeeba51a53 --- /dev/null +++ b/src/vkcv/FeatureManager.cpp @@ -0,0 +1,437 @@ + +#include "vkcv/FeatureManager.hpp" + +#include <stddef.h> +#include <string.h> +#include <type_traits> + +namespace vkcv { + +#ifdef _MSVC_LANG +#define typeof(var) std::decay<decltype((var))>::type +#endif + +#define vkcv_check_init_features2(type)\ +type supported; \ +vk::PhysicalDeviceFeatures2 query; \ +query.setPNext(&supported); \ +m_physicalDevice.getFeatures2(&query) + +#define vkcv_check_feature(attribute) { \ + const char *f = reinterpret_cast<const char*>(&(features)); \ + const char *s = reinterpret_cast<const char*>(&(supported)); \ + const vk::Bool32* fb = reinterpret_cast<const vk::Bool32*>(f + offsetof(typeof((features)), attribute)); \ + const vk::Bool32* sb = reinterpret_cast<const vk::Bool32*>(s + offsetof(typeof((features)), attribute)); \ + if ((*fb) && (!*sb)) { \ + vkcv_log(((required)? LogLevel::ERROR : LogLevel::WARNING), \ + "Feature '" #attribute "' is not supported"); \ + return false; \ + } \ +} + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceFeatures &features, bool required) const { + const auto& supported = m_physicalDevice.getFeatures(); + + vkcv_check_feature(alphaToOne); + vkcv_check_feature(depthBiasClamp); + vkcv_check_feature(depthBounds); + vkcv_check_feature(depthClamp); + vkcv_check_feature(drawIndirectFirstInstance); + vkcv_check_feature(dualSrcBlend); + vkcv_check_feature(fillModeNonSolid); + vkcv_check_feature(fragmentStoresAndAtomics); + vkcv_check_feature(fullDrawIndexUint32); + vkcv_check_feature(geometryShader); + vkcv_check_feature(imageCubeArray); + vkcv_check_feature(independentBlend); + vkcv_check_feature(inheritedQueries); + vkcv_check_feature(largePoints); + vkcv_check_feature(logicOp); + vkcv_check_feature(multiDrawIndirect); + vkcv_check_feature(multiViewport); + vkcv_check_feature(occlusionQueryPrecise); + vkcv_check_feature(pipelineStatisticsQuery); + vkcv_check_feature(robustBufferAccess); + vkcv_check_feature(sampleRateShading); + vkcv_check_feature(samplerAnisotropy); + vkcv_check_feature(shaderClipDistance); + vkcv_check_feature(shaderCullDistance); + vkcv_check_feature(shaderFloat64); + vkcv_check_feature(shaderImageGatherExtended); + vkcv_check_feature(shaderInt16); + vkcv_check_feature(shaderInt64); + vkcv_check_feature(shaderResourceMinLod); + vkcv_check_feature(shaderResourceResidency); + vkcv_check_feature(shaderSampledImageArrayDynamicIndexing); + vkcv_check_feature(shaderStorageBufferArrayDynamicIndexing); + vkcv_check_feature(shaderStorageImageArrayDynamicIndexing); + vkcv_check_feature(shaderStorageImageExtendedFormats); + vkcv_check_feature(shaderStorageImageMultisample); + vkcv_check_feature(shaderStorageImageReadWithoutFormat); + vkcv_check_feature(shaderStorageImageWriteWithoutFormat); + vkcv_check_feature(shaderTessellationAndGeometryPointSize); + vkcv_check_feature(shaderUniformBufferArrayDynamicIndexing); + vkcv_check_feature(sparseBinding); + vkcv_check_feature(sparseResidency2Samples); + vkcv_check_feature(sparseResidency4Samples); + vkcv_check_feature(sparseResidency8Samples); + vkcv_check_feature(sparseResidency16Samples); + vkcv_check_feature(sparseResidencyAliased); + vkcv_check_feature(sparseResidencyBuffer); + vkcv_check_feature(sparseResidencyImage2D); + vkcv_check_feature(sparseResidencyImage3D); + vkcv_check_feature(tessellationShader); + vkcv_check_feature(textureCompressionASTC_LDR); + vkcv_check_feature(textureCompressionBC); + vkcv_check_feature(textureCompressionETC2); + vkcv_check_feature(variableMultisampleRate); + vkcv_check_feature(vertexPipelineStoresAndAtomics); + vkcv_check_feature(wideLines); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDevice16BitStorageFeatures &features, bool required) const { + vkcv_check_init_features2(vk::PhysicalDevice16BitStorageFeatures); + + vkcv_check_feature(storageBuffer16BitAccess); + vkcv_check_feature(storageInputOutput16); + vkcv_check_feature(storagePushConstant16); + vkcv_check_feature(uniformAndStorageBuffer16BitAccess); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDevice8BitStorageFeatures &features, bool required) const { + vkcv_check_init_features2(vk::PhysicalDevice8BitStorageFeatures); + + vkcv_check_feature(storageBuffer8BitAccess); + vkcv_check_feature(storagePushConstant8); + vkcv_check_feature(uniformAndStorageBuffer8BitAccess); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceBufferDeviceAddressFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceBufferDeviceAddressFeatures); + + vkcv_check_feature(bufferDeviceAddress); + vkcv_check_feature(bufferDeviceAddressCaptureReplay); + vkcv_check_feature(bufferDeviceAddressMultiDevice); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceDescriptorIndexingFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceDescriptorIndexingFeatures); + + vkcv_check_feature(shaderInputAttachmentArrayDynamicIndexing); + vkcv_check_feature(shaderInputAttachmentArrayNonUniformIndexing); + vkcv_check_feature(shaderSampledImageArrayNonUniformIndexing); + vkcv_check_feature(shaderStorageBufferArrayNonUniformIndexing); + vkcv_check_feature(shaderStorageImageArrayNonUniformIndexing); + vkcv_check_feature(shaderStorageTexelBufferArrayDynamicIndexing); + vkcv_check_feature(shaderStorageTexelBufferArrayNonUniformIndexing); + vkcv_check_feature(shaderUniformBufferArrayNonUniformIndexing); + vkcv_check_feature(shaderUniformTexelBufferArrayDynamicIndexing); + vkcv_check_feature(shaderUniformTexelBufferArrayNonUniformIndexing); + vkcv_check_feature(descriptorBindingPartiallyBound); + vkcv_check_feature(descriptorBindingSampledImageUpdateAfterBind); + vkcv_check_feature(descriptorBindingStorageBufferUpdateAfterBind); + vkcv_check_feature(descriptorBindingStorageImageUpdateAfterBind); + vkcv_check_feature(descriptorBindingStorageTexelBufferUpdateAfterBind); + vkcv_check_feature(descriptorBindingUniformBufferUpdateAfterBind); + vkcv_check_feature(descriptorBindingUniformTexelBufferUpdateAfterBind); + vkcv_check_feature(descriptorBindingUpdateUnusedWhilePending); + vkcv_check_feature(descriptorBindingVariableDescriptorCount); + vkcv_check_feature(runtimeDescriptorArray); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceHostQueryResetFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceHostQueryResetFeatures); + + vkcv_check_feature(hostQueryReset); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceImagelessFramebufferFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceImagelessFramebufferFeatures); + + vkcv_check_feature(imagelessFramebuffer); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceMultiviewFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceMultiviewFeatures); + + vkcv_check_feature(multiview); + vkcv_check_feature(multiviewGeometryShader); + vkcv_check_feature(multiviewTessellationShader); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceProtectedMemoryFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceProtectedMemoryFeatures); + + vkcv_check_feature(protectedMemory); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceSamplerYcbcrConversionFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceSamplerYcbcrConversionFeatures); + + vkcv_check_feature(samplerYcbcrConversion); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceScalarBlockLayoutFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceScalarBlockLayoutFeatures); + + vkcv_check_feature(scalarBlockLayout); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceSeparateDepthStencilLayoutsFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceSeparateDepthStencilLayoutsFeatures); + + vkcv_check_feature(separateDepthStencilLayouts); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceShaderAtomicInt64Features &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceShaderAtomicInt64Features); + + vkcv_check_feature(shaderBufferInt64Atomics); + vkcv_check_feature(shaderSharedInt64Atomics); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceShaderFloat16Int8Features &features, bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceShaderFloat16Int8Features); + + vkcv_check_feature(shaderFloat16); + vkcv_check_feature(shaderInt8); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceShaderSubgroupExtendedTypesFeatures); + + vkcv_check_feature(shaderSubgroupExtendedTypes); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceTimelineSemaphoreFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceTimelineSemaphoreFeatures); + + vkcv_check_feature(timelineSemaphore); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceUniformBufferStandardLayoutFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceUniformBufferStandardLayoutFeatures); + + vkcv_check_feature(uniformBufferStandardLayout); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceVariablePointersFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceVariablePointersFeatures); + + vkcv_check_feature(variablePointers); + vkcv_check_feature(variablePointersStorageBuffer); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceVulkanMemoryModelFeatures &features, + bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceVulkanMemoryModelFeatures); + + vkcv_check_feature(vulkanMemoryModel); + vkcv_check_feature(vulkanMemoryModelDeviceScope); + vkcv_check_feature(vulkanMemoryModelAvailabilityVisibilityChains); + + return true; + } + + bool FeatureManager::checkSupport(const vk::PhysicalDeviceMeshShaderFeaturesNV &features, bool required) const { + vkcv_check_init_features2(vk::PhysicalDeviceMeshShaderFeaturesNV); + + vkcv_check_feature(taskShader); + vkcv_check_feature(meshShader); + + return true; + } + + vk::BaseOutStructure* FeatureManager::findFeatureStructure(vk::StructureType type) const { + for (auto& base : m_featuresExtensions) { + if (base->sType == type) { + return base; + } + } + + return nullptr; + } + + const char* strclone(const char* str) { + if (!str) { + return nullptr; + } + + const size_t length = strlen(str) + 1; + + if (length <= 1) { + return nullptr; + } + + char* clone = new char[length]; + strcpy(clone, str); + return clone; + } + + FeatureManager::FeatureManager(vk::PhysicalDevice &physicalDevice) : + m_physicalDevice(physicalDevice), + m_supportedExtensions(), + m_activeExtensions(), + m_featuresBase(), + m_featuresExtensions() { + for (const auto& extension : m_physicalDevice.enumerateDeviceExtensionProperties()) { + const char* clone = strclone(extension.extensionName); + + if (clone) { + m_supportedExtensions.push_back(clone); + } + } + } + + FeatureManager::FeatureManager(FeatureManager &&other) noexcept : + m_physicalDevice(other.m_physicalDevice), + m_supportedExtensions(std::move(other.m_supportedExtensions)), + m_activeExtensions(std::move(other.m_activeExtensions)), + m_featuresBase(other.m_featuresBase), + m_featuresExtensions(std::move(other.m_featuresExtensions)) { + other.m_featuresExtensions.clear(); + other.m_activeExtensions.clear(); + other.m_supportedExtensions.clear(); + } + + FeatureManager::~FeatureManager() { + for (auto& features : m_featuresExtensions) { + delete features; + } + + for (auto& extension : m_activeExtensions) { + delete[] extension; + } + + for (auto& extension : m_supportedExtensions) { + delete[] extension; + } + } + + FeatureManager &FeatureManager::operator=(FeatureManager &&other) noexcept { + m_physicalDevice = other.m_physicalDevice; + m_supportedExtensions = std::move(other.m_supportedExtensions); + m_activeExtensions = std::move(other.m_activeExtensions); + m_featuresBase = other.m_featuresBase; + m_featuresExtensions = std::move(other.m_featuresExtensions); + + other.m_featuresExtensions.clear(); + other.m_activeExtensions.clear(); + other.m_supportedExtensions.clear(); + + return *this; + } + + bool FeatureManager::isExtensionSupported(const std::string& extension) const { + for (const auto& supported : m_supportedExtensions) { + if (0 == strcmp(supported, extension.c_str())) { + return true; + } + } + + return false; + } + + bool FeatureManager::useExtension(const std::string& extension, bool required) { + const char* clone = strclone(extension.c_str()); + + if (!clone) { + vkcv_log(LogLevel::WARNING, "Extension '%s' is not valid", extension.c_str()); + return false; + } + + if (!isExtensionSupported(extension)) { + vkcv_log((required? LogLevel::ERROR : LogLevel::WARNING), "Extension '%s' is not supported", + extension.c_str()); + + delete[] clone; + return false; + } + + m_activeExtensions.push_back(clone); + return true; + } + + bool FeatureManager::isExtensionActive(const std::string& extension) const { + for (const auto& supported : m_activeExtensions) { + if (0 == strcmp(supported, extension.c_str())) { + return true; + } + } + + return false; + } + + const std::vector<const char*>& FeatureManager::getActiveExtensions() const { + return m_activeExtensions; + } + + bool FeatureManager::useFeatures(const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction, + bool required) { + vk::PhysicalDeviceFeatures features = m_featuresBase.features; + + featureFunction(features); + + if (!checkSupport(features, required)) { + return false; + } + + m_featuresBase.features = features; + return true; + } + + const vk::PhysicalDeviceFeatures2& FeatureManager::getFeatures() const { + return m_featuresBase; + } + +} diff --git a/src/vkcv/Features.cpp b/src/vkcv/Features.cpp new file mode 100644 index 0000000000000000000000000000000000000000..60616685f67084b6875ce14bf09f524a9127688f --- /dev/null +++ b/src/vkcv/Features.cpp @@ -0,0 +1,62 @@ + +#include "vkcv/Features.hpp" + +namespace vkcv { + + Features::Features(const std::initializer_list<std::string>& list) : m_features() { + for (const auto& extension : list) { + requireExtension(extension); + } + } + + void Features::requireExtension(const std::string& extension) { + m_features.emplace_back([extension](FeatureManager& featureManager) { + return featureManager.useExtension(extension, true); + }); + } + + void Features::requireExtensionFeature(const std::string &extension, + const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction) { + m_features.emplace_back([extension, featureFunction](FeatureManager& featureManager) { + if (featureManager.useExtension(extension, true)) { + return featureManager.useFeatures(featureFunction, true); + } else { + return false; + } + }); + } + + void Features::requireFeature(const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction) { + m_features.emplace_back([featureFunction](FeatureManager& featureManager) { + return featureManager.useFeatures(featureFunction, true); + }); + } + + void Features::tryExtension(const std::string& extension) { + m_features.emplace_back([extension](FeatureManager& featureManager) { + return featureManager.useExtension(extension, false); + }); + } + + void Features::tryExtensionFeature(const std::string &extension, + const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction) { + m_features.emplace_back([extension, featureFunction](FeatureManager& featureManager) { + if (featureManager.useExtension(extension, false)) { + return featureManager.useFeatures(featureFunction, false); + } else { + return false; + } + }); + } + + void Features::tryFeature(const std::function<void(vk::PhysicalDeviceFeatures &)> &featureFunction) { + m_features.emplace_back([featureFunction](FeatureManager& featureManager) { + return featureManager.useFeatures(featureFunction, false); + }); + } + + const std::vector<Feature>& Features::getList() const { + return m_features; + } + +} diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp index 1cb6ad3a1187c08cf1aa014ae4ae259591f5c786..4ddd7f8c44c6023a80831bc8b4b092692e84ec86 100644 --- a/src/vkcv/ImageManager.cpp +++ b/src/vkcv/ImageManager.cpp @@ -387,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); diff --git a/src/vkcv/QueueManager.cpp b/src/vkcv/QueueManager.cpp index 15e958b0de929e53170324ade27a9b3663a15d6a..79e15c9b00e4c67fb956bdcd0e8b1ff05261b2f1 100644 --- a/src/vkcv/QueueManager.cpp +++ b/src/vkcv/QueueManager.cpp @@ -44,7 +44,7 @@ namespace vkcv { std::vector<int> prios; for(auto flag: queueFlags) { int prioCount = 0; - for (int i = 0; i < qFamilyProperties.size(); i++) { + for (size_t i = 0; i < qFamilyProperties.size(); i++) { prioCount += (static_cast<uint32_t>(flag & qFamilyProperties[i].queueFlags) != 0) * qFamilyProperties[i].queueCount; } prios.push_back(prioCount); @@ -65,10 +65,14 @@ namespace vkcv { std::vector<std::vector<int>> queueFamilyStatus, initialQueueFamilyStatus; for (auto qFamily : qFamilyProperties) { - int graphicsCount = int(static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eGraphics) != 0) * qFamily.queueCount; - int computeCount = int(static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eCompute) != 0) * qFamily.queueCount; - int transferCount = int(static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eTransfer) != 0) * qFamily.queueCount; - queueFamilyStatus.push_back({graphicsCount, computeCount, transferCount}); + auto graphicsCount = static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eGraphics) != 0? qFamily.queueCount : 0; + auto computeCount = static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eCompute) != 0? qFamily.queueCount : 0; + auto transferCount = static_cast<uint32_t>(qFamily.queueFlags & vk::QueueFlagBits::eTransfer) != 0? qFamily.queueCount : 0; + queueFamilyStatus.push_back({ + static_cast<int>(graphicsCount), + static_cast<int>(computeCount), + static_cast<int>(transferCount) + }); } initialQueueFamilyStatus = queueFamilyStatus; diff --git a/src/vkcv/ShaderProgram.cpp b/src/vkcv/ShaderProgram.cpp index 971797d9a42d071a1730ebf31a0b554f92fa361f..134ba7afd203d3222b37dfaf627a9d4fd3ede1c7 100644 --- a/src/vkcv/ShaderProgram.cpp +++ b/src/vkcv/ShaderProgram.cpp @@ -195,7 +195,25 @@ namespace vkcv { if (maxSetID != -1) { if((int32_t)m_DescriptorSets.size() <= maxSetID) m_DescriptorSets.resize(maxSetID + 1); for (const auto &binding : bindings) { - m_DescriptorSets[binding.first].push_back(binding.second); + //checking if descriptor has already been reflected in another shader stage + bool bindingFound = false; + uint32_t pos = 0; + for (const auto& descriptor : m_DescriptorSets[binding.first]) { + if (binding.second.bindingID == descriptor.bindingID) { + if (binding.second.descriptorType == descriptor.descriptorType && binding.second.descriptorCount == descriptor.descriptorCount) { + //updating descriptor binding with another shader stage + ShaderStages updatedShaders = descriptor.shaderStages | shaderStage; + DescriptorBinding newBinding = DescriptorBinding(binding.second.bindingID, binding.second.descriptorType, binding.second.descriptorCount, updatedShaders); + m_DescriptorSets[binding.first][pos] = newBinding; + bindingFound = true; + break; + } + else vkcv_log(LogLevel::ERROR, "Included shaders contain resources with same identifier but different type or count"); + } + pos++; + } + //append new descriptor if it has not been reflected yet + if(!bindingFound) m_DescriptorSets[binding.first].push_back(binding.second); } } diff --git a/src/vkcv/Window.cpp b/src/vkcv/Window.cpp index aea00fb10d579aea0dc5be789ced3e6582b868bf..072efcd00eb6520fa4f20379721b559668339f6e 100644 --- a/src/vkcv/Window.cpp +++ b/src/vkcv/Window.cpp @@ -4,6 +4,7 @@ * @brief Window class to handle a basic rendering surface and input */ +#include <thread> #include <vector> #include <GLFW/glfw3.h> @@ -80,12 +81,17 @@ namespace vkcv { window->e_key.unlock(); window->e_char.unlock(); window->e_gamepad.unlock(); - } + } + + glfwPollEvents(); + + // 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)); - glfwPollEvents(); - - for (int gamepadIndex = GLFW_JOYSTICK_1; gamepadIndex <= GLFW_JOYSTICK_LAST; gamepadIndex++) { - if (glfwJoystickPresent(gamepadIndex)) { + for (int gamepadIndex = GLFW_JOYSTICK_1; gamepadIndex <= GLFW_JOYSTICK_LAST; gamepadIndex++) { + if (glfwJoystickPresent(gamepadIndex)) { onGamepadEvent(gamepadIndex); } }