From 1789c8fddaf4a936393166dca4810b53450474ae Mon Sep 17 00:00:00 2001 From: Alexander Gauggel <agauggel@uni-koblenz.de> Date: Tue, 17 Aug 2021 13:28:22 +0200 Subject: [PATCH] [#106] Refactored motion blur into separate class for easier reuse --- projects/indirect_dispatch/CMakeLists.txt | 11 +- projects/indirect_dispatch/src/App.cpp | 265 +++++------------- projects/indirect_dispatch/src/App.hpp | 10 +- projects/indirect_dispatch/src/AppConfig.hpp | 1 - projects/indirect_dispatch/src/AppSetup.cpp | 32 +-- projects/indirect_dispatch/src/AppSetup.hpp | 7 +- projects/indirect_dispatch/src/MotionBlur.cpp | 232 +++++++++++++++ projects/indirect_dispatch/src/MotionBlur.hpp | 57 ++++ .../src/MotionBlurConfig.hpp | 14 + .../indirect_dispatch/src/MotionBlurSetup.cpp | 41 +++ .../indirect_dispatch/src/MotionBlurSetup.hpp | 12 + 11 files changed, 437 insertions(+), 245 deletions(-) create mode 100644 projects/indirect_dispatch/src/MotionBlur.cpp create mode 100644 projects/indirect_dispatch/src/MotionBlur.hpp create mode 100644 projects/indirect_dispatch/src/MotionBlurConfig.hpp create mode 100644 projects/indirect_dispatch/src/MotionBlurSetup.cpp create mode 100644 projects/indirect_dispatch/src/MotionBlurSetup.hpp diff --git a/projects/indirect_dispatch/CMakeLists.txt b/projects/indirect_dispatch/CMakeLists.txt index b54a3186..7bc86cbc 100644 --- a/projects/indirect_dispatch/CMakeLists.txt +++ b/projects/indirect_dispatch/CMakeLists.txt @@ -16,10 +16,17 @@ target_sources(indirect_dispatch PRIVATE src/App.cpp src/AppConfig.hpp + src/MotionBlurConfig.hpp src/AppSetup.hpp - src/AppSetup.cpp) - + 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}) diff --git a/projects/indirect_dispatch/src/App.cpp b/projects/indirect_dispatch/src/App.cpp index 02442423..88fdca59 100644 --- a/projects/indirect_dispatch/src/App.cpp +++ b/projects/indirect_dispatch/src/App.cpp @@ -38,36 +38,21 @@ bool App::initialize() { if (!loadComputePass(m_core, "resources/shaders/gammaCorrection.comp", &m_gammaCorrectionPass)) return false; - if(!loadComputePass(m_core, "resources/shaders/motionBlur.comp", &m_motionBlurPass)) - return false; - - if (!loadComputePass(m_core, "resources/shaders/motionVectorMax.comp", &m_motionVectorMaxPass)) - return false; - - if (!loadComputePass(m_core, "resources/shaders/motionVectorMaxNeighbourhood.comp", &m_motionVectorMaxNeighbourhoodPass)) - return false; - - if (!loadComputePass(m_core, "resources/shaders/motionVectorVisualisation.comp", &m_motionVectorVisualisationPass)) - return false; - if (!loadMesh(m_core, "resources/models/sphere.gltf", & m_sphereMesh)) return false; if (!loadMesh(m_core, "resources/models/cube.gltf", &m_cubeMesh)) 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_nearestSampler = m_core.createSampler( - vkcv::SamplerFilterType::NEAREST, - vkcv::SamplerFilterType::NEAREST, - vkcv::SamplerMipmapMode::NEAREST, - vkcv::SamplerAddressMode::CLAMP_TO_EDGE); - m_renderTargets = createRenderTargets(m_core, m_windowWidth, m_windowHeight); const int cameraIndex = m_cameraManager.addCamera(vkcv::camera::ControllerType::PILOT); @@ -85,32 +70,22 @@ void App::run() { const vkcv::DrawcallInfo cubeDrawcall(m_cubeMesh.mesh, {}, 1); vkcv::gui::GUI gui(m_core, m_window); - enum class eDebugView : int { - None = 0, - MotionVector = 1, - MotionVectorMaxTile = 2, - MotionVectorMaxTileNeighbourhood = 3, - OptionCount = 4 }; - - const char* debugViewLabels[] = { - "None", - "Motion vectors", - "Motion vector max tiles", - "Motion vector tile neighbourhood max" }; - enum class eMotionBlurInput : int { - MotionVector = 0, - MotionVectorMaxTile = 1, - MotionVectorMaxTileNeighbourhood = 2, - OptionCount = 3 }; + enum class eMotionVectorVisualisationMode : int { + None = 0, + FullResolution = 1, + MaxTile = 2, + MaxTileNeighbourhood = 3, + OptionCount = 4 }; - const char* motionInputLabels[] = { - "Motion vectors", - "Motion vector max tiles", - "Motion vector tile neighbourhood max" }; + const char* motionVectorVisualisationModeLabels[] = { + "None", + "Full resolution", + "Max tiles", + "Tile neighbourhood max" }; - eDebugView debugView = eDebugView::None; - eMotionBlurInput motionBlurInput = eMotionBlurInput::MotionVectorMaxTileNeighbourhood; + eMotionVectorVisualisationMode motionVectorVisualisationMode = eMotionVectorVisualisationMode::None; + eMotionVectorMode motionBlurMotionMode = eMotionVectorMode::MaxTileNeighbourhood; float objectVerticalSpeed = 5; float motionBlurMinVelocity = 0.001; @@ -136,6 +111,7 @@ void App::run() { m_windowHeight = swapchainHeight; m_renderTargets = createRenderTargets(m_core, m_windowWidth, m_windowHeight); + m_motionBlur.setResolution(m_windowWidth, m_windowHeight); } auto frameEndTime = std::chrono::system_clock::now(); @@ -186,53 +162,6 @@ void App::run() { { cubeDrawcall }, prepassRenderTargets); - // motion vector max tiles - vkcv::DescriptorWrites motionVectorMaxTilesDescriptorWrites; - motionVectorMaxTilesDescriptorWrites.sampledImageWrites = { - vkcv::SampledImageDescriptorWrite(0, m_renderTargets.motionBuffer) }; - motionVectorMaxTilesDescriptorWrites.samplerWrites = { - vkcv::SamplerDescriptorWrite(1, m_linearSampler) }; - motionVectorMaxTilesDescriptorWrites.storageImageWrites = { - vkcv::StorageImageDescriptorWrite(2, m_renderTargets.motionMax)}; - - m_core.writeDescriptorSet(m_motionVectorMaxPass.descriptorSet, motionVectorMaxTilesDescriptorWrites); - - m_core.prepareImageForSampling(cmdStream, m_renderTargets.motionBuffer); - m_core.prepareImageForStorage(cmdStream, m_renderTargets.motionMax); - - const uint32_t motionTileDispatchCounts[3] = { - (m_core.getImageWidth( m_renderTargets.motionMax) + 7) / 8, - (m_core.getImageHeight(m_renderTargets.motionMax) + 7) / 8, - 1 }; - - m_core.recordComputeDispatchToCmdStream( - cmdStream, - m_motionVectorMaxPass.pipeline, - motionTileDispatchCounts, - { vkcv::DescriptorSetUsage(0, m_core.getDescriptorSet(m_motionVectorMaxPass.descriptorSet).vulkanHandle) }, - vkcv::PushConstants(0)); - - // motion vector max neighbourhood - vkcv::DescriptorWrites motionVectorMaxNeighbourhoodDescriptorWrites; - motionVectorMaxNeighbourhoodDescriptorWrites.sampledImageWrites = { - vkcv::SampledImageDescriptorWrite(0, m_renderTargets.motionMax) }; - motionVectorMaxNeighbourhoodDescriptorWrites.samplerWrites = { - vkcv::SamplerDescriptorWrite(1, m_linearSampler) }; - motionVectorMaxNeighbourhoodDescriptorWrites.storageImageWrites = { - vkcv::StorageImageDescriptorWrite(2, m_renderTargets.motionMaxNeighbourhood) }; - - m_core.writeDescriptorSet(m_motionVectorMaxNeighbourhoodPass.descriptorSet, motionVectorMaxNeighbourhoodDescriptorWrites); - - m_core.prepareImageForSampling(cmdStream, m_renderTargets.motionMax); - m_core.prepareImageForStorage(cmdStream, m_renderTargets.motionMaxNeighbourhood); - - m_core.recordComputeDispatchToCmdStream( - cmdStream, - m_motionVectorMaxNeighbourhoodPass.pipeline, - motionTileDispatchCounts, - { vkcv::DescriptorSetUsage(0, m_core.getDescriptorSet(m_motionVectorMaxNeighbourhoodPass.descriptorSet).vulkanHandle) }, - vkcv::PushConstants(0)); - // main pass const std::vector<vkcv::ImageHandle> renderTargets = { m_renderTargets.colorBuffer, @@ -262,122 +191,52 @@ void App::run() { renderTargets); // motion blur - vkcv::ImageHandle motionBuffer; - if (motionBlurInput == eMotionBlurInput::MotionVector) - motionBuffer = m_renderTargets.motionBuffer; - else if (motionBlurInput == eMotionBlurInput::MotionVectorMaxTile) - motionBuffer = m_renderTargets.motionMax; - else if (motionBlurInput == eMotionBlurInput::MotionVectorMaxTileNeighbourhood) - motionBuffer = m_renderTargets.motionMaxNeighbourhood; - else { - vkcv_log(vkcv::LogLevel::ERROR, "Unknown eMotionInput enum value"); - motionBuffer = m_renderTargets.motionBuffer; - } + vkcv::ImageHandle motionBlurOutput; - vkcv::DescriptorWrites motionBlurDescriptorWrites; - motionBlurDescriptorWrites.sampledImageWrites = { - vkcv::SampledImageDescriptorWrite(0, m_renderTargets.colorBuffer), - vkcv::SampledImageDescriptorWrite(1, m_renderTargets.depthBuffer), - vkcv::SampledImageDescriptorWrite(2, motionBuffer) }; - motionBlurDescriptorWrites.samplerWrites = { - vkcv::SamplerDescriptorWrite(3, m_nearestSampler) }; - motionBlurDescriptorWrites.storageImageWrites = { - vkcv::StorageImageDescriptorWrite(4, m_renderTargets.motionBlurOutput) }; + if (motionVectorVisualisationMode == eMotionVectorVisualisationMode::None) { + const float microsecondToSecond = 0.000001; + const float fDeltaTimeSeconds = microsecondToSecond * std::chrono::duration_cast<std::chrono::microseconds>(frameEndTime - frameStartTime).count(); - m_core.writeDescriptorSet(m_motionBlurPass.descriptorSet, motionBlurDescriptorWrites); + float cameraNear; + float cameraFar; + m_cameraManager.getActiveCamera().getNearFar(cameraNear, cameraFar); - 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.prepareImageForStorage(cmdStream, m_renderTargets.motionBlurOutput); - m_core.prepareImageForSampling(cmdStream, m_renderTargets.colorBuffer); - m_core.prepareImageForSampling(cmdStream, m_renderTargets.depthBuffer); - m_core.prepareImageForSampling(cmdStream, motionBuffer); - - const float microsecondToSecond = 0.000001; - const float fDeltatimeSeconds = microsecondToSecond * std::chrono::duration_cast<std::chrono::microseconds>(frameEndTime - frameStartTime).count(); - - // must match layout in "motionBlur.comp" - struct MotionBlurConstantData { - float motionFactor; - float minVelocity; - float cameraNearPlane; - float cameraFarPlane; - }; - MotionBlurConstantData motionBlurConstantData; - - // small mouse movements are restricted to pixel level and therefore quite unprecise - // therefore extrapolating movement at high framerates results in big jerky movements - // this results in wide sudden motion blur, which looks quite bad - // as a workaround the time scale is limited to a maximum value - const float motionBlurTimeScaleMax = 1.f / 60; - const float deltaTimeMotionBlur = std::max(fDeltatimeSeconds, motionBlurTimeScaleMax); - - motionBlurConstantData.motionFactor = 1 / (deltaTimeMotionBlur * cameraShutterSpeedInverse); - motionBlurConstantData.minVelocity = motionBlurMinVelocity; - - float cameraNear, cameraFar; - m_cameraManager.getActiveCamera().getNearFar(cameraNear, cameraFar); - motionBlurConstantData.cameraNearPlane = cameraNear; - motionBlurConstantData.cameraFarPlane = cameraFar; - - vkcv::PushConstants motionBlurPushConstants(sizeof(motionBlurConstantData)); - motionBlurPushConstants.appendDrawcall(motionBlurConstantData); - - m_core.recordComputeDispatchToCmdStream( - cmdStream, - m_motionBlurPass.pipeline, - fullScreenImageDispatch, - { vkcv::DescriptorSetUsage(0, m_core.getDescriptorSet(m_motionBlurPass.descriptorSet).vulkanHandle) }, - motionBlurPushConstants); - - // motion vector debug visualisation - // writes to motion blur output - if (debugView != eDebugView::None) { - vkcv::ImageHandle visualisationInput; - if (debugView == eDebugView::MotionVector) - visualisationInput = m_renderTargets.motionBuffer; - else if (debugView == eDebugView::MotionVectorMaxTile) - visualisationInput = m_renderTargets.motionMax; - else if (debugView == eDebugView::MotionVectorMaxTileNeighbourhood) - visualisationInput = m_renderTargets.motionMaxNeighbourhood; + motionBlurOutput = m_motionBlur.render( + cmdStream, + m_renderTargets.motionBuffer, + m_renderTargets.colorBuffer, + m_renderTargets.depthBuffer, + motionBlurMotionMode, + cameraNear, + cameraFar, + fDeltaTimeSeconds, + cameraShutterSpeedInverse, + motionBlurMinVelocity); + } + else { + eMotionVectorMode debugViewMode; + if (motionVectorVisualisationMode == eMotionVectorVisualisationMode::FullResolution) + debugViewMode = eMotionVectorMode::FullResolution; + else if(motionVectorVisualisationMode == eMotionVectorVisualisationMode::MaxTile) + debugViewMode = eMotionVectorMode::MaxTile; + else if (motionVectorVisualisationMode == eMotionVectorVisualisationMode::MaxTileNeighbourhood) + debugViewMode = eMotionVectorMode::MaxTileNeighbourhood; else { - vkcv_log(vkcv::LogLevel::ERROR, "Unknown eDebugView enum value"); - visualisationInput = m_renderTargets.motionBlurOutput; + vkcv_log(vkcv::LogLevel::ERROR, "Unknown eMotionVectorMode enum option"); + debugViewMode = eMotionVectorMode::FullResolution; } - vkcv::DescriptorWrites motionVectorVisualisationDescriptorWrites; - motionVectorVisualisationDescriptorWrites.sampledImageWrites = { - vkcv::SampledImageDescriptorWrite(0, visualisationInput) }; - motionVectorVisualisationDescriptorWrites.samplerWrites = { - vkcv::SamplerDescriptorWrite(1, m_nearestSampler) }; - motionVectorVisualisationDescriptorWrites.storageImageWrites = { - vkcv::StorageImageDescriptorWrite(2, m_renderTargets.motionBlurOutput) }; - - m_core.writeDescriptorSet( - m_motionVectorVisualisationPass.descriptorSet, - motionVectorVisualisationDescriptorWrites); - - m_core.prepareImageForSampling(cmdStream, visualisationInput); - m_core.prepareImageForStorage(cmdStream, m_renderTargets.motionBlurOutput); - - vkcv::PushConstants motionVectorVisualisationPushConstants(sizeof(float)); - motionVectorVisualisationPushConstants.appendDrawcall(motionVectorVisualisationRange); - - m_core.recordComputeDispatchToCmdStream( + motionBlurOutput = m_motionBlur.renderMotionVectorVisualisation( cmdStream, - m_motionVectorVisualisationPass.pipeline, - fullScreenImageDispatch, - { vkcv::DescriptorSetUsage(0, m_core.getDescriptorSet(m_motionVectorVisualisationPass.descriptorSet).vulkanHandle) }, - motionVectorVisualisationPushConstants); + m_renderTargets.motionBuffer, + debugViewMode, + motionVectorVisualisationRange); } // gamma correction vkcv::DescriptorWrites gammaCorrectionDescriptorWrites; gammaCorrectionDescriptorWrites.sampledImageWrites = { - vkcv::SampledImageDescriptorWrite(0, m_renderTargets.motionBlurOutput) }; + vkcv::SampledImageDescriptorWrite(0, motionBlurOutput) }; gammaCorrectionDescriptorWrites.samplerWrites = { vkcv::SamplerDescriptorWrite(1, m_linearSampler) }; gammaCorrectionDescriptorWrites.storageImageWrites = { @@ -385,9 +244,14 @@ void App::run() { m_core.writeDescriptorSet(m_gammaCorrectionPass.descriptorSet, gammaCorrectionDescriptorWrites); - m_core.prepareImageForSampling(cmdStream, m_renderTargets.motionBlurOutput); + 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, @@ -403,19 +267,18 @@ void App::run() { ImGui::Combo( "Debug view", - reinterpret_cast<int*>(&debugView), - debugViewLabels, - static_cast<int>(eDebugView::OptionCount)); + reinterpret_cast<int*>(&motionVectorVisualisationMode), + motionVectorVisualisationModeLabels, + static_cast<int>(eMotionVectorVisualisationMode::OptionCount)); - if (debugView != eDebugView::None) { + if (motionVectorVisualisationMode != eMotionVectorVisualisationMode::None) ImGui::InputFloat("Motion vector visualisation range", &motionVectorVisualisationRange); - } ImGui::Combo( "Motion blur input", - reinterpret_cast<int*>(&motionBlurInput), - motionInputLabels, - static_cast<int>(eMotionBlurInput::OptionCount)); + reinterpret_cast<int*>(&motionBlurMotionMode), + MotionVectorModeLabels, + static_cast<int>(eMotionVectorMode::OptionCount)); ImGui::InputFloat("Object movement speed", &objectVerticalSpeed); ImGui::InputInt("Camera shutter speed inverse", &cameraShutterSpeedInverse); diff --git a/projects/indirect_dispatch/src/App.hpp b/projects/indirect_dispatch/src/App.hpp index 49743e4a..9871776f 100644 --- a/projects/indirect_dispatch/src/App.hpp +++ b/projects/indirect_dispatch/src/App.hpp @@ -2,6 +2,7 @@ #include <vkcv/Core.hpp> #include <vkcv/camera/CameraManager.hpp> #include "AppSetup.hpp" +#include "MotionBlur.hpp" class App { public: @@ -18,6 +19,8 @@ private: vkcv::Core m_core; vkcv::camera::CameraManager m_cameraManager; + MotionBlur m_motionBlur; + MeshResources m_sphereMesh; MeshResources m_cubeMesh; @@ -27,12 +30,7 @@ private: GraphicPassHandles m_skyPrePass; ComputePassHandles m_gammaCorrectionPass; - ComputePassHandles m_motionBlurPass; - ComputePassHandles m_motionVectorMaxPass; - ComputePassHandles m_motionVectorMaxNeighbourhoodPass; - ComputePassHandles m_motionVectorVisualisationPass; - RenderTargets m_renderTargets; + AppRenderTargets m_renderTargets; vkcv::SamplerHandle m_linearSampler; - vkcv::SamplerHandle m_nearestSampler; }; \ No newline at end of file diff --git a/projects/indirect_dispatch/src/AppConfig.hpp b/projects/indirect_dispatch/src/AppConfig.hpp index 370b9684..c89c34ea 100644 --- a/projects/indirect_dispatch/src/AppConfig.hpp +++ b/projects/indirect_dispatch/src/AppConfig.hpp @@ -7,5 +7,4 @@ namespace AppConfig{ const vk::Format depthBufferFormat = vk::Format::eD32Sfloat; const vk::Format colorBufferFormat = vk::Format::eB10G11R11UfloatPack32; const vk::Format motionBufferFormat = vk::Format::eR16G16Sfloat; - const uint32_t maxMotionTileSize = 20; // must match "motionTileSize" in motionVectorMax.comp } \ No newline at end of file diff --git a/projects/indirect_dispatch/src/AppSetup.cpp b/projects/indirect_dispatch/src/AppSetup.cpp index 0733aee4..1d27e141 100644 --- a/projects/indirect_dispatch/src/AppSetup.cpp +++ b/projects/indirect_dispatch/src/AppSetup.cpp @@ -232,9 +232,9 @@ bool loadComputePass(vkcv::Core& core, const std::filesystem::path& path, Comput return true; } -RenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const uint32_t height) { +AppRenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const uint32_t height) { - RenderTargets targets; + AppRenderTargets targets; targets.depthBuffer = core.createImage( AppConfig::depthBufferFormat, @@ -252,14 +252,6 @@ RenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const false, true).getHandle(); - targets.motionBlurOutput = core.createImage( - AppConfig::colorBufferFormat, - width, - height, - 1, - false, - true).getHandle(); - targets.motionBuffer = core.createImage( AppConfig::motionBufferFormat, width, @@ -269,25 +261,5 @@ RenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const false, true).getHandle(); - // divide and ceil to int - const uint32_t motionMaxWidth = (width + (AppConfig::maxMotionTileSize - 1)) / AppConfig::maxMotionTileSize; - const uint32_t motionMaxheight = (height + (AppConfig::maxMotionTileSize - 1)) / AppConfig::maxMotionTileSize; - - targets.motionMax = core.createImage( - AppConfig::motionBufferFormat, - motionMaxWidth, - motionMaxheight, - 1, - false, - true).getHandle(); - - targets.motionMaxNeighbourhood = core.createImage( - AppConfig::motionBufferFormat, - motionMaxWidth, - motionMaxheight, - 1, - 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 index adb8c198..f4aaad0a 100644 --- a/projects/indirect_dispatch/src/AppSetup.hpp +++ b/projects/indirect_dispatch/src/AppSetup.hpp @@ -1,13 +1,10 @@ #pragma once #include <vkcv/Core.hpp> -struct RenderTargets { +struct AppRenderTargets { vkcv::ImageHandle depthBuffer; vkcv::ImageHandle colorBuffer; - vkcv::ImageHandle motionBlurOutput; vkcv::ImageHandle motionBuffer; - vkcv::ImageHandle motionMax; - vkcv::ImageHandle motionMaxNeighbourhood; }; struct GraphicPassHandles { @@ -44,4 +41,4 @@ bool loadSkyPrePass(vkcv::Core& core, GraphicPassHandles* outHandles); bool loadComputePass(vkcv::Core& core, const std::filesystem::path& path, ComputePassHandles* outComputePass); -RenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const uint32_t height); \ No newline at end of file +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 00000000..946bc008 --- /dev/null +++ b/projects/indirect_dispatch/src/MotionBlur.cpp @@ -0,0 +1,232 @@ +#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/motionVectorMax.comp", &m_motionVectorMaxPass)) + return false; + + if (!loadComputePass(*m_core, "resources/shaders/motionVectorMaxNeighbourhood.comp", &m_motionVectorMaxNeighbourhoodPass)) + return false; + + if (!loadComputePass(*m_core, "resources/shaders/motionVectorVisualisation.comp", &m_motionVectorVisualisationPass)) + return false; + + 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); +} + +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 eMotionVectorMode motionVectorMode, + const float cameraNear, + const float cameraFar, + const float deltaTimeSeconds, + const float cameraShutterSpeedInverse, + const float motionBlurMinVelocity) { + + computeMotionTiles(cmdStream, motionBufferFullRes); + + vkcv::ImageHandle inputMotionBuffer; + if (motionVectorMode == eMotionVectorMode::FullResolution) + inputMotionBuffer = motionBufferFullRes; + else if (motionVectorMode == eMotionVectorMode::MaxTile) + inputMotionBuffer = m_renderTargets.motionMax; + else if (motionVectorMode == eMotionVectorMode::MaxTileNeighbourhood) + inputMotionBuffer = m_renderTargets.motionMaxNeighbourhood; + else { + vkcv_log(vkcv::LogLevel::ERROR, "Unknown eMotionInput enum value"); + inputMotionBuffer = motionBufferFullRes; + } + + vkcv::DescriptorWrites motionBlurDescriptorWrites; + motionBlurDescriptorWrites.sampledImageWrites = { + vkcv::SampledImageDescriptorWrite(0, colorBuffer), + vkcv::SampledImageDescriptorWrite(1, depthBuffer), + vkcv::SampledImageDescriptorWrite(2, inputMotionBuffer) }; + motionBlurDescriptorWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(3, m_nearestSampler) }; + motionBlurDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(4, m_renderTargets.outputColor) }; + + m_core->writeDescriptorSet(m_motionBlurPass.descriptorSet, motionBlurDescriptorWrites); + + // must match layout in "motionBlur.comp" + struct MotionBlurConstantData { + float motionFactor; + float minVelocity; + float cameraNearPlane; + float cameraFarPlane; + }; + MotionBlurConstantData motionBlurConstantData; + + const float deltaTimeMotionBlur = std::max(deltaTimeSeconds, MotionBlurConfig::timeScaleMax); + + motionBlurConstantData.motionFactor = 1 / (deltaTimeMotionBlur * cameraShutterSpeedInverse); + motionBlurConstantData.minVelocity = motionBlurMinVelocity; + motionBlurConstantData.cameraNearPlane = cameraNear; + motionBlurConstantData.cameraFarPlane = cameraFar; + + vkcv::PushConstants motionBlurPushConstants(sizeof(motionBlurConstantData)); + motionBlurPushConstants.appendDrawcall(motionBlurConstantData); + + m_core->prepareImageForStorage(cmdStream, m_renderTargets.outputColor); + m_core->prepareImageForSampling(cmdStream, colorBuffer); + m_core->prepareImageForSampling(cmdStream, depthBuffer); + m_core->prepareImageForSampling(cmdStream, inputMotionBuffer); + + const auto fullscreenDispatchSizes = computeFullscreenDispatchSize( + m_core->getImageWidth(m_renderTargets.outputColor), + m_core->getImageHeight(m_renderTargets.outputColor), + 8); + + m_core->recordComputeDispatchToCmdStream( + cmdStream, + m_motionBlurPass.pipeline, + fullscreenDispatchSizes.data(), + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_motionBlurPass.descriptorSet).vulkanHandle) }, + motionBlurPushConstants); + + return m_renderTargets.outputColor; +} + +vkcv::ImageHandle MotionBlur::renderMotionVectorVisualisation( + const vkcv::CommandStreamHandle cmdStream, + const vkcv::ImageHandle motionBuffer, + const eMotionVectorMode debugView, + const float velocityRange) { + + computeMotionTiles(cmdStream, motionBuffer); + + vkcv::ImageHandle visualisationInput; + if ( debugView == eMotionVectorMode::FullResolution) + visualisationInput = motionBuffer; + else if (debugView == eMotionVectorMode::MaxTile) + visualisationInput = m_renderTargets.motionMax; + else if (debugView == eMotionVectorMode::MaxTileNeighbourhood) + visualisationInput = m_renderTargets.motionMaxNeighbourhood; + 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 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) }; + + m_core->writeDescriptorSet(m_motionVectorMaxPass.descriptorSet, motionVectorMaxTilesDescriptorWrites); + + m_core->prepareImageForSampling(cmdStream, motionBufferFullRes); + m_core->prepareImageForStorage(cmdStream, m_renderTargets.motionMax); + + 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_motionVectorMaxPass.pipeline, + motionTileDispatchCounts.data(), + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_motionVectorMaxPass.descriptorSet).vulkanHandle) }, + vkcv::PushConstants(0)); + + // motion vector max neighbourhood + vkcv::DescriptorWrites motionVectorMaxNeighbourhoodDescriptorWrites; + motionVectorMaxNeighbourhoodDescriptorWrites.sampledImageWrites = { + vkcv::SampledImageDescriptorWrite(0, m_renderTargets.motionMax) }; + motionVectorMaxNeighbourhoodDescriptorWrites.samplerWrites = { + vkcv::SamplerDescriptorWrite(1, m_nearestSampler) }; + motionVectorMaxNeighbourhoodDescriptorWrites.storageImageWrites = { + vkcv::StorageImageDescriptorWrite(2, m_renderTargets.motionMaxNeighbourhood) }; + + m_core->writeDescriptorSet(m_motionVectorMaxNeighbourhoodPass.descriptorSet, motionVectorMaxNeighbourhoodDescriptorWrites); + + m_core->prepareImageForSampling(cmdStream, m_renderTargets.motionMax); + m_core->prepareImageForStorage(cmdStream, m_renderTargets.motionMaxNeighbourhood); + + m_core->recordComputeDispatchToCmdStream( + cmdStream, + m_motionVectorMaxNeighbourhoodPass.pipeline, + motionTileDispatchCounts.data(), + { vkcv::DescriptorSetUsage(0, m_core->getDescriptorSet(m_motionVectorMaxNeighbourhoodPass.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 00000000..1812a536 --- /dev/null +++ b/projects/indirect_dispatch/src/MotionBlur.hpp @@ -0,0 +1,57 @@ +#pragma once +#include "AppSetup.hpp" +#include "MotionBlurSetup.hpp" + +// selection for motion blur input and visualisation +enum class eMotionVectorMode : int { + FullResolution = 0, + MaxTile = 1, + MaxTileNeighbourhood = 2, + OptionCount = 3 +}; + +static const char* MotionVectorModeLabels[3] = { + "Full resolution", + "Max tile", + "Tile neighbourhood max" }; + +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 eMotionVectorMode motionVectorMode, + const float cameraNear, + const float cameraFar, + const float deltaTimeSeconds, + const float cameraShutterSpeedInverse, + const float motionBlurMinVelocity); + + vkcv::ImageHandle MotionBlur::renderMotionVectorVisualisation( + const vkcv::CommandStreamHandle cmdStream, + const vkcv::ImageHandle motionBuffer, + const eMotionVectorMode debugView, + 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_motionVectorMaxPass; + ComputePassHandles m_motionVectorMaxNeighbourhoodPass; + ComputePassHandles m_motionVectorVisualisationPass; +}; \ 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 00000000..ecd7f8f8 --- /dev/null +++ b/projects/indirect_dispatch/src/MotionBlurConfig.hpp @@ -0,0 +1,14 @@ +#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 = 20; // must match "motionTileSize" in motionVectorMax.comp + + // small mouse movements are restricted to pixel level and therefore quite unprecise + // therefore extrapolating movement at high framerates results in big jerky movements + // this results in wide sudden motion blur, which looks quite bad + // as a workaround the time scale is limited to a maximum value + const float timeScaleMax = 1.f / 60; +} \ 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 00000000..2ac1e7d1 --- /dev/null +++ b/projects/indirect_dispatch/src/MotionBlurSetup.cpp @@ -0,0 +1,41 @@ +#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.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 00000000..9c104ce7 --- /dev/null +++ b/projects/indirect_dispatch/src/MotionBlurSetup.hpp @@ -0,0 +1,12 @@ +#pragma once +#include <vkcv/Core.hpp> + +struct MotionBlurRenderTargets { + vkcv::ImageHandle outputColor; + vkcv::ImageHandle motionMax; + vkcv::ImageHandle motionMaxNeighbourhood; +}; + +namespace MotionBlurSetup { + MotionBlurRenderTargets createRenderTargets(const uint32_t width, const uint32_t height, vkcv::Core& core); +} \ No newline at end of file -- GitLab