From a8a3b210fb69f3c121a3ae6f47653fe7bb559cfd Mon Sep 17 00:00:00 2001
From: Alexander Gauggel <agauggel@uni-koblenz.de>
Date: Wed, 11 Aug 2021 12:51:44 +0200
Subject: [PATCH] [#106] Add motion vector max and neighbourhood max options
 for motion blur

---
 .../resources/shaders/gammaCorrection.comp    |  10 +-
 .../resources/shaders/mesh.frag               |   4 +-
 .../resources/shaders/mesh.vert               |   2 +
 .../resources/shaders/motionBlurDummy.comp    |   2 -
 .../resources/shaders/motionVectorMax.comp    |  38 +++++
 .../shaders/motionVectorMaxNeighbourhood.comp |  36 ++++
 projects/indirect_dispatch/src/App.cpp        | 161 ++++++++++++++++--
 projects/indirect_dispatch/src/App.hpp        |   2 +
 projects/indirect_dispatch/src/AppSetup.cpp   |  21 +++
 projects/indirect_dispatch/src/AppSetup.hpp   |   2 +
 10 files changed, 254 insertions(+), 24 deletions(-)
 create mode 100644 projects/indirect_dispatch/resources/shaders/motionVectorMax.comp
 create mode 100644 projects/indirect_dispatch/resources/shaders/motionVectorMaxNeighbourhood.comp

diff --git a/projects/indirect_dispatch/resources/shaders/gammaCorrection.comp b/projects/indirect_dispatch/resources/shaders/gammaCorrection.comp
index 59540806..d1c3c049 100644
--- a/projects/indirect_dispatch/resources/shaders/gammaCorrection.comp
+++ b/projects/indirect_dispatch/resources/shaders/gammaCorrection.comp
@@ -9,13 +9,13 @@ layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
 
 void main(){
 
-    if(any(greaterThanEqual(gl_GlobalInvocationID.xy, imageSize(outImage))))
+    ivec2 outImageRes = imageSize(outImage);
+    ivec2 coord       = ivec2(gl_GlobalInvocationID.xy);
+
+    if(any(greaterThanEqual(coord, outImageRes)))
         return;
    
-    ivec2   textureRes  = textureSize(sampler2D(inTexture, textureSampler), 0);
-    ivec2   coord       = ivec2(gl_GlobalInvocationID.xy);
-    vec2    uv          = vec2(coord) / textureRes;
-
+    vec2 uv             = vec2(coord) / outImageRes;
     vec3 linearColor    = texture(sampler2D(inTexture, textureSampler), uv).rgb;
     
     // in case of motion vector visualisation negative values are possible
diff --git a/projects/indirect_dispatch/resources/shaders/mesh.frag b/projects/indirect_dispatch/resources/shaders/mesh.frag
index 8d032cab..46d808c4 100644
--- a/projects/indirect_dispatch/resources/shaders/mesh.frag
+++ b/projects/indirect_dispatch/resources/shaders/mesh.frag
@@ -2,9 +2,11 @@
 #extension GL_ARB_separate_shader_objects : enable
 
 layout(location = 0) in vec3 passNormal;
+layout(location = 1) in vec3 passPos;
 
 layout(location = 0) out vec3 outColor;
 
 void main()	{
-	outColor = passNormal * 0.5 + 0.5;
+	// outColor = passNormal * 0.5 + 0.5;
+    outColor = vec3(sin(passPos.y * 100) * 0.5 + 0.5);
 }
\ No newline at end of file
diff --git a/projects/indirect_dispatch/resources/shaders/mesh.vert b/projects/indirect_dispatch/resources/shaders/mesh.vert
index 5e1f72dc..769867e3 100644
--- a/projects/indirect_dispatch/resources/shaders/mesh.vert
+++ b/projects/indirect_dispatch/resources/shaders/mesh.vert
@@ -5,6 +5,7 @@ layout(location = 0) in vec3 inPosition;
 layout(location = 1) in vec3 inNormal;
 
 layout(location = 0) out vec3 passNormal;
+layout(location = 1) out vec3 passPos;
 
 layout( push_constant ) uniform constants{
     mat4 mvp;
@@ -13,4 +14,5 @@ layout( push_constant ) uniform constants{
 void main()	{
 	gl_Position = mvp * vec4(inPosition, 1.0);
 	passNormal  = inNormal;
+    passPos     = inPosition;
 }
\ No newline at end of file
diff --git a/projects/indirect_dispatch/resources/shaders/motionBlurDummy.comp b/projects/indirect_dispatch/resources/shaders/motionBlurDummy.comp
index a3a21557..8801203b 100644
--- a/projects/indirect_dispatch/resources/shaders/motionBlurDummy.comp
+++ b/projects/indirect_dispatch/resources/shaders/motionBlurDummy.comp
@@ -18,8 +18,6 @@ void main(){
     vec2    uv          = vec2(coord) / textureRes;
 
     vec2    motion      = texture(sampler2D(inMotion, textureSampler), uv).rg;
-    float   blurFactor  = 5;
-    motion              *= blurFactor;
 
     vec3        color       = vec3(0);
     const int   sampleCount = 16;
diff --git a/projects/indirect_dispatch/resources/shaders/motionVectorMax.comp b/projects/indirect_dispatch/resources/shaders/motionVectorMax.comp
new file mode 100644
index 00000000..f5a9fa3b
--- /dev/null
+++ b/projects/indirect_dispatch/resources/shaders/motionVectorMax.comp
@@ -0,0 +1,38 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+layout(set=0, binding=0)        uniform texture2D   inMotion;
+layout(set=0, binding=1)        uniform sampler     textureSampler;
+layout(set=0, binding=2, rgba8) uniform image2D     outMotionMax;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+const int motionTileSize = 8;
+
+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);
+    
+    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;
+            }
+        }
+    }
+
+    imageStore(outMotionMax, motionTileCoord, vec4(motionMax, 0, 0));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/resources/shaders/motionVectorMaxNeighbourhood.comp b/projects/indirect_dispatch/resources/shaders/motionVectorMaxNeighbourhood.comp
new file mode 100644
index 00000000..85d45235
--- /dev/null
+++ b/projects/indirect_dispatch/resources/shaders/motionVectorMaxNeighbourhood.comp
@@ -0,0 +1,36 @@
+#version 440
+#extension GL_GOOGLE_include_directive : enable
+
+layout(set=0, binding=0)        uniform texture2D   inMotionMax;
+layout(set=0, binding=1)        uniform sampler     textureSampler;
+layout(set=0, binding=2, rgba8) uniform image2D     outMotionMaxNeighbourhood;
+
+layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
+
+const int motionTileSize = 8;
+
+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);
+    
+    for(int x = -1; x <= 1; x++){
+        for(int y = -1; y <= 1; y++){
+            ivec2   sampleCoord     = motionTileCoord + ivec2(x, y);
+            vec2    motionSample    = texelFetch(sampler2D(inMotionMax, textureSampler), sampleCoord, 0).rg;
+            float   velocitySample  = length(motionSample);
+            if(velocitySample > velocityMax){
+                velocityMax = velocitySample;
+                motionMax   = motionSample;
+            }
+        }
+    }
+
+    imageStore(outMotionMaxNeighbourhood, motionTileCoord, vec4(motionMax, 0, 0));
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/App.cpp b/projects/indirect_dispatch/src/App.cpp
index 8ab6bb88..fa8f260a 100644
--- a/projects/indirect_dispatch/src/App.cpp
+++ b/projects/indirect_dispatch/src/App.cpp
@@ -41,6 +41,12 @@ bool App::initialize() {
 	if(!loadComputePass(m_core, "resources/shaders/motionBlurDummy.comp", &m_motionBlurDummyPass))
 		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 (!loadMesh(m_core, "resources/models/sphere.gltf", & m_sphereMesh))
 		return false;
 
@@ -62,15 +68,43 @@ bool App::initialize() {
 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    sphereDrawcall(m_sphereMesh.mesh, {}, 1);
 	const vkcv::DrawcallInfo    cubeDrawcall(m_cubeMesh.mesh, {}, 1);
 
 	vkcv::gui::GUI gui(m_core, m_window);
-
-	bool drawMotionVectors = false;
-
-	glm::mat4 previousFrameViewProjection = m_cameraManager.getActiveCamera().getMVP();
+	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 };
+
+	const char* motionInputLabels[] = {
+		"Motion vectors",
+		"Motion vector max tiles",
+		"Motion vector tile neighbourhood max" };
+
+	eDebugView          debugView       = eDebugView::None;
+	eMotionBlurInput    motionBlurInput = eMotionBlurInput::MotionVectorMaxTileNeighbourhood;
+
+	float objectVerticalSpeed = 0.005;
+
+	glm::mat4 mvpPrevious               = glm::mat4(1.f);
+	glm::mat4 viewProjectionPrevious    = m_cameraManager.getActiveCamera().getMVP();
 
 	while (m_window.isWindowOpen()) {
 		vkcv::Window::pollEvents();
@@ -97,12 +131,18 @@ void App::run() {
 		m_cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
 		const glm::mat4 viewProjection = m_cameraManager.getActiveCamera().getMVP();
 
+		const auto      time            = frameEndTime - appStartTime;
+		const float     fCurrentTime    = std::chrono::duration_cast<std::chrono::milliseconds>(time).count();
+		const float     currentHeight   = glm::sin(fCurrentTime * objectVerticalSpeed);
+		const glm::mat4 modelMatrix     = glm::translate(glm::mat4(1), glm::vec3(0, currentHeight, 0));
+		const glm::mat4 mvp             = viewProjection * modelMatrix;
+
 		const vkcv::CommandStreamHandle cmdStream = m_core.createCommandStream(vkcv::QueueType::Graphics);
 
 		// prepass
 		glm::mat4 prepassMatrices[2] = {
-			viewProjection,
-			previousFrameViewProjection };
+			mvp,
+			mvpPrevious };
 		vkcv::PushConstants prepassPushConstants(sizeof(glm::mat4)*2);
 		prepassPushConstants.appendDrawcall(prepassMatrices);
 
@@ -119,21 +159,74 @@ void App::run() {
 			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,
-			prepassPushConstants,
-			{ sphereDrawcall },
+			skyPrepassPushConstants,
+			{ 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, 
 			m_renderTargets.depthBuffer };
 
 		vkcv::PushConstants meshPushConstants(sizeof(glm::mat4));
-		meshPushConstants.appendDrawcall(viewProjection);
+		meshPushConstants.appendDrawcall(mvp);
 
 		m_core.recordDrawcallsToCmdStream(
 			cmdStream,
@@ -145,7 +238,7 @@ void App::run() {
 
 		// sky
 		vkcv::PushConstants skyPushConstants(sizeof(glm::mat4));
-		skyPushConstants.appendDrawcall(viewProjection);
+		skyPushConstants.appendDrawcall(viewProjectionPrevious);
 
 		m_core.recordDrawcallsToCmdStream(
 			cmdStream,
@@ -156,10 +249,22 @@ 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::DescriptorWrites motionBlurDescriptorWrites;
 		motionBlurDescriptorWrites.sampledImageWrites = {
 			vkcv::SampledImageDescriptorWrite(0, m_renderTargets.colorBuffer),
-			vkcv::SampledImageDescriptorWrite(1, m_renderTargets.motionBuffer) };
+			vkcv::SampledImageDescriptorWrite(1, motionBuffer) };
 		motionBlurDescriptorWrites.samplerWrites = {
 			vkcv::SamplerDescriptorWrite(2, m_linearSampler) };
 		motionBlurDescriptorWrites.storageImageWrites = {
@@ -174,7 +279,7 @@ void App::run() {
 
 		m_core.prepareImageForStorage(cmdStream, m_renderTargets.motionBlurOutput);
 		m_core.prepareImageForSampling(cmdStream, m_renderTargets.colorBuffer);
-		m_core.prepareImageForSampling(cmdStream, m_renderTargets.motionBuffer);
+		m_core.prepareImageForSampling(cmdStream, motionBuffer);
 
 		m_core.recordComputeDispatchToCmdStream(
 			cmdStream,
@@ -184,9 +289,18 @@ void App::run() {
 			vkcv::PushConstants(0));
 
 		// gamma correction
-		vkcv::ImageHandle gammaCorrectionInput = m_renderTargets.motionBlurOutput;
-		if (drawMotionVectors) {
+		vkcv::ImageHandle gammaCorrectionInput;
+		if (debugView == eDebugView::None)
+			gammaCorrectionInput = m_renderTargets.motionBlurOutput;
+		else if (debugView == eDebugView::MotionVector)
 			gammaCorrectionInput = m_renderTargets.motionBuffer;
+		else if (debugView == eDebugView::MotionVectorMaxTile)
+			gammaCorrectionInput = m_renderTargets.motionMax;
+		else if (debugView == eDebugView::MotionVectorMaxTileNeighbourhood)
+			gammaCorrectionInput = m_renderTargets.motionMaxNeighbourhood;
+		else {
+			vkcv_log(vkcv::LogLevel::ERROR, "Unknown eDebugView enum value");
+			gammaCorrectionInput = m_renderTargets.motionBlurOutput;
 		}
 
 		vkcv::DescriptorWrites gammaCorrectionDescriptorWrites;
@@ -214,12 +328,27 @@ void App::run() {
 
 		gui.beginGUI();
 		ImGui::Begin("Settings");
-		ImGui::Checkbox("View motion vectors", &drawMotionVectors);
+
+		ImGui::Combo(
+			"Debug view",
+			reinterpret_cast<int*>(&debugView),
+			debugViewLabels,
+			static_cast<int>(eDebugView::OptionCount));
+
+		ImGui::Combo(
+			"Motion blur input",
+			reinterpret_cast<int*>(&motionBlurInput),
+			motionInputLabels,
+			static_cast<int>(eMotionBlurInput::OptionCount));
+
+		ImGui::InputFloat("Object movement speed", &objectVerticalSpeed);
+
 		ImGui::End();
 		gui.endGUI();
 
 		m_core.endFrame();
 
-		previousFrameViewProjection = viewProjection;
+		viewProjectionPrevious  = viewProjection;
+		mvpPrevious             = mvp;
 	}
 }
\ No newline at end of file
diff --git a/projects/indirect_dispatch/src/App.hpp b/projects/indirect_dispatch/src/App.hpp
index 3dc0b908..9bbdde94 100644
--- a/projects/indirect_dispatch/src/App.hpp
+++ b/projects/indirect_dispatch/src/App.hpp
@@ -28,6 +28,8 @@ private:
 
 	ComputePassHandles m_gammaCorrectionPass;
 	ComputePassHandles m_motionBlurDummyPass;
+	ComputePassHandles m_motionVectorMaxPass;
+	ComputePassHandles m_motionVectorMaxNeighbourhoodPass;
 
 	RenderTargets       m_renderTargets;
 	vkcv::SamplerHandle m_linearSampler;
diff --git a/projects/indirect_dispatch/src/AppSetup.cpp b/projects/indirect_dispatch/src/AppSetup.cpp
index d6176c93..96b182fd 100644
--- a/projects/indirect_dispatch/src/AppSetup.cpp
+++ b/projects/indirect_dispatch/src/AppSetup.cpp
@@ -269,5 +269,26 @@ RenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const
 		false,
 		true).getHandle();
 
+	const uint32_t motionTileSize  = 8;
+	// divide and ceil to int
+	const uint32_t motionMaxWidth  = (width  + (motionTileSize - 1)) / motionTileSize;
+	const uint32_t motionMaxheight = (height + (motionTileSize - 1)) / motionTileSize;
+
+	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 0253a35f..adb8c198 100644
--- a/projects/indirect_dispatch/src/AppSetup.hpp
+++ b/projects/indirect_dispatch/src/AppSetup.hpp
@@ -6,6 +6,8 @@ struct RenderTargets {
 	vkcv::ImageHandle colorBuffer;
 	vkcv::ImageHandle motionBlurOutput;
 	vkcv::ImageHandle motionBuffer;
+	vkcv::ImageHandle motionMax;
+	vkcv::ImageHandle motionMaxNeighbourhood;
 };
 
 struct GraphicPassHandles {
-- 
GitLab