diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h b/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h index bea2a187..7ee512e1 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanCommandList.h @@ -9,7 +9,9 @@ namespace XCEngine { namespace RHI { class VulkanDevice; +class VulkanFramebuffer; class VulkanPipelineState; +class VulkanRenderPass; class VulkanTexture; class VulkanCommandList : public RHICommandList { diff --git a/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h b/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h index c08f272f..4bc39fc0 100644 --- a/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h +++ b/engine/include/XCEngine/RHI/Vulkan/VulkanTexture.h @@ -51,6 +51,8 @@ public: VkImage GetImage() const { return m_image; } VkDeviceMemory GetMemory() const { return m_memory; } VkFormat GetVkFormat() const { return m_vkFormat; } + bool OwnsImage() const { return m_ownsImage; } + bool IsSwapChainImage() const { return !m_ownsImage; } private: VkDevice m_device = VK_NULL_HANDLE; diff --git a/engine/src/RHI/Vulkan/VulkanCommandList.cpp b/engine/src/RHI/Vulkan/VulkanCommandList.cpp index 3dd17762..84942138 100644 --- a/engine/src/RHI/Vulkan/VulkanCommandList.cpp +++ b/engine/src/RHI/Vulkan/VulkanCommandList.cpp @@ -3,11 +3,14 @@ #include "XCEngine/RHI/Vulkan/VulkanBuffer.h" #include "XCEngine/RHI/Vulkan/VulkanDescriptorSet.h" #include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#include "XCEngine/RHI/Vulkan/VulkanFramebuffer.h" #include "XCEngine/RHI/Vulkan/VulkanPipelineLayout.h" #include "XCEngine/RHI/Vulkan/VulkanPipelineState.h" +#include "XCEngine/RHI/Vulkan/VulkanRenderPass.h" #include "XCEngine/RHI/Vulkan/VulkanResourceView.h" #include "XCEngine/RHI/Vulkan/VulkanTexture.h" +#include #include namespace XCEngine { @@ -26,10 +29,18 @@ VkImageLayout ToVulkanImageLayout(ResourceStates state) { case ResourceStates::Present: return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; case ResourceStates::PixelShaderResource: + case ResourceStates::NonPixelShaderResource: + case ResourceStates::GenericRead: return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; case ResourceStates::DepthWrite: return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + case ResourceStates::DepthRead: + return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; + case ResourceStates::UnorderedAccess: + return VK_IMAGE_LAYOUT_GENERAL; case ResourceStates::Common: + case ResourceStates::VertexAndConstantBuffer: + case ResourceStates::IndexBuffer: default: return VK_IMAGE_LAYOUT_UNDEFINED; } @@ -37,16 +48,27 @@ VkImageLayout ToVulkanImageLayout(ResourceStates state) { VkAccessFlags ToVulkanAccessMask(ResourceStates state) { switch (state) { + case ResourceStates::VertexAndConstantBuffer: + return VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT; + case ResourceStates::IndexBuffer: + return VK_ACCESS_INDEX_READ_BIT; case ResourceStates::RenderTarget: return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + case ResourceStates::UnorderedAccess: + return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + case ResourceStates::DepthRead: + return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; case ResourceStates::CopySrc: return VK_ACCESS_TRANSFER_READ_BIT; case ResourceStates::CopyDst: return VK_ACCESS_TRANSFER_WRITE_BIT; case ResourceStates::PixelShaderResource: + case ResourceStates::NonPixelShaderResource: return VK_ACCESS_SHADER_READ_BIT; case ResourceStates::DepthWrite: return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + case ResourceStates::GenericRead: + return VK_ACCESS_MEMORY_READ_BIT; case ResourceStates::Present: case ResourceStates::Common: default: @@ -56,15 +78,29 @@ VkAccessFlags ToVulkanAccessMask(ResourceStates state) { VkPipelineStageFlags ToVulkanStageMask(ResourceStates state) { switch (state) { + case ResourceStates::VertexAndConstantBuffer: + return VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | + VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + case ResourceStates::IndexBuffer: + return VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; case ResourceStates::RenderTarget: return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + case ResourceStates::UnorderedAccess: + return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + case ResourceStates::DepthRead: + return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; case ResourceStates::CopySrc: case ResourceStates::CopyDst: return VK_PIPELINE_STAGE_TRANSFER_BIT; case ResourceStates::PixelShaderResource: return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + case ResourceStates::NonPixelShaderResource: + return VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; case ResourceStates::DepthWrite: return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + case ResourceStates::GenericRead: + return VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; case ResourceStates::Present: return VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; case ResourceStates::Common: @@ -85,6 +121,40 @@ VkImageAspectFlags ResolveClearAspectMask(Format format, uint32_t buffers) { return aspectMask; } +ResourceStates ResolvePostCopyState(ResourceStates previousState, ResourceStates fallbackState) { + return previousState == ResourceStates::Common ? fallbackState : previousState; +} + +void TransitionBuffer(VkCommandBuffer commandBuffer, VulkanBuffer* buffer, ResourceStates newState) { + if (commandBuffer == VK_NULL_HANDLE || buffer == nullptr || buffer->GetBuffer() == VK_NULL_HANDLE) { + return; + } + if (buffer->GetState() == newState) { + return; + } + + VkBufferMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + barrier.srcAccessMask = ToVulkanAccessMask(buffer->GetState()); + barrier.dstAccessMask = ToVulkanAccessMask(newState); + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.buffer = buffer->GetBuffer(); + barrier.offset = 0; + barrier.size = buffer->GetSize(); + + vkCmdPipelineBarrier( + commandBuffer, + ToVulkanStageMask(buffer->GetState()), + ToVulkanStageMask(newState), + 0, + 0, nullptr, + 1, &barrier, + 0, nullptr); + + buffer->SetState(newState); +} + } // namespace VulkanCommandList::~VulkanCommandList() { @@ -138,9 +208,12 @@ void VulkanCommandList::Reset() { return; } + EndActiveRenderPass(); vkResetCommandPool(m_device->GetDevice(), m_commandPool, 0); DestroyTransientFramebuffers(); + m_currentColorTarget = nullptr; + m_currentDepthTarget = nullptr; m_currentPipelineState = nullptr; m_currentPrimitiveTopology = PrimitiveTopology::TriangleList; m_renderPassActive = false; @@ -157,7 +230,9 @@ void VulkanCommandList::Close() { EndActiveRenderPass(); auto* colorView = static_cast(m_currentColorTarget); - if (colorView != nullptr && colorView->GetTexture() != nullptr) { + if (colorView != nullptr && + colorView->GetTexture() != nullptr && + colorView->GetTexture()->IsSwapChainImage()) { TransitionTexture(colorView->GetTexture(), ResourceStates::Present); } @@ -211,11 +286,96 @@ void VulkanCommandList::TransitionBarrier(RHIResourceView* resource, ResourceSta } void VulkanCommandList::BeginRenderPass(class RHIRenderPass* renderPass, class RHIFramebuffer* framebuffer, const Rect& renderArea, uint32_t clearValueCount, const ClearValue* clearValues) { - (void)renderPass; - (void)framebuffer; - (void)renderArea; - (void)clearValueCount; - (void)clearValues; + if (m_commandBuffer == VK_NULL_HANDLE || renderPass == nullptr || framebuffer == nullptr) { + return; + } + + auto* vulkanRenderPass = static_cast(renderPass); + auto* vulkanFramebuffer = static_cast(framebuffer); + if (vulkanRenderPass == nullptr || + vulkanFramebuffer == nullptr || + vulkanRenderPass->GetRenderPass() == VK_NULL_HANDLE || + vulkanFramebuffer->GetFramebuffer() == VK_NULL_HANDLE || + vulkanFramebuffer->GetRenderPass() != vulkanRenderPass) { + return; + } + + EndActiveRenderPass(); + + const uint32_t colorAttachmentCount = vulkanFramebuffer->GetColorAttachmentCount(); + for (uint32_t i = 0; i < colorAttachmentCount; ++i) { + VulkanTexture* texture = vulkanFramebuffer->GetColorAttachmentTexture(i); + if (texture != nullptr) { + TransitionTexture(texture, ResourceStates::RenderTarget); + } + } + + if (VulkanTexture* depthTexture = vulkanFramebuffer->GetDepthStencilTexture()) { + TransitionTexture(depthTexture, ResourceStates::DepthWrite); + } + + std::vector vkClearValues(vulkanRenderPass->GetAttachmentCount()); + const AttachmentDesc* colorAttachments = vulkanRenderPass->GetColorAttachments(); + for (uint32_t i = 0; i < colorAttachmentCount; ++i) { + if (clearValues != nullptr && + clearValueCount > i && + colorAttachments != nullptr && + colorAttachments[i].loadOp == LoadAction::Clear) { + vkClearValues[i].color.float32[0] = clearValues[i].color.r; + vkClearValues[i].color.float32[1] = clearValues[i].color.g; + vkClearValues[i].color.float32[2] = clearValues[i].color.b; + vkClearValues[i].color.float32[3] = clearValues[i].color.a; + } + } + + if (vulkanRenderPass->HasDepthStencil()) { + const uint32_t depthIndex = colorAttachmentCount; + const AttachmentDesc* depthAttachment = vulkanRenderPass->GetDepthStencilAttachment(); + if (clearValues != nullptr && clearValueCount > depthIndex && depthAttachment != nullptr) { + if (depthAttachment->loadOp == LoadAction::Clear) { + vkClearValues[depthIndex].depthStencil.depth = clearValues[depthIndex].depth; + } + if (depthAttachment->stencilLoadOp == LoadAction::Clear) { + vkClearValues[depthIndex].depthStencil.stencil = clearValues[depthIndex].stencil; + } + } + } + + const uint32_t width = renderArea.right > renderArea.left + ? static_cast(renderArea.right - renderArea.left) + : vulkanFramebuffer->GetWidth(); + const uint32_t height = renderArea.bottom > renderArea.top + ? static_cast(renderArea.bottom - renderArea.top) + : vulkanFramebuffer->GetHeight(); + + VkRenderPassBeginInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = vulkanRenderPass->GetRenderPass(); + renderPassInfo.framebuffer = vulkanFramebuffer->GetFramebuffer(); + renderPassInfo.renderArea.offset = { renderArea.left, renderArea.top }; + renderPassInfo.renderArea.extent = { width, height }; + renderPassInfo.clearValueCount = static_cast(vkClearValues.size()); + renderPassInfo.pClearValues = vkClearValues.empty() ? nullptr : vkClearValues.data(); + + vkCmdBeginRenderPass(m_commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + m_currentColorTarget = colorAttachmentCount > 0 ? vulkanFramebuffer->GetColorAttachmentView(0) : nullptr; + m_currentDepthTarget = vulkanFramebuffer->GetDepthStencilView(); + m_renderPassActive = true; + + m_viewport.x = static_cast(renderArea.left); + m_viewport.y = static_cast(renderArea.top) + static_cast(height); + m_viewport.width = static_cast(width); + m_viewport.height = -static_cast(height); + m_viewport.minDepth = 0.0f; + m_viewport.maxDepth = 1.0f; + m_hasViewport = true; + vkCmdSetViewport(m_commandBuffer, 0, 1, &m_viewport); + + m_scissor.offset = { renderArea.left, renderArea.top }; + m_scissor.extent = { width, height }; + m_hasScissor = true; + vkCmdSetScissor(m_commandBuffer, 0, 1, &m_scissor); } void VulkanCommandList::EndRenderPass() { @@ -516,8 +676,89 @@ void VulkanCommandList::ClearDepthStencil(RHIResourceView* depthStencil, float d } void VulkanCommandList::CopyResource(RHIResourceView* dst, RHIResourceView* src) { - (void)dst; - (void)src; + auto* dstView = static_cast(dst); + auto* srcView = static_cast(src); + if (m_commandBuffer == VK_NULL_HANDLE || + dstView == nullptr || + srcView == nullptr || + !dstView->IsValid() || + !srcView->IsValid()) { + return; + } + + EndActiveRenderPass(); + + VulkanTexture* dstTexture = dstView->GetTexture(); + VulkanTexture* srcTexture = srcView->GetTexture(); + if (dstTexture != nullptr && srcTexture != nullptr) { + const VkImageAspectFlags srcAspectMask = GetImageAspectMask(srcTexture->GetFormat()); + const VkImageAspectFlags dstAspectMask = GetImageAspectMask(dstTexture->GetFormat()); + if (srcAspectMask != dstAspectMask) { + return; + } + + const ResourceStates previousSrcState = srcTexture->GetState(); + const ResourceStates previousDstState = dstTexture->GetState(); + + TransitionTexture(srcTexture, ResourceStates::CopySrc); + TransitionTexture(dstTexture, ResourceStates::CopyDst); + + VkImageCopy copyRegion = {}; + copyRegion.srcSubresource.aspectMask = srcAspectMask; + copyRegion.srcSubresource.mipLevel = 0; + copyRegion.srcSubresource.baseArrayLayer = 0; + copyRegion.srcSubresource.layerCount = 1; + copyRegion.dstSubresource.aspectMask = dstAspectMask; + copyRegion.dstSubresource.mipLevel = 0; + copyRegion.dstSubresource.baseArrayLayer = 0; + copyRegion.dstSubresource.layerCount = 1; + copyRegion.extent.width = (std::min)(srcTexture->GetWidth(), dstTexture->GetWidth()); + copyRegion.extent.height = (std::min)(srcTexture->GetHeight(), dstTexture->GetHeight()); + copyRegion.extent.depth = (std::max)(1u, (std::min)(srcTexture->GetDepth(), dstTexture->GetDepth())); + + vkCmdCopyImage( + m_commandBuffer, + srcTexture->GetImage(), + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstTexture->GetImage(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ©Region); + + TransitionTexture(srcTexture, ResolvePostCopyState(previousSrcState, ResourceStates::PixelShaderResource)); + TransitionTexture(dstTexture, ResolvePostCopyState(previousDstState, ResourceStates::PixelShaderResource)); + return; + } + + VulkanBuffer* dstBuffer = dstView->GetBufferResource(); + VulkanBuffer* srcBuffer = srcView->GetBufferResource(); + if (dstBuffer != nullptr && srcBuffer != nullptr) { + const VkDeviceSize srcSize = srcView->GetBufferSize() > 0 + ? static_cast(srcView->GetBufferSize()) + : static_cast(srcBuffer->GetSize()); + const VkDeviceSize dstSize = dstView->GetBufferSize() > 0 + ? static_cast(dstView->GetBufferSize()) + : static_cast(dstBuffer->GetSize()); + const VkDeviceSize copySize = (std::min)(srcSize, dstSize); + if (copySize == 0) { + return; + } + + const ResourceStates previousSrcState = srcBuffer->GetState(); + const ResourceStates previousDstState = dstBuffer->GetState(); + + TransitionBuffer(m_commandBuffer, srcBuffer, ResourceStates::CopySrc); + TransitionBuffer(m_commandBuffer, dstBuffer, ResourceStates::CopyDst); + + VkBufferCopy copyRegion = {}; + copyRegion.srcOffset = srcView->GetBufferOffset(); + copyRegion.dstOffset = dstView->GetBufferOffset(); + copyRegion.size = copySize; + vkCmdCopyBuffer(m_commandBuffer, srcBuffer->GetBuffer(), dstBuffer->GetBuffer(), 1, ©Region); + + TransitionBuffer(m_commandBuffer, srcBuffer, ResolvePostCopyState(previousSrcState, ResourceStates::GenericRead)); + TransitionBuffer(m_commandBuffer, dstBuffer, ResolvePostCopyState(previousDstState, ResourceStates::GenericRead)); + } } void VulkanCommandList::Dispatch(uint32_t x, uint32_t y, uint32_t z) { diff --git a/engine/src/RHI/Vulkan/VulkanDevice.cpp b/engine/src/RHI/Vulkan/VulkanDevice.cpp index 44a2e1e9..a78f0359 100644 --- a/engine/src/RHI/Vulkan/VulkanDevice.cpp +++ b/engine/src/RHI/Vulkan/VulkanDevice.cpp @@ -6,8 +6,10 @@ #include "XCEngine/RHI/Vulkan/VulkanDescriptorPool.h" #include "XCEngine/RHI/Vulkan/VulkanDescriptorSet.h" #include "XCEngine/RHI/Vulkan/VulkanFence.h" +#include "XCEngine/RHI/Vulkan/VulkanFramebuffer.h" #include "XCEngine/RHI/Vulkan/VulkanPipelineLayout.h" #include "XCEngine/RHI/Vulkan/VulkanPipelineState.h" +#include "XCEngine/RHI/Vulkan/VulkanRenderPass.h" #include "XCEngine/RHI/Vulkan/VulkanResourceView.h" #include "XCEngine/RHI/Vulkan/VulkanSampler.h" #include "XCEngine/RHI/Vulkan/VulkanSwapChain.h" @@ -648,19 +650,22 @@ RHISampler* VulkanDevice::CreateSampler(const SamplerDesc& desc) { } RHIRenderPass* VulkanDevice::CreateRenderPass(uint32_t colorAttachmentCount, const AttachmentDesc* colorAttachments, const AttachmentDesc* depthStencilAttachment) { - (void)colorAttachmentCount; - (void)colorAttachments; - (void)depthStencilAttachment; + auto* renderPass = new VulkanRenderPass(); + if (renderPass->Initialize(m_device, colorAttachmentCount, colorAttachments, depthStencilAttachment)) { + return renderPass; + } + + delete renderPass; return nullptr; } RHIFramebuffer* VulkanDevice::CreateFramebuffer(class RHIRenderPass* renderPass, uint32_t width, uint32_t height, uint32_t colorAttachmentCount, RHIResourceView** colorAttachments, RHIResourceView* depthStencilAttachment) { - (void)renderPass; - (void)width; - (void)height; - (void)colorAttachmentCount; - (void)colorAttachments; - (void)depthStencilAttachment; + auto* framebuffer = new VulkanFramebuffer(); + if (framebuffer->Initialize(m_device, renderPass, width, height, colorAttachmentCount, colorAttachments, depthStencilAttachment)) { + return framebuffer; + } + + delete framebuffer; return nullptr; } diff --git a/engine/src/RHI/Vulkan/VulkanFramebuffer.cpp b/engine/src/RHI/Vulkan/VulkanFramebuffer.cpp index f67987bc..e3ac4706 100644 --- a/engine/src/RHI/Vulkan/VulkanFramebuffer.cpp +++ b/engine/src/RHI/Vulkan/VulkanFramebuffer.cpp @@ -34,6 +34,15 @@ bool VulkanFramebuffer::Initialize(VkDevice device, RHIRenderPass* renderPass, u m_renderPass = vulkanRenderPass; m_width = width; m_height = height; + if (colorAttachmentCount != vulkanRenderPass->GetColorAttachmentCount()) { + Shutdown(); + return false; + } + if ((vulkanRenderPass->HasDepthStencil() && depthStencilAttachment == nullptr) || + (!vulkanRenderPass->HasDepthStencil() && depthStencilAttachment != nullptr)) { + Shutdown(); + return false; + } m_colorAttachmentViews.reserve(colorAttachmentCount); std::vector attachments; diff --git a/engine/src/RHI/Vulkan/VulkanRenderPass.cpp b/engine/src/RHI/Vulkan/VulkanRenderPass.cpp index 34a62ebc..38d3d61f 100644 --- a/engine/src/RHI/Vulkan/VulkanRenderPass.cpp +++ b/engine/src/RHI/Vulkan/VulkanRenderPass.cpp @@ -51,7 +51,9 @@ bool VulkanRenderPass::Initialize(VkDevice device, uint32_t colorAttachmentCount m_device = device; m_colorAttachmentCount = colorAttachmentCount; - m_colorAttachments.assign(colorAttachments, colorAttachments + colorAttachmentCount); + if (colorAttachmentCount > 0) { + m_colorAttachments.assign(colorAttachments, colorAttachments + colorAttachmentCount); + } if (depthStencilAttachment != nullptr) { m_depthStencilAttachment = *depthStencilAttachment; m_hasDepthStencil = true; diff --git a/tests/RHI/unit/CMakeLists.txt b/tests/RHI/unit/CMakeLists.txt index 0beead3c..7acb8b4b 100644 --- a/tests/RHI/unit/CMakeLists.txt +++ b/tests/RHI/unit/CMakeLists.txt @@ -26,6 +26,7 @@ set(TEST_SOURCES test_capabilities.cpp test_views.cpp test_screenshot.cpp + test_vulkan_graphics.cpp ${CMAKE_SOURCE_DIR}/tests/opengl/package/src/glad.c ) diff --git a/tests/RHI/unit/test_vulkan_graphics.cpp b/tests/RHI/unit/test_vulkan_graphics.cpp new file mode 100644 index 00000000..b7255220 --- /dev/null +++ b/tests/RHI/unit/test_vulkan_graphics.cpp @@ -0,0 +1,422 @@ +#if defined(XCENGINE_SUPPORT_VULKAN) + +#include + +#include +#include +#include + +#include "XCEngine/RHI/RHICommandList.h" +#include "XCEngine/RHI/RHICommandQueue.h" +#include "XCEngine/RHI/RHIDevice.h" +#include "XCEngine/RHI/RHIFramebuffer.h" +#include "XCEngine/RHI/RHIFactory.h" +#include "XCEngine/RHI/RHIRenderPass.h" +#include "XCEngine/RHI/RHIResourceView.h" +#include "XCEngine/RHI/RHITexture.h" +#include "XCEngine/RHI/Vulkan/VulkanCommon.h" +#include "XCEngine/RHI/Vulkan/VulkanDevice.h" +#include "XCEngine/RHI/Vulkan/VulkanTexture.h" + +using namespace XCEngine::RHI; + +namespace { + +VkImageLayout ToImageLayout(ResourceStates state) { + switch (state) { + case ResourceStates::RenderTarget: + return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + case ResourceStates::DepthWrite: + return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + case ResourceStates::DepthRead: + return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; + case ResourceStates::CopySrc: + return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + case ResourceStates::CopyDst: + return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + case ResourceStates::Present: + return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + case ResourceStates::PixelShaderResource: + case ResourceStates::NonPixelShaderResource: + case ResourceStates::GenericRead: + return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + case ResourceStates::UnorderedAccess: + return VK_IMAGE_LAYOUT_GENERAL; + case ResourceStates::Common: + case ResourceStates::VertexAndConstantBuffer: + case ResourceStates::IndexBuffer: + default: + return VK_IMAGE_LAYOUT_UNDEFINED; + } +} + +VkAccessFlags ToAccessMask(ResourceStates state) { + switch (state) { + case ResourceStates::RenderTarget: + return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + case ResourceStates::DepthWrite: + return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + case ResourceStates::DepthRead: + return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + case ResourceStates::CopySrc: + return VK_ACCESS_TRANSFER_READ_BIT; + case ResourceStates::CopyDst: + return VK_ACCESS_TRANSFER_WRITE_BIT; + case ResourceStates::PixelShaderResource: + case ResourceStates::NonPixelShaderResource: + return VK_ACCESS_SHADER_READ_BIT; + case ResourceStates::GenericRead: + return VK_ACCESS_MEMORY_READ_BIT; + case ResourceStates::UnorderedAccess: + return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + case ResourceStates::Present: + case ResourceStates::Common: + case ResourceStates::VertexAndConstantBuffer: + case ResourceStates::IndexBuffer: + default: + return 0; + } +} + +VkPipelineStageFlags ToStageMask(ResourceStates state) { + switch (state) { + case ResourceStates::RenderTarget: + return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + case ResourceStates::DepthWrite: + case ResourceStates::DepthRead: + return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + case ResourceStates::CopySrc: + case ResourceStates::CopyDst: + return VK_PIPELINE_STAGE_TRANSFER_BIT; + case ResourceStates::PixelShaderResource: + return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + case ResourceStates::NonPixelShaderResource: + return VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; + case ResourceStates::GenericRead: + return VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + case ResourceStates::UnorderedAccess: + return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + case ResourceStates::Present: + return VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + case ResourceStates::Common: + case ResourceStates::VertexAndConstantBuffer: + case ResourceStates::IndexBuffer: + default: + return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + } +} + +class VulkanGraphicsFixture : public ::testing::Test { +protected: + void SetUp() override { + m_device = RHIFactory::CreateRHIDevice(RHIType::Vulkan); + ASSERT_NE(m_device, nullptr); + + RHIDeviceDesc deviceDesc = {}; + deviceDesc.enableDebugLayer = false; + ASSERT_TRUE(m_device->Initialize(deviceDesc)); + + CommandQueueDesc queueDesc = {}; + queueDesc.queueType = static_cast(CommandQueueType::Direct); + m_queue = m_device->CreateCommandQueue(queueDesc); + ASSERT_NE(m_queue, nullptr); + } + + void TearDown() override { + if (m_queue != nullptr) { + m_queue->WaitForIdle(); + m_queue->Shutdown(); + delete m_queue; + m_queue = nullptr; + } + + if (m_device != nullptr) { + m_device->Shutdown(); + delete m_device; + m_device = nullptr; + } + } + + TextureDesc CreateColorTextureDesc(uint32_t width, uint32_t height) const { + TextureDesc desc = {}; + desc.width = width; + desc.height = height; + desc.depth = 1; + desc.mipLevels = 1; + desc.arraySize = 1; + desc.format = static_cast(Format::R8G8B8A8_UNorm); + desc.textureType = static_cast(TextureType::Texture2D); + desc.sampleCount = 1; + return desc; + } + + TextureDesc CreateDepthTextureDesc(uint32_t width, uint32_t height) const { + TextureDesc desc = {}; + desc.width = width; + desc.height = height; + desc.depth = 1; + desc.mipLevels = 1; + desc.arraySize = 1; + desc.format = static_cast(Format::D24_UNorm_S8_UInt); + desc.textureType = static_cast(TextureType::Texture2D); + desc.sampleCount = 1; + return desc; + } + + RHICommandList* CreateCommandList() const { + CommandListDesc desc = {}; + desc.commandListType = static_cast(CommandQueueType::Direct); + return m_device->CreateCommandList(desc); + } + + void SubmitAndWait(RHICommandList* commandList) { + ASSERT_NE(commandList, nullptr); + commandList->Close(); + void* commandLists[] = { commandList }; + m_queue->ExecuteCommandLists(1, commandLists); + m_queue->WaitForIdle(); + } + + std::vector ReadTextureRgba8(VulkanTexture* texture) { + EXPECT_NE(texture, nullptr); + EXPECT_EQ(texture->GetFormat(), Format::R8G8B8A8_UNorm); + + auto* device = static_cast(m_device); + const uint32_t width = texture->GetWidth(); + const uint32_t height = texture->GetHeight(); + const VkDeviceSize bufferSize = static_cast(width) * static_cast(height) * 4; + + VkBuffer stagingBuffer = VK_NULL_HANDLE; + VkDeviceMemory stagingMemory = VK_NULL_HANDLE; + VkCommandPool commandPool = VK_NULL_HANDLE; + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + + VkBufferCreateInfo bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = bufferSize; + bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + EXPECT_EQ(vkCreateBuffer(device->GetDevice(), &bufferInfo, nullptr, &stagingBuffer), VK_SUCCESS); + + VkMemoryRequirements memoryRequirements = {}; + vkGetBufferMemoryRequirements(device->GetDevice(), stagingBuffer, &memoryRequirements); + + VkMemoryAllocateInfo allocateInfo = {}; + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = memoryRequirements.size; + allocateInfo.memoryTypeIndex = device->FindMemoryType( + memoryRequirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + EXPECT_NE(allocateInfo.memoryTypeIndex, UINT32_MAX); + EXPECT_EQ(vkAllocateMemory(device->GetDevice(), &allocateInfo, nullptr, &stagingMemory), VK_SUCCESS); + EXPECT_EQ(vkBindBufferMemory(device->GetDevice(), stagingBuffer, stagingMemory, 0), VK_SUCCESS); + + VkCommandPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = device->GetGraphicsQueueFamilyIndex(); + EXPECT_EQ(vkCreateCommandPool(device->GetDevice(), &poolInfo, nullptr, &commandPool), VK_SUCCESS); + + VkCommandBufferAllocateInfo commandBufferInfo = {}; + commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + commandBufferInfo.commandPool = commandPool; + commandBufferInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + commandBufferInfo.commandBufferCount = 1; + EXPECT_EQ(vkAllocateCommandBuffers(device->GetDevice(), &commandBufferInfo, &commandBuffer), VK_SUCCESS); + + VkCommandBufferBeginInfo beginInfo = {}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + EXPECT_EQ(vkBeginCommandBuffer(commandBuffer, &beginInfo), VK_SUCCESS); + + const ResourceStates previousState = texture->GetState(); + + VkImageMemoryBarrier toCopySource = {}; + toCopySource.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + toCopySource.oldLayout = ToImageLayout(previousState); + toCopySource.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + toCopySource.srcAccessMask = ToAccessMask(previousState); + toCopySource.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + toCopySource.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toCopySource.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toCopySource.image = texture->GetImage(); + toCopySource.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + toCopySource.subresourceRange.baseMipLevel = 0; + toCopySource.subresourceRange.levelCount = 1; + toCopySource.subresourceRange.baseArrayLayer = 0; + toCopySource.subresourceRange.layerCount = 1; + vkCmdPipelineBarrier( + commandBuffer, + ToStageMask(previousState), + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &toCopySource); + + VkBufferImageCopy copyRegion = {}; + copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.imageSubresource.mipLevel = 0; + copyRegion.imageSubresource.baseArrayLayer = 0; + copyRegion.imageSubresource.layerCount = 1; + copyRegion.imageExtent.width = width; + copyRegion.imageExtent.height = height; + copyRegion.imageExtent.depth = 1; + vkCmdCopyImageToBuffer( + commandBuffer, + texture->GetImage(), + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + stagingBuffer, + 1, + ©Region); + + VkImageMemoryBarrier restoreBarrier = toCopySource; + restoreBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + restoreBarrier.newLayout = ToImageLayout(previousState); + restoreBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + restoreBarrier.dstAccessMask = ToAccessMask(previousState); + vkCmdPipelineBarrier( + commandBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + ToStageMask(previousState), + 0, + 0, nullptr, + 0, nullptr, + 1, &restoreBarrier); + + EXPECT_EQ(vkEndCommandBuffer(commandBuffer), VK_SUCCESS); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + EXPECT_EQ(vkQueueSubmit(device->GetGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE), VK_SUCCESS); + EXPECT_EQ(vkQueueWaitIdle(device->GetGraphicsQueue()), VK_SUCCESS); + + void* mappedData = nullptr; + EXPECT_EQ(vkMapMemory(device->GetDevice(), stagingMemory, 0, bufferSize, 0, &mappedData), VK_SUCCESS); + std::vector pixels(static_cast(bufferSize)); + std::copy_n(static_cast(mappedData), pixels.size(), pixels.data()); + vkUnmapMemory(device->GetDevice(), stagingMemory); + + vkDestroyCommandPool(device->GetDevice(), commandPool, nullptr); + vkFreeMemory(device->GetDevice(), stagingMemory, nullptr); + vkDestroyBuffer(device->GetDevice(), stagingBuffer, nullptr); + return pixels; + } + + RHIDevice* m_device = nullptr; + RHICommandQueue* m_queue = nullptr; +}; + +TEST_F(VulkanGraphicsFixture, RenderPassFramebufferBeginRenderPassClearWritesColor) { + AttachmentDesc colorAttachment = {}; + colorAttachment.format = Format::R8G8B8A8_UNorm; + colorAttachment.loadOp = LoadAction::Clear; + colorAttachment.storeOp = StoreAction::Store; + + AttachmentDesc depthAttachment = {}; + depthAttachment.format = Format::D24_UNorm_S8_UInt; + depthAttachment.loadOp = LoadAction::Clear; + depthAttachment.storeOp = StoreAction::Store; + depthAttachment.stencilLoadOp = LoadAction::Clear; + depthAttachment.stencilStoreOp = StoreAction::Store; + + RHIRenderPass* renderPass = m_device->CreateRenderPass(1, &colorAttachment, &depthAttachment); + ASSERT_NE(renderPass, nullptr); + + RHITexture* colorTexture = m_device->CreateTexture(CreateColorTextureDesc(64, 64)); + RHITexture* depthTexture = m_device->CreateTexture(CreateDepthTextureDesc(64, 64)); + ASSERT_NE(colorTexture, nullptr); + ASSERT_NE(depthTexture, nullptr); + + RHIResourceView* colorView = m_device->CreateRenderTargetView(colorTexture, {}); + RHIResourceView* depthView = m_device->CreateDepthStencilView(depthTexture, {}); + ASSERT_NE(colorView, nullptr); + ASSERT_NE(depthView, nullptr); + + RHIFramebuffer* framebuffer = m_device->CreateFramebuffer(renderPass, 64, 64, 1, &colorView, depthView); + ASSERT_NE(framebuffer, nullptr); + + RHICommandList* commandList = CreateCommandList(); + ASSERT_NE(commandList, nullptr); + + ClearValue clearValues[2] = {}; + clearValues[0].color = { 0.25f, 0.5f, 0.75f, 1.0f }; + clearValues[1].depth = 1.0f; + clearValues[1].stencil = 0; + + commandList->Reset(); + commandList->BeginRenderPass(renderPass, framebuffer, Rect{0, 0, 64, 64}, 2, clearValues); + commandList->EndRenderPass(); + SubmitAndWait(commandList); + + const std::vector pixels = ReadTextureRgba8(static_cast(colorTexture)); + ASSERT_GE(pixels.size(), 4u); + EXPECT_NEAR(static_cast(pixels[0]), 64, 1); + EXPECT_NEAR(static_cast(pixels[1]), 128, 1); + EXPECT_NEAR(static_cast(pixels[2]), 191, 1); + EXPECT_EQ(pixels[3], 255u); + + commandList->Shutdown(); + delete commandList; + framebuffer->Shutdown(); + delete framebuffer; + delete depthView; + delete colorView; + depthTexture->Shutdown(); + delete depthTexture; + colorTexture->Shutdown(); + delete colorTexture; + renderPass->Shutdown(); + delete renderPass; +} + +TEST_F(VulkanGraphicsFixture, CopyResourceCopiesTexturePixels) { + constexpr uint32_t kWidth = 32; + constexpr uint32_t kHeight = 16; + + std::vector sourcePixels(kWidth * kHeight * 4); + for (uint32_t y = 0; y < kHeight; ++y) { + for (uint32_t x = 0; x < kWidth; ++x) { + const size_t index = static_cast((y * kWidth + x) * 4); + sourcePixels[index + 0] = static_cast((x * 13) & 0xFF); + sourcePixels[index + 1] = static_cast((y * 29) & 0xFF); + sourcePixels[index + 2] = static_cast(((x + y) * 17) & 0xFF); + sourcePixels[index + 3] = 255; + } + } + + TextureDesc textureDesc = CreateColorTextureDesc(kWidth, kHeight); + RHITexture* sourceTexture = m_device->CreateTexture(textureDesc, sourcePixels.data(), sourcePixels.size(), kWidth * 4); + RHITexture* destinationTexture = m_device->CreateTexture(textureDesc); + ASSERT_NE(sourceTexture, nullptr); + ASSERT_NE(destinationTexture, nullptr); + + RHIResourceView* sourceView = m_device->CreateShaderResourceView(sourceTexture, {}); + RHIResourceView* destinationView = m_device->CreateShaderResourceView(destinationTexture, {}); + ASSERT_NE(sourceView, nullptr); + ASSERT_NE(destinationView, nullptr); + + RHICommandList* commandList = CreateCommandList(); + ASSERT_NE(commandList, nullptr); + + commandList->Reset(); + commandList->CopyResource(destinationView, sourceView); + SubmitAndWait(commandList); + + const std::vector copiedPixels = ReadTextureRgba8(static_cast(destinationTexture)); + EXPECT_EQ(copiedPixels, sourcePixels); + + commandList->Shutdown(); + delete commandList; + delete destinationView; + delete sourceView; + destinationTexture->Shutdown(); + delete destinationTexture; + sourceTexture->Shutdown(); + delete sourceTexture; +} + +} // namespace + +#endif