diff --git a/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.cpp b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.cpp index 4158d3c3..7913574c 100644 --- a/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.cpp +++ b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.cpp @@ -33,6 +33,8 @@ namespace SHADE }; cmdBufferHdl->GetVkCommandBuffer().copyBuffer(stagingBuffer, vkBuffer, 1, ©Region); } + + // TODO: Need to destroy staging buffer. Obviously not here but after the command has finished executing. } vk::Buffer SHVkBuffer::GetVkBuffer(void) const noexcept diff --git a/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.h b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.h index 2ad3e4e9..9df1a1d0 100644 --- a/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.h +++ b/SHADE_Engine/src/Graphics/Buffers/SHVkBuffer.h @@ -48,7 +48,6 @@ namespace SHADE vk::BufferUsageFlags bufferUsageFlags; //! Reference to the allocator - //VmaAllocator const& vmaAllocator; std::reference_wrapper vmaAllocator; /*-----------------------------------------------------------------------*/ diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp index 6a307230..a9753416 100644 --- a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.cpp @@ -406,6 +406,12 @@ namespace SHADE } + + //void SHVkCommandBuffer::PipelineBarrier(vk::PipelineStageFlags ) const noexcept + //{ + // //vkCommandBuffer.pipelineBarrier() + //} + /***************************************************************************/ /*! diff --git a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h index 08fc45f7..4f89f813 100644 --- a/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h +++ b/SHADE_Engine/src/Graphics/Commands/SHVkCommandBuffer.h @@ -114,6 +114,9 @@ namespace SHADE void DrawArrays (uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance) const noexcept; void DrawIndexed (uint32_t indexCount, uint32_t firstIndex, uint32_t vertexOffset) const noexcept; + // memory barriers + //void PipelineBarrier (void) const noexcept; + // Push Constant variable setting template void SetPushConstantVariable(std::string variableName, T const& data) noexcept diff --git a/SHADE_Engine/src/Graphics/Images/SHVkImage.cpp b/SHADE_Engine/src/Graphics/Images/SHVkImage.cpp index 003e36ec..3cd311d3 100644 --- a/SHADE_Engine/src/Graphics/Images/SHVkImage.cpp +++ b/SHADE_Engine/src/Graphics/Images/SHVkImage.cpp @@ -5,16 +5,88 @@ #include "Tools/SHLogger.h" #include "SHVkImageView.h" #include "Graphics/Instance/SHVkInstance.h" +#include "Graphics/Buffers/SHVkBuffer.h" namespace SHADE { + /***************************************************************************/ + /*! + + \brief + If an image is a GPU only resource, we need to prep a staging buffer + to use for transferring data to the GPU. #NoteToSelf: I don't really + like this because its duplicate code. Should try to find a way to utilize + the logical device for this. + + \param data + Data to transfer. + + \param srcSize + Size in bytes of the data. + + */ + /***************************************************************************/ + void SHVkImage::PrepStagingBuffer(void* data, uint32_t srcSize) noexcept + { + // For creation of buffer + vk::BufferCreateInfo bufferInfo{}; + + // size stored same as GPU buffer + bufferInfo.size = srcSize; + + // We just want to set the transfer bit + bufferInfo.usage = vk::BufferUsageFlagBits::eTransferSrc; + + // sharing mode exclusive + bufferInfo.sharingMode = vk::SharingMode::eExclusive; + + // Set to auto detect bits + VmaAllocationCreateInfo allocCreateInfo{}; + allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; + + // We want to just write all at once. Using random access bit could make this slow + allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + + // parameters of a vmaAllocation retrieved via vmaGetAllocationInfo + VmaAllocationInfo allocInfo; + + // results of allocation + VmaAllocation stagingAlloc; + + // To get around VMA's usage for C version of vulkan, create a temp first..., + VkBuffer tempBuffer{}; + + // Create the buffer... + vmaCreateBuffer(*vmaAllocator, + &bufferInfo.operator VkBufferCreateInfo & (), // TODO: Verify if this works (can use renderdoc to check buffer variables?) + &allocCreateInfo, + &tempBuffer, &stagingAlloc, &allocInfo); + + // then assign it to the hpp version + stagingBuffer = tempBuffer; + + // Just map, copy then unmap + void* stagingBufferMappedPtr = nullptr; + vmaMapMemory(*vmaAllocator, stagingAlloc, &stagingBufferMappedPtr); + + if (stagingBufferMappedPtr) + std::memcpy(static_cast(stagingBufferMappedPtr), static_cast(data), srcSize); + + const VkDeviceSize offsets = 0; + const VkDeviceSize sizes = srcSize; + vmaFlushAllocations(*vmaAllocator, 1, &stagingAlloc, &offsets, &sizes); + + vmaUnmapMemory(*vmaAllocator, stagingAlloc); + } + SHVkImage::SHVkImage( - VmaAllocator const& vmaAllocator, - SHImageCreateParams const& imageDetails, - unsigned char* data, - uint32_t dataSize, - VmaMemoryUsage memUsage, - VmaAllocationCreateFlags allocFlags + VmaAllocator const* allocator, + SHImageCreateParams const& imageDetails, + unsigned char* data, + uint32_t dataSize, + std::span inMipOffsets, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags ) noexcept : imageType { imageDetails.imageType } , width{ imageDetails.width } @@ -25,6 +97,11 @@ namespace SHADE , imageFormat{ imageDetails.imageFormat } , usageFlags{} , createFlags{} + , vmaAllocator{allocator} + , mipOffsets { inMipOffsets } + , boundToCoherent{false} + , randomAccessOptimized {false} + , mappedPtr{nullptr} { for (auto& bit : imageDetails.usageBits) usageFlags |= bit; @@ -66,7 +143,7 @@ namespace SHADE VmaAllocationInfo allocInfo{}; VkImage tempImage; - auto result = vmaCreateImage(vmaAllocator, &imageCreateInfo.operator VkImageCreateInfo&(), &allocCreateInfo, &tempImage, &alloc, &allocInfo); + auto result = vmaCreateImage(*vmaAllocator, &imageCreateInfo.operator VkImageCreateInfo&(), &allocCreateInfo, &tempImage, &alloc, &allocInfo); if (result != VK_SUCCESS) SHVulkanDebugUtil::ReportVkError(vk::Result(result), "Failed to create vulkan image. "); @@ -75,55 +152,43 @@ namespace SHADE vkImage = tempImage; - //if (allocFlags & ) + // At this point the image and device memory have been created. + + if (allocFlags & VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT) + randomAccessOptimized = true; + + // TODO: This constructor can only create a GPU only resource for now. Due to time constraint, I was trying to create a ctor + // fast to finish up the ImGUI backend. In the future, there definitely needs to be more versatility to the constructor. + + // Get the memory property flags + VkMemoryPropertyFlags memPropFlags; + vmaGetAllocationMemoryProperties(*vmaAllocator, alloc, &memPropFlags); + + // mainly host visible. Can be cached (need to flush/invalidate), uncached (always coherent) and coherent (virtual). + //if (memPropFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) + //{ + // // If memory is marked to be coherent between CPU and GPU (no need flush/invalidate) (TODO: Verify if VMA_ALLOCATION_CREATE_MAPPED_BIT is used when VMA_MEMORY_USAGE_AUTO is set) + // // TODO: also verify that coherent bit = pointer is already mapped + // if (memPropFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) + // { + // boundToCoherent = true; + // mappedPtr = allocInfo.pMappedData; + // } + // else + // mappedPtr = nullptr; + + // if (data) + // MapWriteUnmap(data, srcSize, 0, 0); + //} + //else + //{ + // We can prep first so that we can do transfers later via 1 cmd buffer recording + PrepStagingBuffer(data, dataSize); + + //} } - /***************************************************************************/ - /*! - - \brief - This is mainly used for images that aren't created internally because - they cannot be created in the traditional way (e.g. swapchain images). - - \param inVkImage - Image already created outside - - \param width - Width of the image - - \param height - Height of the image - - \param depth - Depth of the image - - \param levels - Number of levels in the image - - \param arrayLayers - if the image is an array, this value will be > 1. - - \param imageFormat - Format of the image - - */ - /***************************************************************************/ - SHVkImage::SHVkImage(vk::Image inVkImage, vk::ImageType type, uint32_t inWidth, uint32_t inHeight, uint32_t inDepth, uint32_t arrayLayers, uint8_t levels, vk::Format format, vk::ImageUsageFlags flags) noexcept - : vkImage (inVkImage) - , width{ inWidth } - , height{ inHeight } - , depth{ inDepth } - , mipLevelCount{ levels } - , layerCount{ arrayLayers } - , imageFormat{ format } - , usageFlags{flags} - , alloc{} - , imageType{type} - , createFlags{} - { - } - - SHVkImage::SHVkImage(VmaAllocator const& vmaAllocator, uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) noexcept + SHVkImage::SHVkImage(VmaAllocator const* allocator, uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) noexcept : width {w} , height{h} , depth {1} @@ -132,6 +197,7 @@ namespace SHADE , imageFormat{format} , usageFlags{usage} , createFlags {create} + , vmaAllocator {allocator} { vk::ImageCreateInfo imageCreateInfo{}; imageCreateInfo.imageType = vk::ImageType::e2D; @@ -157,7 +223,7 @@ namespace SHADE VmaAllocationInfo allocInfo{}; VkImage tempImage; - auto result = vmaCreateImage(vmaAllocator, &imageCreateInfo.operator VkImageCreateInfo & (), &allocCreateInfo, &tempImage, &alloc, &allocInfo); + auto result = vmaCreateImage(*vmaAllocator, &imageCreateInfo.operator VkImageCreateInfo & (), &allocCreateInfo, &tempImage, &alloc, &allocInfo); vkImage = tempImage; if (result != VK_SUCCESS) @@ -171,6 +237,69 @@ namespace SHADE return SHVkInstance::GetResourceManager().Create(inLogicalDeviceHdl, parent, createParams); } + void SHVkImage::TransferToDeviceResource(Handle const& cmdBufferHdl) noexcept + { + // prepare copy regions + std::vector copyRegions{mipOffsets.size()}; + + for (uint32_t i = 0; i < mipOffsets.size(); ++i) + { + copyRegions[i].bufferOffset = mipOffsets[i]; + copyRegions[i].bufferRowLength = 0; // for padding + copyRegions[i].bufferImageHeight = 0; // for padding + copyRegions[i].imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; // TODO: Need to change this to base it off image format. + copyRegions[i].imageSubresource.mipLevel = i; + copyRegions[i].imageSubresource.baseArrayLayer = 0; // TODO: Array textures not supported yet + copyRegions[i].imageSubresource.layerCount = layerCount; + copyRegions[i].imageOffset = vk::Offset3D{ 0,0,0 }; + copyRegions[i].imageExtent = vk::Extent3D{ width >> i, height >> i, 1 }; + } + + } + + void SHVkImage::TransitionImage(vk::ImageLayout oldLayout, vk::ImageLayout newLayout) noexcept + { + vk::ImageMemoryBarrier barrier{}; + barrier.oldLayout = oldLayout; + barrier.newLayout = newLayout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = vkImage; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; // TODO: Need to change this to base it off image format. + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mipLevelCount; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = layerCount; + + vk::PipelineStageFlagBits srcStage = vk::PipelineStageFlagBits::eTopOfPipe; + vk::PipelineStageFlagBits dstStage = vk::PipelineStageFlagBits::eTopOfPipe; + + if (oldLayout == vk::ImageLayout::eUndefined && newLayout == vk::ImageLayout::eTransferDstOptimal) + { + srcStage = vk::PipelineStageFlagBits::eTopOfPipe; + dstStage = vk::PipelineStageFlagBits::eTransfer; + + barrier.srcAccessMask = vk::AccessFlagBits::eNone; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; + } + else if (oldLayout == vk::ImageLayout::eTransferDstOptimal && newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) + { + srcStage = vk::PipelineStageFlagBits::eTransfer; + + // TODO, what if we want to access in compute shader + dstStage = vk::PipelineStageFlagBits::eFragmentShader; + + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + } + else + { + SHLOG_ERROR("Image layouts are invalid. "); + } + + //cmdBufferHdl + } + void SHVkImage::LinkWithExteriorImage(vk::Image inVkImage, vk::ImageType type, uint32_t inWidth, uint32_t inHeight, uint32_t inDepth, uint32_t layers, uint8_t levels, vk::Format format, vk::ImageUsageFlags flags) noexcept { vkImage = inVkImage; diff --git a/SHADE_Engine/src/Graphics/Images/SHVkImage.h b/SHADE_Engine/src/Graphics/Images/SHVkImage.h index 48ad1b75..2884cd63 100644 --- a/SHADE_Engine/src/Graphics/Images/SHVkImage.h +++ b/SHADE_Engine/src/Graphics/Images/SHVkImage.h @@ -10,6 +10,7 @@ namespace SHADE { class SHVkLogicalDevice; class SHVkImageView; + class SHVkCommandBuffer; struct SHImageCreateParams { @@ -47,6 +48,12 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* PRIVATE MEMBER VARIABLES */ /*-----------------------------------------------------------------------*/ + //! Pointer to the vma allocator. #NoteToSelf: Not super proud of this being a pointer. + //! The only reason why this is a pointer is because a reference_wrapper cannot be default constructed. + //! And the reason why we want a default constructor is because sometimes we don't want to create images + //! but merely link them from outside (swapchain images) + VmaAllocator const* vmaAllocator; + //! 1D, 2D or 3D vk::ImageType imageType = vk::ImageType::e2D; @@ -80,6 +87,29 @@ namespace SHADE //! allocation object containing details of an allocation VmaAllocation alloc{}; + //! Whether or not this image is HOST_VISIBLE and random access optimized + bool randomAccessOptimized; + + //! Whether or not the memory the image is bound to is memory coherent (updates on CPU can be seen on GPU without flushing cache) + bool boundToCoherent; + + //! Persistently mapped pointer if applicable (will be void if image is + //! not created with the correct flags). Note that this is only used for + //! persistent mapping. One time updates do not use this pointer. + void* mappedPtr; + + //! Staging buffer for images purely in the GPU + vk::Buffer stagingBuffer; + + //! Mipmap offsets for initializing the vk::BufferImageCopy during transfer to GPU resource + std::span mipOffsets; + + /*-----------------------------------------------------------------------*/ + /* PRIVATE MEMBER FUNCTIONS */ + /*-----------------------------------------------------------------------*/ + void PrepStagingBuffer(void* data, uint32_t srcSize) noexcept; + + public: /*-----------------------------------------------------------------------*/ /* CTOR AND DTOR */ @@ -87,16 +117,16 @@ namespace SHADE SHVkImage(void) noexcept = default; SHVkImage( - VmaAllocator const& vmaAllocator, - SHImageCreateParams const& imageDetails, - unsigned char* data, - uint32_t dataSize, - VmaMemoryUsage memUsage, - VmaAllocationCreateFlags allocFlags + VmaAllocator const* allocator, + SHImageCreateParams const& imageDetails, + unsigned char* data, + uint32_t dataSize, + std::span inMipOffsets, + VmaMemoryUsage memUsage, + VmaAllocationCreateFlags allocFlags ) noexcept; - SHVkImage(vk::Image inVkImage, vk::ImageType type, uint32_t inWidth, uint32_t inHeight, uint32_t inDepth, uint32_t arrayLayers, uint8_t levels, vk::Format format, vk::ImageUsageFlags flags) noexcept; - SHVkImage(VmaAllocator const& vmaAllocator, uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) noexcept; + SHVkImage(VmaAllocator const* allocator, uint32_t w, uint32_t h, uint8_t levels, vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags create) noexcept; SHVkImage(SHVkImage&& rhs) noexcept = default; SHVkImage& operator=(SHVkImage && rhs) noexcept = default; @@ -104,7 +134,9 @@ namespace SHADE /*-----------------------------------------------------------------------*/ /* PUBLIC MEMBER FUNCTIONS */ /*-----------------------------------------------------------------------*/ - Handle CreateImageView(Handle const& inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) const noexcept; + Handle CreateImageView (Handle const& inLogicalDeviceHdl, Handle const& parent, SHImageViewDetails const& createParams) const noexcept; + void TransferToDeviceResource (Handle const& cmdBufferHdl) noexcept; + void TransitionImage (vk::ImageLayout oldLayout, vk::ImageLayout newLayout) noexcept; /*-----------------------------------------------------------------------*/ /* GETTERS AND SETTERS */