From 745ad19cd5d23a0f6f8ad432df5dc8ffec922b0f Mon Sep 17 00:00:00 2001
From: Tobias Frisch <tfrisch@uni-koblenz.de>
Date: Fri, 11 Aug 2023 13:56:38 +0200
Subject: [PATCH] Implement host memory maps to images if possible

Signed-off-by: Tobias Frisch <tfrisch@uni-koblenz.de>
---
 include/vkcv/FeatureManager.hpp     |  10 +
 lib/SPIRV-Cross                     |   2 +-
 lib/Vulkan-Headers                  |   2 +-
 lib/Vulkan-Hpp                      |   2 +-
 lib/VulkanMemoryAllocator           |   2 +-
 lib/VulkanMemoryAllocator-Hpp       |   2 +-
 modules/gui/lib/imgui               |   2 +-
 modules/shader_compiler/lib/glslang |   2 +-
 projects/indirect_draw/src/main.cpp |  24 +--
 src/vkcv/BufferManager.cpp          |   4 +
 src/vkcv/BufferManager.hpp          |   2 +
 src/vkcv/FeatureManager.cpp         |   9 +
 src/vkcv/ImageManager.cpp           | 274 ++++++++++++++++++++--------
 src/vkcv/ImageManager.hpp           |   1 +
 14 files changed, 247 insertions(+), 91 deletions(-)

diff --git a/include/vkcv/FeatureManager.hpp b/include/vkcv/FeatureManager.hpp
index 741d49a5..d0ec3e75 100644
--- a/include/vkcv/FeatureManager.hpp
+++ b/include/vkcv/FeatureManager.hpp
@@ -373,6 +373,16 @@ namespace vkcv {
 		 */
 		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceMeshShaderFeaturesEXT &features,
 										bool required) const;
