Add Vulkan graphics descriptor offset unit test
This commit is contained in:
@@ -56,6 +56,7 @@ tests/RHI/Vulkan/
|
||||
- RTV / DSV / SRV 视图创建与原生 image-view 句柄有效性
|
||||
- `Sampler` 创建与原生 sampler 句柄有效性
|
||||
- `DescriptorSet` 的原生句柄、绑定布局元数据与常量缓冲脏标记行为
|
||||
- graphics draw 到离屏纹理后的像素回读,以及 `SetGraphicsDescriptorSets(firstSet != 0)` 绑定路径
|
||||
- `PipelineLayout` 的显式 set-layout 聚合与 flat-count 合成逻辑
|
||||
|
||||
当前这些 Vulkan backend unit 用例已经按职责拆分为 fixture / render-pass / shader / pipeline / compute 几个文件,避免后续继续堆到单个巨型测试文件中。
|
||||
|
||||
@@ -12,6 +12,7 @@ set(TEST_SOURCES
|
||||
fixtures/VulkanTestFixture.cpp
|
||||
test_compute.cpp
|
||||
test_descriptor_set.cpp
|
||||
test_graphics.cpp
|
||||
test_pipeline_state.cpp
|
||||
test_pipeline_layout.cpp
|
||||
test_render_pass.cpp
|
||||
|
||||
309
tests/RHI/Vulkan/unit/test_graphics.cpp
Normal file
309
tests/RHI/Vulkan/unit/test_graphics.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
#if defined(XCENGINE_SUPPORT_VULKAN)
|
||||
|
||||
#include "fixtures/VulkanTestFixture.h"
|
||||
#include "XCEngine/RHI/RHIBuffer.h"
|
||||
#include "XCEngine/RHI/RHIDescriptorPool.h"
|
||||
#include "XCEngine/RHI/RHIDescriptorSet.h"
|
||||
#include "XCEngine/RHI/RHIPipelineLayout.h"
|
||||
#include "XCEngine/RHI/RHIPipelineState.h"
|
||||
#include "XCEngine/RHI/RHIResourceView.h"
|
||||
#include "XCEngine/RHI/RHISampler.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanTexture.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
using namespace XCEngine::RHI;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Vertex {
|
||||
float pos[4];
|
||||
float uv[2];
|
||||
};
|
||||
|
||||
constexpr Vertex kQuadVertices[] = {
|
||||
{{-0.5f, -0.5f, 0.0f, 1.0f}, {0.0f, 1.0f}},
|
||||
{{-0.5f, 0.5f, 0.0f, 1.0f}, {0.0f, 0.0f}},
|
||||
{{ 0.5f, -0.5f, 0.0f, 1.0f}, {1.0f, 1.0f}},
|
||||
{{ 0.5f, 0.5f, 0.0f, 1.0f}, {1.0f, 0.0f}},
|
||||
};
|
||||
|
||||
constexpr uint32_t kQuadIndices[] = {0, 1, 2, 2, 1, 3};
|
||||
|
||||
const char kVertexShader[] = R"(#version 450
|
||||
layout(location = 0) in vec4 aPosition;
|
||||
layout(location = 1) in vec2 aTexCoord;
|
||||
|
||||
layout(location = 0) out vec2 vTexCoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = aPosition;
|
||||
vTexCoord = aTexCoord;
|
||||
}
|
||||
)";
|
||||
|
||||
const char kFragmentShader[] = R"(#version 450
|
||||
layout(set = 1, binding = 0) uniform texture2D uTexture;
|
||||
layout(set = 2, binding = 0) uniform sampler uSampler;
|
||||
|
||||
layout(location = 0) in vec2 vTexCoord;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = texture(sampler2D(uTexture, uSampler), vTexCoord);
|
||||
}
|
||||
)";
|
||||
|
||||
GraphicsPipelineDesc CreatePipelineDesc(RHIPipelineLayout* pipelineLayout) {
|
||||
GraphicsPipelineDesc desc = {};
|
||||
desc.pipelineLayout = pipelineLayout;
|
||||
desc.topologyType = static_cast<uint32_t>(PrimitiveTopologyType::Triangle);
|
||||
desc.renderTargetFormats[0] = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
|
||||
desc.depthStencilFormat = static_cast<uint32_t>(Format::Unknown);
|
||||
desc.sampleCount = 1;
|
||||
|
||||
desc.rasterizerState.fillMode = static_cast<uint32_t>(FillMode::Solid);
|
||||
desc.rasterizerState.cullMode = static_cast<uint32_t>(CullMode::None);
|
||||
desc.rasterizerState.frontFace = static_cast<uint32_t>(FrontFace::CounterClockwise);
|
||||
desc.rasterizerState.depthClipEnable = true;
|
||||
|
||||
desc.depthStencilState.depthTestEnable = false;
|
||||
desc.depthStencilState.depthWriteEnable = false;
|
||||
desc.depthStencilState.stencilEnable = false;
|
||||
|
||||
InputElementDesc position = {};
|
||||
position.semanticName = "POSITION";
|
||||
position.semanticIndex = 0;
|
||||
position.format = static_cast<uint32_t>(Format::R32G32B32A32_Float);
|
||||
position.inputSlot = 0;
|
||||
position.alignedByteOffset = 0;
|
||||
desc.inputLayout.elements.push_back(position);
|
||||
|
||||
InputElementDesc texcoord = {};
|
||||
texcoord.semanticName = "TEXCOORD";
|
||||
texcoord.semanticIndex = 0;
|
||||
texcoord.format = static_cast<uint32_t>(Format::R32G32_Float);
|
||||
texcoord.inputSlot = 0;
|
||||
texcoord.alignedByteOffset = sizeof(float) * 4;
|
||||
desc.inputLayout.elements.push_back(texcoord);
|
||||
|
||||
desc.vertexShader.source.assign(kVertexShader, kVertexShader + std::strlen(kVertexShader));
|
||||
desc.vertexShader.sourceLanguage = ShaderLanguage::GLSL;
|
||||
desc.vertexShader.profile = L"vs";
|
||||
|
||||
desc.fragmentShader.source.assign(kFragmentShader, kFragmentShader + std::strlen(kFragmentShader));
|
||||
desc.fragmentShader.sourceLanguage = ShaderLanguage::GLSL;
|
||||
desc.fragmentShader.profile = L"fs";
|
||||
return desc;
|
||||
}
|
||||
|
||||
TEST_F(VulkanGraphicsFixture, DrawIndexedTexturedQuadWithFirstSetOffsetWritesExpectedPixels) {
|
||||
constexpr uint32_t kWidth = 64;
|
||||
constexpr uint32_t kHeight = 64;
|
||||
|
||||
TextureDesc colorDesc = CreateColorTextureDesc(kWidth, kHeight);
|
||||
RHITexture* colorTexture = m_device->CreateTexture(colorDesc);
|
||||
ASSERT_NE(colorTexture, nullptr);
|
||||
|
||||
RHIResourceView* renderTargetView = m_device->CreateRenderTargetView(colorTexture, {});
|
||||
ASSERT_NE(renderTargetView, nullptr);
|
||||
|
||||
BufferDesc vertexBufferDesc = {};
|
||||
vertexBufferDesc.size = sizeof(kQuadVertices);
|
||||
vertexBufferDesc.stride = sizeof(Vertex);
|
||||
vertexBufferDesc.bufferType = static_cast<uint32_t>(BufferType::Vertex);
|
||||
RHIBuffer* vertexBuffer = m_device->CreateBuffer(vertexBufferDesc);
|
||||
ASSERT_NE(vertexBuffer, nullptr);
|
||||
vertexBuffer->SetData(kQuadVertices, sizeof(kQuadVertices));
|
||||
vertexBuffer->SetStride(sizeof(Vertex));
|
||||
vertexBuffer->SetBufferType(BufferType::Vertex);
|
||||
|
||||
ResourceViewDesc vertexViewDesc = {};
|
||||
vertexViewDesc.dimension = ResourceViewDimension::Buffer;
|
||||
vertexViewDesc.structureByteStride = sizeof(Vertex);
|
||||
RHIResourceView* vertexView = m_device->CreateVertexBufferView(vertexBuffer, vertexViewDesc);
|
||||
ASSERT_NE(vertexView, nullptr);
|
||||
|
||||
BufferDesc indexBufferDesc = {};
|
||||
indexBufferDesc.size = sizeof(kQuadIndices);
|
||||
indexBufferDesc.stride = sizeof(uint32_t);
|
||||
indexBufferDesc.bufferType = static_cast<uint32_t>(BufferType::Index);
|
||||
RHIBuffer* indexBuffer = m_device->CreateBuffer(indexBufferDesc);
|
||||
ASSERT_NE(indexBuffer, nullptr);
|
||||
indexBuffer->SetData(kQuadIndices, sizeof(kQuadIndices));
|
||||
indexBuffer->SetStride(sizeof(uint32_t));
|
||||
indexBuffer->SetBufferType(BufferType::Index);
|
||||
|
||||
ResourceViewDesc indexViewDesc = {};
|
||||
indexViewDesc.dimension = ResourceViewDimension::Buffer;
|
||||
indexViewDesc.format = static_cast<uint32_t>(Format::R32_UInt);
|
||||
RHIResourceView* indexView = m_device->CreateIndexBufferView(indexBuffer, indexViewDesc);
|
||||
ASSERT_NE(indexView, nullptr);
|
||||
|
||||
const uint8_t texturePixel[] = {255, 0, 0, 255};
|
||||
TextureDesc textureDesc = CreateColorTextureDesc(1, 1);
|
||||
RHITexture* texture = m_device->CreateTexture(textureDesc, texturePixel, sizeof(texturePixel), 4);
|
||||
ASSERT_NE(texture, nullptr);
|
||||
|
||||
RHIResourceView* shaderResourceView = m_device->CreateShaderResourceView(texture, {});
|
||||
ASSERT_NE(shaderResourceView, nullptr);
|
||||
|
||||
SamplerDesc samplerDesc = {};
|
||||
samplerDesc.filter = static_cast<uint32_t>(FilterMode::Linear);
|
||||
samplerDesc.addressU = static_cast<uint32_t>(TextureAddressMode::Clamp);
|
||||
samplerDesc.addressV = static_cast<uint32_t>(TextureAddressMode::Clamp);
|
||||
samplerDesc.addressW = static_cast<uint32_t>(TextureAddressMode::Clamp);
|
||||
samplerDesc.comparisonFunc = static_cast<uint32_t>(ComparisonFunc::Always);
|
||||
samplerDesc.maxAnisotropy = 1;
|
||||
samplerDesc.minLod = 0.0f;
|
||||
samplerDesc.maxLod = 1000.0f;
|
||||
RHISampler* sampler = m_device->CreateSampler(samplerDesc);
|
||||
ASSERT_NE(sampler, nullptr);
|
||||
|
||||
DescriptorPoolDesc texturePoolDesc = {};
|
||||
texturePoolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
|
||||
texturePoolDesc.descriptorCount = 1;
|
||||
texturePoolDesc.shaderVisible = true;
|
||||
RHIDescriptorPool* texturePool = m_device->CreateDescriptorPool(texturePoolDesc);
|
||||
ASSERT_NE(texturePool, nullptr);
|
||||
|
||||
DescriptorSetLayoutBinding textureBinding = {};
|
||||
textureBinding.binding = 0;
|
||||
textureBinding.type = static_cast<uint32_t>(DescriptorType::SRV);
|
||||
textureBinding.count = 1;
|
||||
textureBinding.visibility = static_cast<uint32_t>(ShaderVisibility::Pixel);
|
||||
|
||||
DescriptorSetLayoutDesc textureLayoutDesc = {};
|
||||
textureLayoutDesc.bindings = &textureBinding;
|
||||
textureLayoutDesc.bindingCount = 1;
|
||||
|
||||
RHIDescriptorSet* textureSet = texturePool->AllocateSet(textureLayoutDesc);
|
||||
ASSERT_NE(textureSet, nullptr);
|
||||
textureSet->Update(0, shaderResourceView);
|
||||
|
||||
DescriptorPoolDesc samplerPoolDesc = {};
|
||||
samplerPoolDesc.type = DescriptorHeapType::Sampler;
|
||||
samplerPoolDesc.descriptorCount = 1;
|
||||
samplerPoolDesc.shaderVisible = true;
|
||||
RHIDescriptorPool* samplerPool = m_device->CreateDescriptorPool(samplerPoolDesc);
|
||||
ASSERT_NE(samplerPool, nullptr);
|
||||
|
||||
DescriptorSetLayoutBinding samplerBinding = {};
|
||||
samplerBinding.binding = 0;
|
||||
samplerBinding.type = static_cast<uint32_t>(DescriptorType::Sampler);
|
||||
samplerBinding.count = 1;
|
||||
samplerBinding.visibility = static_cast<uint32_t>(ShaderVisibility::Pixel);
|
||||
|
||||
DescriptorSetLayoutDesc samplerLayoutDesc = {};
|
||||
samplerLayoutDesc.bindings = &samplerBinding;
|
||||
samplerLayoutDesc.bindingCount = 1;
|
||||
|
||||
RHIDescriptorSet* samplerSet = samplerPool->AllocateSet(samplerLayoutDesc);
|
||||
ASSERT_NE(samplerSet, nullptr);
|
||||
samplerSet->UpdateSampler(0, sampler);
|
||||
|
||||
DescriptorSetLayoutBinding reservedBinding = {};
|
||||
reservedBinding.binding = 0;
|
||||
reservedBinding.type = static_cast<uint32_t>(DescriptorType::CBV);
|
||||
reservedBinding.count = 1;
|
||||
reservedBinding.visibility = static_cast<uint32_t>(ShaderVisibility::Vertex);
|
||||
|
||||
DescriptorSetLayoutDesc reservedLayoutDesc = {};
|
||||
reservedLayoutDesc.bindings = &reservedBinding;
|
||||
reservedLayoutDesc.bindingCount = 1;
|
||||
|
||||
DescriptorSetLayoutDesc setLayouts[3] = {};
|
||||
setLayouts[0] = reservedLayoutDesc;
|
||||
setLayouts[1] = textureLayoutDesc;
|
||||
setLayouts[2] = samplerLayoutDesc;
|
||||
|
||||
RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
||||
pipelineLayoutDesc.setLayouts = setLayouts;
|
||||
pipelineLayoutDesc.setLayoutCount = 3;
|
||||
RHIPipelineLayout* pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
||||
ASSERT_NE(pipelineLayout, nullptr);
|
||||
|
||||
RHIPipelineState* pipelineState = m_device->CreatePipelineState(CreatePipelineDesc(pipelineLayout));
|
||||
ASSERT_NE(pipelineState, nullptr);
|
||||
ASSERT_TRUE(pipelineState->IsValid());
|
||||
|
||||
RHICommandList* commandList = CreateCommandList();
|
||||
ASSERT_NE(commandList, nullptr);
|
||||
|
||||
commandList->Reset();
|
||||
commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
||||
commandList->SetViewport({0.0f, 0.0f, static_cast<float>(kWidth), static_cast<float>(kHeight), 0.0f, 1.0f});
|
||||
commandList->SetScissorRect({0, 0, static_cast<int32_t>(kWidth), static_cast<int32_t>(kHeight)});
|
||||
commandList->Clear(0.0f, 0.0f, 1.0f, 1.0f, 1);
|
||||
commandList->SetPipelineState(pipelineState);
|
||||
|
||||
RHIDescriptorSet* descriptorSets[] = {textureSet, samplerSet};
|
||||
commandList->SetGraphicsDescriptorSets(1, 2, descriptorSets, pipelineLayout);
|
||||
commandList->SetPrimitiveTopology(PrimitiveTopology::TriangleList);
|
||||
|
||||
RHIResourceView* vertexViews[] = {vertexView};
|
||||
uint64_t offsets[] = {0};
|
||||
uint32_t strides[] = {sizeof(Vertex)};
|
||||
commandList->SetVertexBuffers(0, 1, vertexViews, offsets, strides);
|
||||
commandList->SetIndexBuffer(indexView, 0);
|
||||
commandList->DrawIndexed(static_cast<uint32_t>(sizeof(kQuadIndices) / sizeof(kQuadIndices[0])));
|
||||
SubmitAndWait(commandList);
|
||||
|
||||
const std::vector<uint8_t> pixels = ReadTextureRgba8(static_cast<VulkanTexture*>(colorTexture));
|
||||
ASSERT_EQ(pixels.size(), static_cast<size_t>(kWidth * kHeight * 4));
|
||||
|
||||
auto PixelAt = [&](uint32_t x, uint32_t y) -> const uint8_t* {
|
||||
return pixels.data() + (static_cast<size_t>(y) * kWidth + x) * 4;
|
||||
};
|
||||
|
||||
const uint8_t* centerPixel = PixelAt(kWidth / 2, kHeight / 2);
|
||||
EXPECT_EQ(centerPixel[0], 255u);
|
||||
EXPECT_EQ(centerPixel[1], 0u);
|
||||
EXPECT_EQ(centerPixel[2], 0u);
|
||||
EXPECT_EQ(centerPixel[3], 255u);
|
||||
|
||||
const uint8_t* cornerPixel = PixelAt(2, 2);
|
||||
EXPECT_EQ(cornerPixel[0], 0u);
|
||||
EXPECT_EQ(cornerPixel[1], 0u);
|
||||
EXPECT_EQ(cornerPixel[2], 255u);
|
||||
EXPECT_EQ(cornerPixel[3], 255u);
|
||||
|
||||
commandList->Shutdown();
|
||||
delete commandList;
|
||||
pipelineState->Shutdown();
|
||||
delete pipelineState;
|
||||
pipelineLayout->Shutdown();
|
||||
delete pipelineLayout;
|
||||
textureSet->Shutdown();
|
||||
delete textureSet;
|
||||
samplerSet->Shutdown();
|
||||
delete samplerSet;
|
||||
texturePool->Shutdown();
|
||||
delete texturePool;
|
||||
samplerPool->Shutdown();
|
||||
delete samplerPool;
|
||||
sampler->Shutdown();
|
||||
delete sampler;
|
||||
shaderResourceView->Shutdown();
|
||||
delete shaderResourceView;
|
||||
texture->Shutdown();
|
||||
delete texture;
|
||||
indexView->Shutdown();
|
||||
delete indexView;
|
||||
vertexView->Shutdown();
|
||||
delete vertexView;
|
||||
indexBuffer->Shutdown();
|
||||
delete indexBuffer;
|
||||
vertexBuffer->Shutdown();
|
||||
delete vertexBuffer;
|
||||
renderTargetView->Shutdown();
|
||||
delete renderTargetView;
|
||||
colorTexture->Shutdown();
|
||||
delete colorTexture;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user