#if defined(XCENGINE_SUPPORT_VULKAN) #include #include #include #include #include #include #include #include "XCEngine/RHI/RHICommandList.h" #include "XCEngine/RHI/RHICommandQueue.h" #include "XCEngine/RHI/RHIDescriptorPool.h" #include "XCEngine/RHI/RHIDescriptorSet.h" #include "XCEngine/RHI/RHIDevice.h" #include "XCEngine/RHI/RHIFramebuffer.h" #include "XCEngine/RHI/RHIFactory.h" #include "XCEngine/RHI/RHIPipelineLayout.h" #include "XCEngine/RHI/RHIPipelineState.h" #include "XCEngine/RHI/RHIRenderPass.h" #include "XCEngine/RHI/RHIResourceView.h" #include "XCEngine/RHI/RHIShader.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 { std::wstring ResolveShaderPath(const wchar_t* relativePath) { wchar_t exePath[MAX_PATH] = {}; const DWORD length = GetModuleFileNameW(nullptr, exePath, MAX_PATH); std::filesystem::path rootPath = length > 0 ? std::filesystem::path(exePath).parent_path() : std::filesystem::current_path(); for (int i = 0; i < 5; ++i) { rootPath = rootPath.parent_path(); } return (rootPath / relativePath).wstring(); } 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; } } constexpr uint32_t kWriteRedComputeSpirv[] = { 0x07230203, 0x00010000, 0x0008000B, 0x00000017, 0x00000000, 0x00020011, 0x00000001, 0x0006000B, 0x00000001, 0x4C534C47, 0x6474732E, 0x3035342E, 0x00000000, 0x0003000E, 0x00000000, 0x00000001, 0x0005000F, 0x00000005, 0x00000004, 0x6E69616D, 0x00000000, 0x00060010, 0x00000004, 0x00000011, 0x00000001, 0x00000001, 0x00000001, 0x00030003, 0x00000002, 0x000001C2, 0x00040005, 0x00000004, 0x6E69616D, 0x00000000, 0x00040005, 0x00000009, 0x616D4975, 0x00006567, 0x00030047, 0x00000009, 0x00000019, 0x00040047, 0x00000009, 0x00000021, 0x00000000, 0x00040047, 0x00000009, 0x00000022, 0x00000000, 0x00040047, 0x00000016, 0x0000000B, 0x00000019, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00090019, 0x00000007, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000002, 0x00000004, 0x00040020, 0x00000008, 0x00000000, 0x00000007, 0x0004003B, 0x00000008, 0x00000009, 0x00000000, 0x00040015, 0x0000000B, 0x00000020, 0x00000001, 0x00040017, 0x0000000C, 0x0000000B, 0x00000002, 0x0004002B, 0x0000000B, 0x0000000D, 0x00000000, 0x0005002C, 0x0000000C, 0x0000000E, 0x0000000D, 0x0000000D, 0x00040017, 0x0000000F, 0x00000006, 0x00000004, 0x0004002B, 0x00000006, 0x00000010, 0x3F800000, 0x0004002B, 0x00000006, 0x00000011, 0x00000000, 0x0007002C, 0x0000000F, 0x00000012, 0x00000010, 0x00000011, 0x00000011, 0x00000010, 0x00040015, 0x00000013, 0x00000020, 0x00000000, 0x00040017, 0x00000014, 0x00000013, 0x00000003, 0x0004002B, 0x00000013, 0x00000015, 0x00000001, 0x0006002C, 0x00000014, 0x00000016, 0x00000015, 0x00000015, 0x00000015, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200F8, 0x00000005, 0x0004003D, 0x00000007, 0x0000000A, 0x00000009, 0x00040063, 0x0000000A, 0x0000000E, 0x00000012, 0x000100FD, 0x00010038 }; 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); } RHIShader* CreateWriteRedComputeShader() const { ShaderCompileDesc shaderDesc = {}; shaderDesc.sourceLanguage = ShaderLanguage::SPIRV; shaderDesc.profile = L"cs_6_0"; shaderDesc.source.resize(sizeof(kWriteRedComputeSpirv)); std::memcpy(shaderDesc.source.data(), kWriteRedComputeSpirv, sizeof(kWriteRedComputeSpirv)); return m_device->CreateShader(shaderDesc); } RHIShader* CreateWriteRedComputeShaderFromGlsl() const { static const char* computeSource = R"(#version 450 layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; layout(set = 0, binding = 0, rgba8) uniform writeonly image2D uImage; void main() { imageStore(uImage, ivec2(0, 0), vec4(1.0, 0.0, 0.0, 1.0)); } )"; ShaderCompileDesc shaderDesc = {}; shaderDesc.sourceLanguage = ShaderLanguage::GLSL; shaderDesc.source.assign(computeSource, computeSource + std::strlen(computeSource)); return m_device->CreateShader(shaderDesc); } 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; } TEST_F(VulkanGraphicsFixture, CreateShaderFromSpirvProducesValidComputeShader) { RHIShader* shader = CreateWriteRedComputeShader(); ASSERT_NE(shader, nullptr); EXPECT_TRUE(shader->IsValid()); EXPECT_EQ(shader->GetType(), ShaderType::Compute); EXPECT_NE(shader->GetNativeHandle(), nullptr); shader->Shutdown(); delete shader; } TEST_F(VulkanGraphicsFixture, CreateShaderFromGlslSourceProducesValidVertexShader) { static const char* vertexSource = R"(#version 450 layout(location = 0) in vec4 aPosition; void main() { gl_Position = aPosition; } )"; ShaderCompileDesc shaderDesc = {}; shaderDesc.sourceLanguage = ShaderLanguage::GLSL; shaderDesc.profile = L"vs_4_50"; shaderDesc.source.assign(vertexSource, vertexSource + std::strlen(vertexSource)); RHIShader* shader = m_device->CreateShader(shaderDesc); ASSERT_NE(shader, nullptr); EXPECT_TRUE(shader->IsValid()); EXPECT_EQ(shader->GetType(), ShaderType::Vertex); EXPECT_NE(shader->GetNativeHandle(), nullptr); shader->Shutdown(); delete shader; } TEST_F(VulkanGraphicsFixture, CreateShaderFromGlslFileProducesValidVertexShader) { ShaderCompileDesc shaderDesc = {}; shaderDesc.fileName = ResolveShaderPath(L"tests/RHI/integration/triangle/Res/Shader/triangle_vulkan.vert"); shaderDesc.entryPoint = L"main"; shaderDesc.profile = L"vs_4_50"; RHIShader* shader = m_device->CreateShader(shaderDesc); ASSERT_NE(shader, nullptr); EXPECT_TRUE(shader->IsValid()); EXPECT_EQ(shader->GetType(), ShaderType::Vertex); EXPECT_NE(shader->GetNativeHandle(), nullptr); shader->Shutdown(); delete shader; } TEST_F(VulkanGraphicsFixture, CreateShaderFromGlslSourceInfersComputeShader) { RHIShader* shader = CreateWriteRedComputeShaderFromGlsl(); ASSERT_NE(shader, nullptr); EXPECT_TRUE(shader->IsValid()); EXPECT_EQ(shader->GetType(), ShaderType::Compute); EXPECT_NE(shader->GetNativeHandle(), nullptr); shader->Shutdown(); delete shader; } TEST_F(VulkanGraphicsFixture, CreateGraphicsPipelineFromGlslShadersProducesValidPipeline) { static const char* vertexSource = R"(#version 450 layout(location = 0) in vec4 aPosition; void main() { gl_Position = aPosition; } )"; static const char* fragmentSource = R"(#version 450 layout(location = 0) out vec4 fragColor; void main() { fragColor = vec4(1.0, 0.0, 0.0, 1.0); } )"; GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.topologyType = static_cast(PrimitiveTopologyType::Triangle); pipelineDesc.renderTargetFormats[0] = static_cast(Format::R8G8B8A8_UNorm); pipelineDesc.depthStencilFormat = static_cast(Format::Unknown); InputElementDesc position = {}; position.semanticName = "POSITION"; position.semanticIndex = 0; position.format = static_cast(Format::R32G32B32A32_Float); position.inputSlot = 0; position.alignedByteOffset = 0; pipelineDesc.inputLayout.elements.push_back(position); pipelineDesc.vertexShader.sourceLanguage = ShaderLanguage::GLSL; pipelineDesc.vertexShader.profile = L"vs_4_50"; pipelineDesc.vertexShader.source.assign(vertexSource, vertexSource + std::strlen(vertexSource)); pipelineDesc.fragmentShader.sourceLanguage = ShaderLanguage::GLSL; pipelineDesc.fragmentShader.profile = L"fs_4_50"; pipelineDesc.fragmentShader.source.assign(fragmentSource, fragmentSource + std::strlen(fragmentSource)); RHIPipelineState* pipelineState = m_device->CreatePipelineState(pipelineDesc); ASSERT_NE(pipelineState, nullptr); EXPECT_TRUE(pipelineState->IsValid()); EXPECT_NE(pipelineState->GetNativeHandle(), nullptr); pipelineState->Shutdown(); delete pipelineState; } TEST_F(VulkanGraphicsFixture, CreateUnorderedAccessViewProducesValidView) { RHITexture* texture = m_device->CreateTexture(CreateColorTextureDesc(4, 4)); ASSERT_NE(texture, nullptr); ResourceViewDesc viewDesc = {}; viewDesc.format = static_cast(Format::R8G8B8A8_UNorm); viewDesc.dimension = ResourceViewDimension::Texture2D; RHIResourceView* uav = m_device->CreateUnorderedAccessView(texture, viewDesc); ASSERT_NE(uav, nullptr); EXPECT_TRUE(uav->IsValid()); EXPECT_EQ(uav->GetViewType(), ResourceViewType::UnorderedAccess); uav->Shutdown(); delete uav; texture->Shutdown(); delete texture; } TEST_F(VulkanGraphicsFixture, DispatchWritesUavTexture) { RHITexture* texture = m_device->CreateTexture(CreateColorTextureDesc(4, 4)); ASSERT_NE(texture, nullptr); ResourceViewDesc uavDesc = {}; uavDesc.format = static_cast(Format::R8G8B8A8_UNorm); uavDesc.dimension = ResourceViewDimension::Texture2D; RHIResourceView* uav = m_device->CreateUnorderedAccessView(texture, uavDesc); ASSERT_NE(uav, nullptr); DescriptorPoolDesc poolDesc = {}; poolDesc.type = DescriptorHeapType::CBV_SRV_UAV; poolDesc.descriptorCount = 1; poolDesc.shaderVisible = true; RHIDescriptorPool* pool = m_device->CreateDescriptorPool(poolDesc); ASSERT_NE(pool, nullptr); DescriptorSetLayoutBinding uavBinding = {}; uavBinding.binding = 0; uavBinding.type = static_cast(DescriptorType::UAV); uavBinding.count = 1; uavBinding.visibility = static_cast(ShaderVisibility::All); DescriptorSetLayoutDesc setLayout = {}; setLayout.bindings = &uavBinding; setLayout.bindingCount = 1; RHIPipelineLayoutDesc pipelineLayoutDesc = {}; pipelineLayoutDesc.setLayouts = &setLayout; pipelineLayoutDesc.setLayoutCount = 1; RHIPipelineLayout* pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc); ASSERT_NE(pipelineLayout, nullptr); RHIDescriptorSet* descriptorSet = pool->AllocateSet(setLayout); ASSERT_NE(descriptorSet, nullptr); descriptorSet->Update(0, uav); GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.pipelineLayout = pipelineLayout; RHIPipelineState* pipelineState = m_device->CreatePipelineState(pipelineDesc); ASSERT_NE(pipelineState, nullptr); RHIShader* shader = CreateWriteRedComputeShader(); ASSERT_NE(shader, nullptr); pipelineState->SetComputeShader(shader); EXPECT_TRUE(pipelineState->HasComputeShader()); EXPECT_EQ(pipelineState->GetType(), PipelineType::Compute); RHICommandList* commandList = CreateCommandList(); ASSERT_NE(commandList, nullptr); commandList->Reset(); commandList->TransitionBarrier(uav, ResourceStates::Common, ResourceStates::UnorderedAccess); commandList->SetPipelineState(pipelineState); RHIDescriptorSet* descriptorSets[] = { descriptorSet }; commandList->SetComputeDescriptorSets(0, 1, descriptorSets, pipelineLayout); commandList->Dispatch(1, 1, 1); SubmitAndWait(commandList); const std::vector pixels = ReadTextureRgba8(static_cast(texture)); ASSERT_GE(pixels.size(), 4u); EXPECT_EQ(pixels[0], 255u); EXPECT_EQ(pixels[1], 0u); EXPECT_EQ(pixels[2], 0u); EXPECT_EQ(pixels[3], 255u); commandList->Shutdown(); delete commandList; shader->Shutdown(); delete shader; pipelineState->Shutdown(); delete pipelineState; descriptorSet->Shutdown(); delete descriptorSet; pipelineLayout->Shutdown(); delete pipelineLayout; pool->Shutdown(); delete pool; uav->Shutdown(); delete uav; texture->Shutdown(); delete texture; } TEST_F(VulkanGraphicsFixture, DispatchWritesUavTextureWithGlslComputeShader) { RHITexture* texture = m_device->CreateTexture(CreateColorTextureDesc(4, 4)); ASSERT_NE(texture, nullptr); ResourceViewDesc uavDesc = {}; uavDesc.format = static_cast(Format::R8G8B8A8_UNorm); uavDesc.dimension = ResourceViewDimension::Texture2D; RHIResourceView* uav = m_device->CreateUnorderedAccessView(texture, uavDesc); ASSERT_NE(uav, nullptr); DescriptorPoolDesc poolDesc = {}; poolDesc.type = DescriptorHeapType::CBV_SRV_UAV; poolDesc.descriptorCount = 1; poolDesc.shaderVisible = true; RHIDescriptorPool* pool = m_device->CreateDescriptorPool(poolDesc); ASSERT_NE(pool, nullptr); DescriptorSetLayoutBinding uavBinding = {}; uavBinding.binding = 0; uavBinding.type = static_cast(DescriptorType::UAV); uavBinding.count = 1; uavBinding.visibility = static_cast(ShaderVisibility::All); DescriptorSetLayoutDesc setLayout = {}; setLayout.bindings = &uavBinding; setLayout.bindingCount = 1; RHIPipelineLayoutDesc pipelineLayoutDesc = {}; pipelineLayoutDesc.setLayouts = &setLayout; pipelineLayoutDesc.setLayoutCount = 1; RHIPipelineLayout* pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc); ASSERT_NE(pipelineLayout, nullptr); RHIDescriptorSet* descriptorSet = pool->AllocateSet(setLayout); ASSERT_NE(descriptorSet, nullptr); descriptorSet->Update(0, uav); GraphicsPipelineDesc pipelineDesc = {}; pipelineDesc.pipelineLayout = pipelineLayout; RHIPipelineState* pipelineState = m_device->CreatePipelineState(pipelineDesc); ASSERT_NE(pipelineState, nullptr); RHIShader* shader = CreateWriteRedComputeShaderFromGlsl(); ASSERT_NE(shader, nullptr); pipelineState->SetComputeShader(shader); EXPECT_TRUE(pipelineState->HasComputeShader()); EXPECT_EQ(pipelineState->GetType(), PipelineType::Compute); RHICommandList* commandList = CreateCommandList(); ASSERT_NE(commandList, nullptr); commandList->Reset(); commandList->TransitionBarrier(uav, ResourceStates::Common, ResourceStates::UnorderedAccess); commandList->SetPipelineState(pipelineState); RHIDescriptorSet* descriptorSets[] = { descriptorSet }; commandList->SetComputeDescriptorSets(0, 1, descriptorSets, pipelineLayout); commandList->Dispatch(1, 1, 1); SubmitAndWait(commandList); const std::vector pixels = ReadTextureRgba8(static_cast(texture)); ASSERT_GE(pixels.size(), 4u); EXPECT_EQ(pixels[0], 255u); EXPECT_EQ(pixels[1], 0u); EXPECT_EQ(pixels[2], 0u); EXPECT_EQ(pixels[3], 255u); commandList->Shutdown(); delete commandList; shader->Shutdown(); delete shader; pipelineState->Shutdown(); delete pipelineState; descriptorSet->Shutdown(); delete descriptorSet; pipelineLayout->Shutdown(); delete pipelineLayout; pool->Shutdown(); delete pool; uav->Shutdown(); delete uav; texture->Shutdown(); delete texture; } } // namespace #endif