From 220d58d836e826693c3d1eb14e7ebfe176bf488c Mon Sep 17 00:00:00 2001
From: Alexander Gauggel <agauggel@uni-koblenz.de>
Date: Tue, 3 Aug 2021 15:48:59 +0200
Subject: [PATCH] [#106] Initial project

---
 include/vkcv/DrawcallRecording.hpp            |  15 +-
 projects/CMakeLists.txt                       |   1 +
 projects/indirect_dispatch/CMakeLists.txt     |  37 +++++
 .../resources/models/Sphere.bin               |   3 +
 .../resources/models/Sphere.gltf              |   3 +
 .../resources/shaders/shader.frag             |  10 ++
 .../resources/shaders/shader.vert             |  16 +++
 projects/indirect_dispatch/src/App.cpp        |  99 +++++++++++++
 projects/indirect_dispatch/src/App.hpp        |  26 ++++
 projects/indirect_dispatch/src/AppConfig.hpp  |   8 ++
 projects/indirect_dispatch/src/AppSetup.cpp   | 130 ++++++++++++++++++
 projects/indirect_dispatch/src/AppSetup.hpp   |  22 +++
 projects/indirect_dispatch/src/main.cpp       |  12 ++
 13 files changed, 380 insertions(+), 2 deletions(-)
 create mode 100644 projects/indirect_dispatch/CMakeLists.txt
 create mode 100644 projects/indirect_dispatch/resources/models/Sphere.bin
 create mode 100644 projects/indirect_dispatch/resources/models/Sphere.gltf
 create mode 100644 projects/indirect_dispatch/resources/shaders/shader.frag
 create mode 100644 projects/indirect_dispatch/resources/shaders/shader.vert
 create mode 100644 projects/indirect_dispatch/src/App.cpp
 create mode 100644 projects/indirect_dispatch/src/App.hpp
 create mode 100644 projects/indirect_dispatch/src/AppConfig.hpp
 create mode 100644 projects/indirect_dispatch/src/AppSetup.cpp
 create mode 100644 projects/indirect_dispatch/src/AppSetup.hpp
 create mode 100644 projects/indirect_dispatch/src/main.cpp

diff --git a/include/vkcv/DrawcallRecording.hpp b/include/vkcv/DrawcallRecording.hpp
index 260fbbc6..37cf02d9 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/projects/CMakeLists.txt b/projects/CMakeLists.txt
index c4fde454..80107184 100644
--- a/projects/CMakeLists.txt
+++ b/projects/CMakeLists.txt
@@ -6,3 +6,4 @@ add_subdirectory(first_scene)
 add_subdirectory(particle_simulation)
 add_subdirectory(voxelization)
 add_subdirectory(mesh_shader)
+add_subdirectory(indirect_dispatch)
diff --git a/projects/indirect_dispatch/CMakeLists.txt b/projects/indirect_dispatch/CMakeLists.txt
new file mode 100644
index 00000000..b54a3186
--- /dev/null
+++ b/projects/indirect_dispatch/CMakeLists.txt
@@ -0,0 +1,37 @@
+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/AppSetup.hpp
+    src/AppSetup.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/Sphere.bin b/projects/indirect_dispatch/resources/models/Sphere.bin
new file mode 100644
index 00000000..786ec34b
--- /dev/null
+++ b/projects/indirect_dispatch/resources/models/Sphere.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a1c5fefa4a07401791a2af9318665bca3b328cf11736b307e129ae8ba4c8931d
+size 17328
diff --git a/projects/indirect_dispatch/resources/models/Sphere.gltf b/projects/indirect_dispatch/resources/models/Sphere.gltf
new file mode 100644
index 00000000..0b380598
--- /dev/null
+++ b/projects/indirect_dispatch/resources/models/Sphere.gltf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:19b29ddffaeecfa15f19dc943c0ecc9cb3c22d47adb35c19e8727edd7e29c565
+size 1847
diff --git a/projects/indirect_dispatch/resources/shaders/shader.frag b/projects/indirect_dispatch/resources/shaders/shader.frag
new file mode 100644
index 00000000..8d032cab
--- /dev/null
+++ b/projects/indirect_dispatch/resources/shaders/shader.frag
@@ -0,0 +1,10 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 passNormal;
+
+layout(location = 0) out vec3 outColor;
+
+void main()	{
+	outColor = passNormal * 0.5 + 0.5;
+}
\ No newline at end of file
diff --git a/projects/indirect_dispatch/resources/shaders/shader.vert b/projects/indirect_dispatch/resources/shaders/shader.vert
new file mode 100644
index 00000000..5e1f72dc
--- /dev/null
+++ b/projects/indirect_dispatch/resources/shaders/shader.vert
@@ -0,0 +1,16 @@
+#version 450
+#extension GL_ARB_separate_shader_objects : enable
+
+layout(location = 0) in vec3 inPosition;
+layout(location = 1) in vec3 inNormal;
+
+layout(location = 0) out vec3 passNormal;
+
+layout( push_constant ) uniform constants{
+    mat4 mvp;
+};
+
+void main()	{
+	gl_Position = mvp * vec4(inPosition, 1.0);
+	passNormal  = inNormal;
+}
\ 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 00000000..b95c0f36
--- /dev/null
+++ b/projects/indirect_dispatch/src/App.cpp
@@ -0,0 +1,99 @@
+#include "App.hpp"
+#include "AppConfig.hpp"
+#include <chrono>
+
+App::App() : 
+	m_applicationName("Indirect Dispatch"),
+	m_windowWidth(AppConfig::defaultWindowWidth),
+	m_windowHeight(AppConfig::defaultWindowHeight),
+	m_window(vkcv::Window::create(
+		m_applicationName,
+		m_windowWidth,
+		m_windowHeight,
+		true)),
+	m_core(vkcv::Core::create(
+		m_window,
+		m_applicationName,
+		VK_MAKE_VERSION(0, 0, 1),
+		{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
+		{},
+		{ "VK_KHR_swapchain" })),
+	m_cameraManager(m_window){
+
+	m_isInitialized = false;
+}
+
+bool App::initialize() {
+
+	if (!loadMeshGraphicPass(m_core, &m_meshPassHandles))
+		return false;
+
+	if (!loadSphereMesh(m_core, &m_sphereMesh))
+		return false;
+
+	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, 0, -3));
+
+	m_isInitialized = true;
+}
+
+void App::run() {
+
+	if (!m_isInitialized) {
+		vkcv_log(vkcv::LogLevel::WARNING, "Application is not initialized, app should be initialized explicitly to check for errors");
+		if (!initialize()) {
+			vkcv_log(vkcv::LogLevel::ERROR, "Emergency initialization failed, exiting");
+			return;
+		}
+	}
+
+	auto                        frameStartTime = std::chrono::system_clock::now();
+	const vkcv::ImageHandle     swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
+	const vkcv::DrawcallInfo    sphereDrawcall(m_sphereMesh.mesh, {}, 1);
+
+	while (m_window.isWindowOpen()) {
+		vkcv::Window::pollEvents();
+
+		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);
+		}
+
+		auto frameEndTime   = std::chrono::system_clock::now();
+		auto deltatime      = std::chrono::duration_cast<std::chrono::microseconds>(frameEndTime - frameStartTime);
+		frameStartTime      = frameEndTime;
+
+		m_cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		const glm::mat4 mvp = m_cameraManager.getActiveCamera().getMVP();
+
+		vkcv::PushConstants pushConstants(sizeof(glm::mat4));
+		pushConstants.appendDrawcall(mvp);
+
+		const std::vector<vkcv::ImageHandle>    renderTargets   = { swapchainInput, m_renderTargets.depthBuffer };
+		const vkcv::CommandStreamHandle         cmdStream       = m_core.createCommandStream(vkcv::QueueType::Graphics);
+
+		m_core.recordDrawcallsToCmdStream(
+			cmdStream,
+			m_meshPassHandles.renderPass,
+			m_meshPassHandles.pipeline,
+			pushConstants,
+			{ sphereDrawcall },
+			renderTargets);
+
+		m_core.prepareSwapchainImageForPresent(cmdStream);
+		m_core.submitCommandStream(cmdStream);
+		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 00000000..d4b5387f
--- /dev/null
+++ b/projects/indirect_dispatch/src/App.hpp
@@ -0,0 +1,26 @@
+#pragma once
+#include <vkcv/Core.hpp>
+#include <vkcv/camera/CameraManager.hpp>
+#include "AppSetup.hpp"
+
+class App {
+public:
+	App();
+	bool initialize();
+	void run();
+private:
+	const char* m_applicationName;
+	bool        m_isInitialized;
+
+	int m_windowWidth;
+	int m_windowHeight;
+
+	vkcv::Window                m_window;
+	vkcv::Core                  m_core;
+	vkcv::camera::CameraManager m_cameraManager;
+
+	MeshResources       m_sphereMesh;
+	GraphicPassHandles  m_meshPassHandles;
+
+	RenderTargets m_renderTargets;
+};
\ 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 00000000..71993ebd
--- /dev/null
+++ b/projects/indirect_dispatch/src/AppConfig.hpp
@@ -0,0 +1,8 @@
+#pragma once
+#include "vulkan/vulkan.hpp"
+
+namespace AppConfig{
+	const int           defaultWindowWidth  = 1280;
+	const int           defaultWindowHeight = 720;
+	const vk::Format    depthBufferFormat   = vk::Format::eD32Sfloat;
+}
\ 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 00000000..ab14c181
--- /dev/null
+++ b/projects/indirect_dispatch/src/AppSetup.cpp
@@ -0,0 +1,130 @@
+#include "AppSetup.hpp"
+#include "AppConfig.hpp"
+#include <vkcv/asset/asset_loader.hpp>
+#include <vkcv/shader/GLSLCompiler.hpp>
+
+bool loadSphereMesh(vkcv::Core& core, MeshResources* outMesh) {
+    assert(outMesh);
+
+    vkcv::asset::Scene sphereScene;
+    const int meshLoadResult = vkcv::asset::loadScene("resources/models/Sphere.gltf", sphereScene);
+
+    if (meshLoadResult != 1) {
+        vkcv_log(vkcv::LogLevel::ERROR, "Mesh loading failed");
+        return false;
+    }
+
+    if (sphereScene.meshes.size() < 1) {
+        vkcv_log(vkcv::LogLevel::ERROR, "Sphere mesh scene does not contain any vertex groups");
+        return false;
+    }
+    assert(!sphereScene.vertexGroups.empty());
+
+    auto& sphereVertexData = sphereScene.vertexGroups[0].vertexBuffer;
+    auto& sphereIndexData = sphereScene.vertexGroups[0].indexBuffer;
+
+    vkcv::Buffer vertexBuffer = core.createBuffer<uint8_t>(
+        vkcv::BufferType::VERTEX,
+        sphereVertexData.data.size(),
+        vkcv::BufferMemoryType::DEVICE_LOCAL);
+
+    vkcv::Buffer indexBuffer = core.createBuffer<uint8_t>(
+        vkcv::BufferType::INDEX,
+        sphereIndexData.data.size(),
+        vkcv::BufferMemoryType::DEVICE_LOCAL);
+
+    vertexBuffer.fill(sphereVertexData.data);
+    indexBuffer.fill(sphereIndexData.data);
+
+    outMesh->vertexBuffer = vertexBuffer.getHandle();
+    outMesh->indexBuffer = indexBuffer.getHandle();
+
+    auto& attributes = sphereVertexData.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()) };
+
+    outMesh->mesh = vkcv::Mesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), sphereScene.vertexGroups[0].numIndices);
+
+    return true;
+}
+
+bool loadMeshGraphicPass(vkcv::Core& core, GraphicPassHandles* outPassHandles) {
+    assert(outPassHandles);
+
+    const vkcv::AttachmentDescription present_color_attachment(
+        vkcv::AttachmentOperation::STORE,
+        vkcv::AttachmentOperation::CLEAR,
+        core.getSwapchain().getFormat());
+
+    const vkcv::AttachmentDescription depth_attachment(
+        vkcv::AttachmentOperation::STORE,
+        vkcv::AttachmentOperation::CLEAR,
+        AppConfig::depthBufferFormat);
+
+    vkcv::PassConfig meshPassDefinition({ present_color_attachment, depth_attachment });
+    outPassHandles->renderPass = core.createPass(meshPassDefinition);
+
+    if (!outPassHandles->renderPass) {
+        vkcv_log(vkcv::LogLevel::ERROR, "Error: Could not create renderpass");
+        return false;
+    }
+
+    vkcv::ShaderProgram meshProgram;
+    vkcv::shader::GLSLCompiler compiler;
+
+    compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"),
+        [&meshProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        meshProgram.addShader(shaderStage, path);
+    });
+
+    compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"),
+        [&meshProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
+        meshProgram.addShader(shaderStage, path);
+    });
+
+    const std::vector<vkcv::VertexAttachment> vertexAttachments = meshProgram.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 meshVertexLayout(bindings);
+
+    const vkcv::PipelineConfig meshPipelineConfig{
+        meshProgram,
+        UINT32_MAX,
+        UINT32_MAX,
+        outPassHandles->renderPass,
+        { meshVertexLayout },
+        {},
+        true };
+    outPassHandles->pipeline = core.createGraphicsPipeline(meshPipelineConfig);
+
+    if (!outPassHandles->pipeline) {
+        vkcv_log(vkcv::LogLevel::ERROR, "Error: Could not create graphics pipeline");
+        return false;
+    }
+
+    return true;
+}
+
+RenderTargets createRenderTargets(vkcv::Core& core, const uint32_t width, const uint32_t height) {
+
+    RenderTargets targets;
+
+    targets.depthBuffer = core.createImage(
+        AppConfig::depthBufferFormat,
+        width,
+        height,
+        1,
+        false).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 00000000..1436d134
--- /dev/null
+++ b/projects/indirect_dispatch/src/AppSetup.hpp
@@ -0,0 +1,22 @@
+#pragma once
+#include <vkcv/Core.hpp>
+
+struct RenderTargets {
+    vkcv::ImageHandle depthBuffer;
+};
+
+struct GraphicPassHandles {
+    vkcv::PipelineHandle    pipeline;
+    vkcv::PassHandle        renderPass;
+};
+
+struct MeshResources {
+    vkcv::Mesh          mesh;
+    vkcv::BufferHandle  vertexBuffer;
+    vkcv::BufferHandle  indexBuffer;
+};
+
+bool loadSphereMesh(vkcv::Core& core, MeshResources* outMesh);
+bool loadMeshGraphicPass(vkcv::Core& core, GraphicPassHandles* outPassHandles);
+
+RenderTargets 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/main.cpp b/projects/indirect_dispatch/src/main.cpp
new file mode 100644
index 00000000..6e3d9e63
--- /dev/null
+++ b/projects/indirect_dispatch/src/main.cpp
@@ -0,0 +1,12 @@
+#include "App.hpp"
+
+int main(int argc, const char** argv) {
+
+	App app;
+	if (!app.initialize()) {
+		std::cerr << "Application initialization failed, exiting" << std::endl;
+	}
+	app.run();
+
+	return 0;
+}
-- 
GitLab