+		
+		/**
+		 * @brief Checks support of the @p vk::PhysicalDeviceHostImageCopyFeaturesEXT.
+		 *
+		 * @param[in] features The features
+		 * @param[in] required True, if the @p features are required, else false
+		 * @return @p True, if the @p features are supported, else @p false
+		 */
+		[[nodiscard]] bool checkSupport(const vk::PhysicalDeviceHostImageCopyFeaturesEXT &features,
+										bool required) const;
 
 		/**
 		 * @brief Searches for a base structure of a given structure type.
diff --git a/lib/SPIRV-Cross b/lib/SPIRV-Cross
index b8e742c9..b43c1a1e 160000
--- a/lib/SPIRV-Cross
+++ b/lib/SPIRV-Cross
@@ -1 +1 @@
-Subproject commit b8e742c91ba47eb3238c939ee11ec9ba2ba247bf
+Subproject commit b43c1a1e63ca7ac967c3b0e71ba29dbe08aa3dc0
diff --git a/lib/Vulkan-Headers b/lib/Vulkan-Headers
index 2565ffa3..9c37439a 160000
--- a/lib/Vulkan-Headers
+++ b/lib/Vulkan-Headers
@@ -1 +1 @@
-Subproject commit 2565ffa31ea67650f95f65347ed8f5917c651fac
+Subproject commit 9c37439a7952c204150863fc35569dd864dbd599
diff --git a/lib/Vulkan-Hpp b/lib/Vulkan-Hpp
index 069c3b87..ce1aacec 160000
--- a/lib/Vulkan-Hpp
+++ b/lib/Vulkan-Hpp
@@ -1 +1 @@
-Subproject commit 069c3b875e0683944216711bc88ce07444ecc257
+Subproject commit ce1aacec066836f5359c15673e8062133985ce9e
diff --git a/lib/VulkanMemoryAllocator b/lib/VulkanMemoryAllocator
index 2a28bc4b..6eb62e15 160000
--- a/lib/VulkanMemoryAllocator
+++ b/lib/VulkanMemoryAllocator
@@ -1 +1 @@
-Subproject commit 2a28bc4b39b9b80dad909036442f629f570d7ae1
+Subproject commit 6eb62e1515072827db992c2befd80b71b2d04329
diff --git a/lib/VulkanMemoryAllocator-Hpp b/lib/VulkanMemoryAllocator-Hpp
index eca325bc..4aa5600e 160000
--- a/lib/VulkanMemoryAllocator-Hpp
+++ b/lib/VulkanMemoryAllocator-Hpp
@@ -1 +1 @@
-Subproject commit eca325bc5d88eb1be1b70af3726a410d04a92512
+Subproject commit 4aa5600e01c533edf42620b91a79e199dae6d0a1
diff --git a/modules/gui/lib/imgui b/modules/gui/lib/imgui
index 6888e6cd..1109de38 160000
--- a/modules/gui/lib/imgui
+++ b/modules/gui/lib/imgui
@@ -1 +1 @@
-Subproject commit 6888e6cdffd43ab8d967f25cc64cfe82b1fce2fa
+Subproject commit 1109de38277fd2d14d4dca4c1cb8d4a2c4ff0f95
diff --git a/modules/shader_compiler/lib/glslang b/modules/shader_compiler/lib/glslang
index 44779f50..65397339 160000
--- a/modules/shader_compiler/lib/glslang
+++ b/modules/shader_compiler/lib/glslang
@@ -1 +1 @@
-Subproject commit 44779f508af3a9ae535153a70e4a735ded913857
+Subproject commit 65397339c5033cc612519a29f3896bbd3dcd2d08
diff --git a/projects/indirect_draw/src/main.cpp b/projects/indirect_draw/src/main.cpp
index 5f40732f..6aa0f362 100644
--- a/projects/indirect_draw/src/main.cpp
+++ b/projects/indirect_draw/src/main.cpp
@@ -305,6 +305,11 @@ int main(int argc, const char** argv) {
             }
     );
 
+    features.tryExtensionFeature<vk::PhysicalDeviceHostImageCopyFeaturesEXT>(
+        VK_EXT_HOST_IMAGE_COPY_EXTENSION_NAME, [](vk::PhysicalDeviceHostImageCopyFeaturesEXT& features) {
+				features.setHostImageCopy(true);
+			}
+    );
 
 	vkcv::Core core = vkcv::Core::create(
 		applicationName,
@@ -349,20 +354,15 @@ int main(int argc, const char** argv) {
 
 	vkcv::ShaderProgram sponzaProgram;
 	vkcv::shader::GLSLCompiler compiler;
-	compiler.compile(vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert"),
-					 [&sponzaProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-        sponzaProgram.addShader(shaderStage, path);
-	});
-	compiler.compile(vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag"),
-					 [&sponzaProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-        sponzaProgram.addShader(shaderStage, path);
-	});
+    compiler.compileProgram(sponzaProgram, {
+        { vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert") },
+        { vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag") },
+    }, nullptr);
 
     vkcv::ShaderProgram cullingProgram;
-    compiler.compile(vkcv::ShaderStage::COMPUTE, std::filesystem::path("resources/shaders/culling.comp"),
-                     [&cullingProgram](vkcv::ShaderStage shaderStage, const std::filesystem::path& path) {
-        cullingProgram.addShader(shaderStage, path);
-    });
+    compiler.compileProgram(cullingProgram, {
+        { vkcv::ShaderStage::COMPUTE, std::filesystem::path("resources/shaders/culling.comp") },
+    }, nullptr);
 
     // vertex layout for the pipeline. (assumed to be) used by all sponza meshes.
     const std::vector<vkcv::VertexAttachment> vertexAttachments = sponzaProgram.getVertexAttachments();
diff --git a/src/vkcv/BufferManager.cpp b/src/vkcv/BufferManager.cpp
index 6678270f..f1676870 100644
--- a/src/vkcv/BufferManager.cpp
+++ b/src/vkcv/BufferManager.cpp
@@ -113,6 +113,10 @@ namespace vkcv {
 		clear();
 	}
 
+	bool BufferManager::useResizableBar() const {
+		return m_resizableBar;
+	}
+
 	BufferHandle BufferManager::createBuffer(const TypeGuard &typeGuard, BufferType type,
 											 BufferMemoryType memoryType, size_t size,
 											 bool readable, size_t alignment) {
diff --git a/src/vkcv/BufferManager.hpp b/src/vkcv/BufferManager.hpp
index f26dbfe4..906503d7 100644
--- a/src/vkcv/BufferManager.hpp
+++ b/src/vkcv/BufferManager.hpp
@@ -69,6 +69,8 @@ namespace vkcv {
 
 		~BufferManager() noexcept override;
 
+		bool useResizableBar() const;
+
 		/**
 		 * @brief Creates and allocates a new buffer and returns its
 		 * unique buffer handle.
diff --git a/src/vkcv/FeatureManager.cpp b/src/vkcv/FeatureManager.cpp
index 10980c27..fe7537be 100644
--- a/src/vkcv/FeatureManager.cpp
+++ b/src/vkcv/FeatureManager.cpp
@@ -527,6 +527,15 @@ namespace vkcv {
 		return true;
 	}
 
+	bool FeatureManager::checkSupport(const vk::PhysicalDeviceHostImageCopyFeaturesEXT &features,
+									  bool required) const {
+		vkcv_check_init_features2(vk::PhysicalDeviceHostImageCopyFeaturesEXT);
+
+		vkcv_check_feature(hostImageCopy);
+
+		return true;
+	}
+
 	vk::BaseOutStructure* FeatureManager::findFeatureStructure(vk::StructureType type) const {
 		for (auto &base : m_featuresExtensions) {
 			if (base->sType == type) {
diff --git a/src/vkcv/ImageManager.cpp b/src/vkcv/ImageManager.cpp
index 657570b6..619f1658 100644
--- a/src/vkcv/ImageManager.cpp
+++ b/src/vkcv/ImageManager.cpp
@@ -271,12 +271,18 @@ namespace vkcv {
 				{},
 				vk::ImageLayout::eUndefined
 		);
+
+		vma::AllocationCreateFlags allocationCreateFlags;
+		if (getBufferManager().useResizableBar()) {
+			allocationCreateFlags = vma::AllocationCreateFlagBits::eHostAccessAllowTransferInstead
+									| vma::AllocationCreateFlagBits::eHostAccessSequentialWrite;
+		}
 		
-		auto imageAllocation = allocator.createImage(
+		const auto imageAllocation = allocator.createImage(
 				imageCreateInfo,
 				vma::AllocationCreateInfo(
-						vma::AllocationCreateFlags(),
-						vma::MemoryUsage::eGpuOnly,
+						allocationCreateFlags,
+						vma::MemoryUsage::eAutoPreferDevice,
 						vk::MemoryPropertyFlagBits::eDeviceLocal,
 						vk::MemoryPropertyFlagBits::eDeviceLocal,
 						0,
@@ -294,6 +300,25 @@ namespace vkcv {
 		} else {
 			aspectFlags = vk::ImageAspectFlagBits::eColor;
 		}
+
+		const vk::MemoryPropertyFlags finalMemoryFlags = allocator.getAllocationMemoryProperties(
+				allocation
+		);
+
+		bool accessible = false;
+		if (vk::MemoryPropertyFlagBits::eHostVisible & finalMemoryFlags) {
+			const auto& featureManager = getCore().getContext().getFeatureManager();
+
+			accessible = (
+				featureManager.isExtensionActive(VK_EXT_HOST_IMAGE_COPY_EXTENSION_NAME) &&
+				featureManager.checkFeatures<vk::PhysicalDeviceHostImageCopyFeaturesEXT>(
+					vk::StructureType::ePhysicalDeviceHostImageCopyFeaturesEXT,
+					[](const vk::PhysicalDeviceHostImageCopyFeaturesEXT& features) {
+						return features.hostImageCopy;
+					}
+				)
+			);
+		}
 		
 		const vk::Device &device = getCore().getContext().getDevice();
 		
@@ -365,7 +390,8 @@ namespace vkcv {
 			config.getDepth(),
 			format,
 			layers,
-			config.isSupportingStorage()
+			config.isSupportingStorage(),
+			accessible
 		});
 	}
 	
@@ -495,27 +521,52 @@ namespace vkcv {
 		const auto transitionBarriers = createImageLayoutTransitionBarriers(image, 0, 0, newLayout, false);
 		
 		auto &core = getCore();
-		auto stream = core.createCommandStream(QueueType::Graphics);
-		
-		core.recordCommandsToStream(
-				stream,
-				[transitionBarriers](const vk::CommandBuffer &commandBuffer) {
-					// TODO: precise PipelineStageFlagBits, will require a lot of context
-					for (const auto& barrier : transitionBarriers) {
-						commandBuffer.pipelineBarrier(
-								vk::PipelineStageFlagBits::eTopOfPipe,
-								vk::PipelineStageFlagBits::eBottomOfPipe,
-								{},
-								nullptr,
-								nullptr,
-								barrier
-						);
-					}
-				},
-				nullptr
-		);
-		
-		core.submitCommandStream(stream, false);
+
+		if (image.m_accessible) {
+			const auto &dynamicDispatch = getCore().getContext().getDispatchLoaderDynamic();
+
+			for (const auto& barrier : transitionBarriers) {
+				const vk::HostImageLayoutTransitionInfoEXT transitionInfo(
+						image.m_handle,
+						barrier.oldLayout,
+						barrier.newLayout,
+						barrier.subresourceRange
+				);
+
+				const auto result = core.getContext().getDevice().transitionImageLayoutEXT(
+						1, 
+						&transitionInfo,
+						dynamicDispatch
+				);
+
+				if (vk::Result::eSuccess != result) {
+					// TODO: warning?
+					break;
+				}
+			}
+		} else {
+			auto stream = core.createCommandStream(QueueType::Graphics);
+		
+			core.recordCommandsToStream(
+					stream,
+					[transitionBarriers](const vk::CommandBuffer &commandBuffer) {
+						// TODO: precise PipelineStageFlagBits, will require a lot of context
+						for (const auto& barrier : transitionBarriers) {
+							commandBuffer.pipelineBarrier(
+									vk::PipelineStageFlagBits::eTopOfPipe,
+									vk::PipelineStageFlagBits::eBottomOfPipe,
+									{},
+									nullptr,
+									nullptr,
+									barrier
+							);
+						}
+					},
+					nullptr
+			);
+			
+			core.submitCommandStream(stream, false);
+		}
 		
 		for (const auto& barrier : transitionBarriers) {
 			for (uint32_t i = 0; i < barrier.subresourceRange.layerCount; i++) {
@@ -601,51 +652,26 @@ namespace vkcv {
 				return 4;
 		}
 	}
-	
-	void ImageManager::fillImage(const ImageHandle &handle,
-								 const void* data,
-								 size_t size,
-								 uint32_t firstLayer,
-								 uint32_t layerCount) {
-		if (handle.isSwapchainImage()) {
-			vkcv_log(LogLevel::ERROR, "Swapchain image cannot be filled");
-			return;
-		}
-		
-		auto &image = (*this) [handle];
-		
-		const auto imageLayerCount = static_cast<uint32_t>(image.m_layers.size());
-		const uint32_t baseArrayLayer = std::min<uint32_t>(firstLayer, imageLayerCount);
-		
-		if (baseArrayLayer >= image.m_layers.size()) {
-			return;
-		}
-		
-		uint32_t arrayLayerCount;
-		
-		if (layerCount > 0) {
-			arrayLayerCount = std::min<uint32_t>(layerCount, imageLayerCount - baseArrayLayer);
-		} else {
-			arrayLayerCount = imageLayerCount - baseArrayLayer;
-		}
-		
-		switchImageLayoutImmediate(handle, vk::ImageLayout::eTransferDstOptimal);
-		
-		const size_t image_size = (
-				image.m_width * image.m_height * image.m_depth * getBytesPerPixel(image.m_format)
-		);
-		
-		const size_t max_size = std::min(size, image_size);
-		
-		BufferHandle bufferHandle = getBufferManager().createBuffer(
-				TypeGuard(1), BufferType::STAGING, BufferMemoryType::DEVICE_LOCAL, max_size, false
+
+	static void fillImageViaCommandBuffer(Core& core, 
+										  ImageManager& imageManager, 
+										  BufferManager& bufferManager, 
+										  const ImageEntry& image,
+										  const ImageHandle& handle,
+										  const void* data, 
+										  size_t size,
+										  uint32_t baseArrayLayer,
+										  uint32_t arrayLayerCount) {
+		imageManager.switchImageLayoutImmediate(handle, vk::ImageLayout::eTransferDstOptimal);
+		
+		BufferHandle bufferHandle = bufferManager.createBuffer(
+				TypeGuard(1), BufferType::STAGING, BufferMemoryType::DEVICE_LOCAL, size, false
 		);
 		
-		getBufferManager().fillBuffer(bufferHandle, data, max_size, 0);
+		bufferManager.fillBuffer(bufferHandle, data, size, 0);
 		
-		vk::Buffer stagingBuffer = getBufferManager().getBuffer(bufferHandle);
+		vk::Buffer stagingBuffer = bufferManager.getBuffer(bufferHandle);
 		
-		auto &core = getCore();
 		auto stream = core.createCommandStream(QueueType::Transfer);
 		
 		core.recordCommandsToStream(
@@ -659,8 +685,8 @@ namespace vkcv {
 					} else {
 						aspectFlags = vk::ImageAspectFlagBits::eColor;
 					}
-					
-					const vk::BufferImageCopy region(
+
+					const vk::BufferImageCopy2 region2(
 							0,
 							0,
 							0,
@@ -669,21 +695,124 @@ namespace vkcv {
 							vk::Extent3D(image.m_width, image.m_height, image.m_depth)
 					);
 					
-					commandBuffer.copyBufferToImage(
+					const vk::CopyBufferToImageInfo2 copyInfo(
 							stagingBuffer,
 							image.m_handle,
 							vk::ImageLayout::eTransferDstOptimal,
 							1,
-							&region
+							&region2
 					);
+
+					commandBuffer.copyBufferToImage2(&copyInfo);
 				},
-				[&]() {
-					switchImageLayoutImmediate(handle, vk::ImageLayout::eShaderReadOnlyOptimal);
+				[&imageManager, &handle]() {
+					imageManager.switchImageLayoutImmediate(handle, vk::ImageLayout::eShaderReadOnlyOptimal);
 				}
 		);
 		
 		core.submitCommandStream(stream, false);
 	}
+
+	static void fillImageFromHost(Core& core, 
+								  ImageManager& imageManager, 
+								  const ImageEntry& image,
+								  const ImageHandle& handle,
+								  const void* data, 
+								  uint32_t baseArrayLayer, 
+								  uint32_t arrayLayerCount) {
+		imageManager.switchImageLayoutImmediate(handle, vk::ImageLayout::eTransferDstOptimal);
+
+		const auto &dynamicDispatch = core.getContext().getDispatchLoaderDynamic();
+		
+		vk::ImageAspectFlags aspectFlags;
+
+		if (isDepthImageFormat(image.m_format)) {
+			aspectFlags = vk::ImageAspectFlagBits::eDepth;
+		} else {
+			aspectFlags = vk::ImageAspectFlagBits::eColor;
+		}
+
+		const vk::MemoryToImageCopyEXT region(
+				data,
+				0,
+				0,
+				vk::ImageSubresourceLayers(aspectFlags, 0, baseArrayLayer, arrayLayerCount),
+				vk::Offset3D(0, 0, 0),
+				vk::Extent3D(image.m_width, image.m_height, image.m_depth)
+		);
+
+		const vk::CopyMemoryToImageInfoEXT copyInfo(
+				vk::HostImageCopyFlagsEXT(),
+				image.m_handle,
+				vk::ImageLayout::eTransferDstOptimal,
+				1,
+				&region
+		);
+
+		core.getContext().getDevice().copyMemoryToImageEXT(copyInfo, dynamicDispatch);
+
+		imageManager.switchImageLayoutImmediate(handle, vk::ImageLayout::eShaderReadOnlyOptimal);
+	}
+	
+	void ImageManager::fillImage(const ImageHandle &handle,
+								 const void* data,
+								 size_t size,
+								 uint32_t firstLayer,
+								 uint32_t layerCount) {
+		if (handle.isSwapchainImage()) {
+			vkcv_log(LogLevel::ERROR, "Swapchain image cannot be filled");
+			return;
+		}
+		
+		auto &image = (*this) [handle];
+		
+		const auto imageLayerCount = static_cast<uint32_t>(image.m_layers.size());
+		const uint32_t baseArrayLayer = std::min<uint32_t>(firstLayer, imageLayerCount);
+		
+		if (baseArrayLayer >= image.m_layers.size()) {
+			return;
+		}
+		
+		uint32_t arrayLayerCount;
+		
+		if (layerCount > 0) {
+			arrayLayerCount = std::min<uint32_t>(layerCount, imageLayerCount - baseArrayLayer);
+		} else {
+			arrayLayerCount = imageLayerCount - baseArrayLayer;
+		}
+		
+		const size_t image_size = (
+				image.m_width * image.m_height * image.m_depth * getBytesPerPixel(image.m_format)
+		);
+		
+		const size_t max_size = std::min(size, image_size);
+
+		auto& core = getCore();
+
+		if (image.m_accessible) {
+			fillImageFromHost(
+				core,
+				(*this), 
+				image, 
+				handle, 
+				data, 
+				baseArrayLayer, 
+				arrayLayerCount
+			);
+		} else {
+			fillImageViaCommandBuffer(
+				core, 
+				(*this), 
+				getBufferManager(), 
+				image, 
+				handle, 
+				data, 
+				max_size, 
+				baseArrayLayer, 
+				arrayLayerCount
+			);
+		}
+	}
 	
 	void ImageManager::recordImageMipChainGenerationToCmdStream(
 			const vkcv::CommandStreamHandle &cmdStream, const ImageHandle &handle) {
@@ -794,6 +923,7 @@ namespace vkcv {
 					1,
 					format,
 					{ layer },
+					false,
 					false
 			});
 		}
diff --git a/src/vkcv/ImageManager.hpp b/src/vkcv/ImageManager.hpp
index b447f6ed..9e4681dc 100644
--- a/src/vkcv/ImageManager.hpp
+++ b/src/vkcv/ImageManager.hpp
@@ -43,6 +43,7 @@ namespace vkcv {
 		vk::Format m_format;
 		Vector<ImageLayer> m_layers;
 		bool m_storage;
+		bool m_accessible;
 	};
 
 	/**
-- 
GitLab