Files
XCEngine/tests/RHI/unit/test_vulkan_graphics.cpp

777 lines
31 KiB
C++

#if defined(XCENGINE_SUPPORT_VULKAN)
#include <windows.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <vector>
#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<uint32_t>(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<uint32_t>(Format::R8G8B8A8_UNorm);
desc.textureType = static_cast<uint32_t>(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<uint32_t>(Format::D24_UNorm_S8_UInt);
desc.textureType = static_cast<uint32_t>(TextureType::Texture2D);
desc.sampleCount = 1;
return desc;
}
RHICommandList* CreateCommandList() const {
CommandListDesc desc = {};
desc.commandListType = static_cast<uint32_t>(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<uint8_t> ReadTextureRgba8(VulkanTexture* texture) {
EXPECT_NE(texture, nullptr);
EXPECT_EQ(texture->GetFormat(), Format::R8G8B8A8_UNorm);
auto* device = static_cast<VulkanDevice*>(m_device);
const uint32_t width = texture->GetWidth();
const uint32_t height = texture->GetHeight();
const VkDeviceSize bufferSize = static_cast<VkDeviceSize>(width) * static_cast<VkDeviceSize>(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,
&copyRegion);
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<uint8_t> pixels(static_cast<size_t>(bufferSize));
std::copy_n(static_cast<const uint8_t*>(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<uint8_t> pixels = ReadTextureRgba8(static_cast<VulkanTexture*>(colorTexture));
ASSERT_GE(pixels.size(), 4u);
EXPECT_NEAR(static_cast<int>(pixels[0]), 64, 1);
EXPECT_NEAR(static_cast<int>(pixels[1]), 128, 1);
EXPECT_NEAR(static_cast<int>(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<uint8_t> 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<size_t>((y * kWidth + x) * 4);
sourcePixels[index + 0] = static_cast<uint8_t>((x * 13) & 0xFF);
sourcePixels[index + 1] = static_cast<uint8_t>((y * 29) & 0xFF);
sourcePixels[index + 2] = static_cast<uint8_t>(((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<uint8_t> copiedPixels = ReadTextureRgba8(static_cast<VulkanTexture*>(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<uint32_t>(PrimitiveTopologyType::Triangle);
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(Format::Unknown);
InputElementDesc position = {};
position.semanticName = "POSITION";
position.semanticIndex = 0;
position.format = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(DescriptorType::UAV);
uavBinding.count = 1;
uavBinding.visibility = static_cast<uint32_t>(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<uint8_t> pixels = ReadTextureRgba8(static_cast<VulkanTexture*>(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<uint32_t>(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<uint32_t>(DescriptorType::UAV);
uavBinding.count = 1;
uavBinding.visibility = static_cast<uint32_t>(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<uint8_t> pixels = ReadTextureRgba8(static_cast<VulkanTexture*>(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