Add backpack RHI integration test

This commit is contained in:
2026-03-26 16:57:54 +08:00
parent 6244b586bb
commit 122495e581
16 changed files with 21504 additions and 10 deletions

View File

@@ -138,6 +138,7 @@ inline DXGI_FORMAT ToD3D12(Format format) {
case Format::R8_UNorm: return DXGI_FORMAT_R8_UNORM;
case Format::R8G8_UNorm: return DXGI_FORMAT_R8G8_UNORM;
case Format::R8G8B8A8_UNorm: return DXGI_FORMAT_R8G8B8A8_UNORM;
case Format::R16_UInt: return DXGI_FORMAT_R16_UINT;
case Format::R16G16B16A16_Float: return DXGI_FORMAT_R16G16B16A16_FLOAT;
case Format::R32G32B32A32_Float: return DXGI_FORMAT_R32G32B32A32_FLOAT;
case Format::R16_Float: return DXGI_FORMAT_R16_FLOAT;
@@ -165,6 +166,7 @@ inline Format FromD3D12(DXGI_FORMAT format) {
case DXGI_FORMAT_R8_UNORM: return Format::R8_UNorm;
case DXGI_FORMAT_R8G8_UNORM: return Format::R8G8_UNorm;
case DXGI_FORMAT_R8G8B8A8_UNORM: return Format::R8G8B8A8_UNorm;
case DXGI_FORMAT_R16_UINT: return Format::R16_UInt;
case DXGI_FORMAT_R16G16B16A16_FLOAT: return Format::R16G16B16A16_Float;
case DXGI_FORMAT_R32G32B32A32_FLOAT: return Format::R32G32B32A32_Float;
case DXGI_FORMAT_R16_FLOAT: return Format::R16_Float;

View File

@@ -206,6 +206,8 @@ private:
unsigned int m_currentVAO;
unsigned int m_currentProgram;
unsigned int m_internalVAO;
unsigned int m_currentIndexType;
uint64_t m_currentIndexOffset;
OpenGLPipelineState* m_currentPipelineState;
std::vector<unsigned int> m_enabledVertexAttributes;
OpenGLShader* m_currentShader;

View File

@@ -299,6 +299,7 @@ enum class Format : uint32_t {
R8_UNorm,
R8G8_UNorm,
R8G8B8A8_UNorm,
R16_UInt,
R16G16B16A16_Float,
R32G32B32A32_Float,
R16_Float,

View File

@@ -58,11 +58,35 @@ bool GetOpenGLVertexAttribFormat(Format format, OpenGLVertexAttribFormat& attrib
case Format::R32G32B32A32_UInt:
attributeFormat = { 4, GL_UNSIGNED_INT, GL_FALSE, true };
return true;
case Format::R16_UInt:
attributeFormat = { 1, GL_UNSIGNED_SHORT, GL_FALSE, true };
return true;
default:
return false;
}
}
GLenum ResolveOpenGLIndexType(Format format) {
switch (format) {
case Format::R16_UInt:
return GL_UNSIGNED_SHORT;
case Format::R32_UInt:
case Format::Unknown:
default:
return GL_UNSIGNED_INT;
}
}
uint64_t GetIndexTypeSize(GLenum indexType) {
switch (indexType) {
case GL_UNSIGNED_SHORT:
return sizeof(GLushort);
case GL_UNSIGNED_INT:
default:
return sizeof(GLuint);
}
}
} // namespace
OpenGLCommandList::OpenGLCommandList()
@@ -70,6 +94,8 @@ OpenGLCommandList::OpenGLCommandList()
, m_currentVAO(0)
, m_currentProgram(0)
, m_internalVAO(0)
, m_currentIndexType(GL_UNSIGNED_INT)
, m_currentIndexOffset(0)
, m_currentPipelineState(nullptr)
, m_currentShader(nullptr)
, m_composedFramebuffer(nullptr) {
@@ -120,12 +146,13 @@ void OpenGLCommandList::SetVertexBuffers(unsigned int startSlot, unsigned int co
}
void OpenGLCommandList::SetIndexBuffer(unsigned int buffer, unsigned int type) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
SetIndexBuffer(buffer, type, 0);
}
void OpenGLCommandList::SetIndexBuffer(unsigned int buffer, unsigned int type, size_t offset) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
(void)offset;
m_currentIndexType = type;
m_currentIndexOffset = static_cast<uint64_t>(offset);
}
void OpenGLCommandList::BindVertexArray(unsigned int vao) {
@@ -137,7 +164,8 @@ void OpenGLCommandList::BindVertexArray(unsigned int vao, unsigned int indexBuff
m_currentVAO = vao;
glBindVertexArray(vao);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
(void)indexType;
m_currentIndexType = indexType;
m_currentIndexOffset = 0;
}
void OpenGLCommandList::UseShader(unsigned int program) {
@@ -261,13 +289,22 @@ void OpenGLCommandList::DrawInstanced(PrimitiveType type, unsigned int vertexCou
void OpenGLCommandList::DrawIndexed(PrimitiveType type, unsigned int indexCount, unsigned int startIndex, int baseVertex) {
m_primitiveType = ToOpenGL(type);
glDrawElements(m_primitiveType, indexCount, GL_UNSIGNED_INT, (void*)(startIndex * sizeof(unsigned int)));
const uint64_t indexOffset = m_currentIndexOffset + static_cast<uint64_t>(startIndex) * GetIndexTypeSize(m_currentIndexType);
glDrawElements(m_primitiveType,
indexCount,
m_currentIndexType,
reinterpret_cast<const void*>(static_cast<uintptr_t>(indexOffset)));
(void)baseVertex;
}
void OpenGLCommandList::DrawIndexedInstanced(PrimitiveType type, unsigned int indexCount, unsigned int instanceCount, unsigned int startIndex, int baseVertex, unsigned int startInstance) {
m_primitiveType = ToOpenGL(type);
glDrawElementsInstanced(m_primitiveType, indexCount, GL_UNSIGNED_INT, (void*)(startIndex * sizeof(unsigned int)), instanceCount);
const uint64_t indexOffset = m_currentIndexOffset + static_cast<uint64_t>(startIndex) * GetIndexTypeSize(m_currentIndexType);
glDrawElementsInstanced(m_primitiveType,
indexCount,
m_currentIndexType,
reinterpret_cast<const void*>(static_cast<uintptr_t>(indexOffset)),
instanceCount);
(void)baseVertex;
(void)startInstance;
}
@@ -284,7 +321,7 @@ void OpenGLCommandList::DrawIndirect(PrimitiveType type, unsigned int buffer, si
void OpenGLCommandList::DrawIndexedIndirect(PrimitiveType type, unsigned int buffer, size_t offset, unsigned int drawCount, unsigned int stride) {
m_primitiveType = ToOpenGL(type);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, buffer);
glDrawElementsIndirect(m_primitiveType, GL_UNSIGNED_INT, (void*)offset);
glDrawElementsIndirect(m_primitiveType, m_currentIndexType, (void*)offset);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
(void)drawCount;
(void)stride;
@@ -477,6 +514,8 @@ void OpenGLCommandList::Shutdown() {
void OpenGLCommandList::Reset() {
ReleaseComposedFramebuffer();
m_currentPipelineState = nullptr;
m_currentIndexOffset = 0;
m_currentIndexType = GL_UNSIGNED_INT;
}
void OpenGLCommandList::Close() {
@@ -827,7 +866,8 @@ void OpenGLCommandList::SetIndexBuffer(RHIResourceView* buffer, uint64_t offset)
GLuint glBuffer = view->GetBuffer();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glBuffer);
(void)offset;
m_currentIndexType = ResolveOpenGLIndexType(view->GetFormat());
m_currentIndexOffset = view->GetBufferOffset() + offset;
}
void OpenGLCommandList::CopyResource(RHIResourceView* dst, RHIResourceView* src) {
@@ -853,12 +893,22 @@ void OpenGLCommandList::CopyResource(RHIResourceView* dst, RHIResourceView* src)
}
void OpenGLCommandList::Draw(uint32_t vertexCount, uint32_t instanceCount, uint32_t startVertex, uint32_t startInstance) {
glDrawArraysInstanced(GL_TRIANGLES, static_cast<GLint>(startVertex), static_cast<GLsizei>(vertexCount), static_cast<GLsizei>(instanceCount));
glDrawArraysInstanced(m_primitiveType,
static_cast<GLint>(startVertex),
static_cast<GLsizei>(vertexCount),
static_cast<GLsizei>(instanceCount));
(void)startInstance;
}
void OpenGLCommandList::DrawIndexed(uint32_t indexCount, uint32_t instanceCount, uint32_t startIndex, int32_t baseVertex, uint32_t startInstance) {
glDrawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(indexCount), GL_UNSIGNED_INT,
reinterpret_cast<const void*>(startIndex * sizeof(GLuint)), static_cast<GLsizei>(instanceCount));
const uint64_t indexOffset = m_currentIndexOffset + static_cast<uint64_t>(startIndex) * GetIndexTypeSize(m_currentIndexType);
glDrawElementsInstanced(m_primitiveType,
static_cast<GLsizei>(indexCount),
m_currentIndexType,
reinterpret_cast<const void*>(static_cast<uintptr_t>(indexOffset)),
static_cast<GLsizei>(instanceCount));
(void)baseVertex;
(void)startInstance;
}
void OpenGLCommandList::SetPrimitiveTopology(PrimitiveTopology topology) {

View File

@@ -8,3 +8,4 @@ add_subdirectory(minimal)
add_subdirectory(triangle)
add_subdirectory(quad)
add_subdirectory(sphere)
add_subdirectory(backpack)

View File

@@ -0,0 +1,62 @@
cmake_minimum_required(VERSION 3.15)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
project(rhi_integration_backpack)
set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine)
set(PACKAGE_DIR ${CMAKE_SOURCE_DIR}/tests/opengl/package)
get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE)
add_executable(rhi_integration_backpack
main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../fixtures/RHIIntegrationFixture.cpp
${PACKAGE_DIR}/src/glad.c
)
target_include_directories(rhi_integration_backpack PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/../fixtures
${ENGINE_ROOT_DIR}/include
${PACKAGE_DIR}/include
${PROJECT_ROOT_DIR}/engine/src
)
target_link_libraries(rhi_integration_backpack PRIVATE
d3d12
dxgi
d3dcompiler
winmm
opengl32
XCEngine
GTest::gtest
)
target_compile_definitions(rhi_integration_backpack PRIVATE
UNICODE
_UNICODE
XCENGINE_SUPPORT_OPENGL
XCENGINE_SUPPORT_D3D12
)
add_custom_command(TARGET rhi_integration_backpack POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/Res
$<TARGET_FILE_DIR:rhi_integration_backpack>/Res
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/tests/RHI/integration/compare_ppm.py
$<TARGET_FILE_DIR:rhi_integration_backpack>/
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm
$<TARGET_FILE_DIR:rhi_integration_backpack>/GT.ppm
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${ENGINE_ROOT_DIR}/third_party/renderdoc/renderdoc.dll
$<TARGET_FILE_DIR:rhi_integration_backpack>/
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${ENGINE_ROOT_DIR}/third_party/assimp/bin/assimp-vc143-mt.dll
$<TARGET_FILE_DIR:rhi_integration_backpack>/
)
include(GoogleTest)
gtest_discover_tests(rhi_integration_backpack)

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -0,0 +1,16 @@
# Blender MTL File: 'None'
# Material Count: 1
newmtl Scene_-_Root
Ns 225.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.0 0.0 0.0
Ni 1.450000
d 1.000000
illum 2
map_Kd diffuse.jpg
map_Bump normal.png
map_Ks specular.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

View File

@@ -0,0 +1,3 @@
Model by Berk Gedik, from: https://sketchfab.com/3d-models/survival-guitar-backpack-low-poly-799f8c4511f84fab8c3f12887f7e6b36
Modified material assignment (Joey de Vries) for easier load in OpenGL model loading chapter, and renamed albedo to diffuse and metallic to specular to match non-PBR lighting setup.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

View File

@@ -0,0 +1,983 @@
#define NOMINMAX
#include <windows.h>
#include <algorithm>
#include <filesystem>
#include <math.h>
#include <memory>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <vector>
#include <gtest/gtest.h>
#include "../fixtures/RHIIntegrationFixture.h"
#include "XCEngine/Core/Containers/String.h"
#include "XCEngine/Core/Math/Matrix4.h"
#include "XCEngine/Core/Math/Vector3.h"
#include "XCEngine/Core/Math/Vector4.h"
#include "XCEngine/Debug/ConsoleLogSink.h"
#include "XCEngine/Debug/Logger.h"
#include "XCEngine/Resources/Material/Material.h"
#include "XCEngine/Resources/Mesh/Mesh.h"
#include "XCEngine/Resources/Mesh/MeshImportSettings.h"
#include "XCEngine/Resources/Mesh/MeshLoader.h"
#include "XCEngine/Resources/Texture/Texture.h"
#include "XCEngine/RHI/RHIBuffer.h"
#include "XCEngine/RHI/RHICommandList.h"
#include "XCEngine/RHI/RHICommandQueue.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/RHISwapChain.h"
#include "XCEngine/RHI/RHITexture.h"
using namespace XCEngine::Containers;
using namespace XCEngine::Debug;
using namespace XCEngine::Math;
using namespace XCEngine::Resources;
using namespace XCEngine::RHI;
using namespace XCEngine::RHI::Integration;
namespace {
struct BackpackVertex {
float position[4];
float normal[4];
float uv[2];
};
struct SceneConstants {
Matrix4x4 projection;
Matrix4x4 view;
Matrix4x4 model;
Vector4 cameraPosition;
Vector4 lightDirection;
Vector4 lightColor;
Vector4 ambientColor;
};
struct MaterialResources {
RHITexture* baseColorTexture = nullptr;
RHIResourceView* baseColorView = nullptr;
RHITexture* specularTexture = nullptr;
RHIResourceView* specularView = nullptr;
RHIDescriptorPool* texturePool = nullptr;
RHIDescriptorSet* textureSet = nullptr;
};
constexpr uint32_t kFrameWidth = 1280;
constexpr uint32_t kFrameHeight = 720;
constexpr uint32_t kTextureBindingCount = 2;
constexpr uint32_t kSamplerBindingCount = 2;
constexpr uint32_t kDescriptorSetCount = 3;
constexpr float kPi = 3.14159265358979323846f;
std::filesystem::path GetExecutableDirectory() {
char exePath[MAX_PATH] = {};
const DWORD length = GetModuleFileNameA(nullptr, exePath, MAX_PATH);
if (length == 0 || length >= MAX_PATH) {
return std::filesystem::current_path();
}
return std::filesystem::path(exePath).parent_path();
}
std::filesystem::path ResolveRuntimePath(const char* relativePath) {
return GetExecutableDirectory() / relativePath;
}
const char* GetScreenshotFilename(RHIType type) {
return type == RHIType::D3D12 ? "backpack_d3d12.ppm" : "backpack_opengl.ppm";
}
int GetComparisonThreshold(RHIType type) {
return type == RHIType::OpenGL ? 8 : 8;
}
Format ToRHITextureFormat(TextureFormat format) {
switch (format) {
case TextureFormat::RGBA8_UNORM:
return Format::R8G8B8A8_UNorm;
default:
return Format::Unknown;
}
}
RHITexture* CreateSolidColorTexture(RHIDevice* device, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
const uint8_t pixels[] = { r, g, b, a };
TextureDesc textureDesc = {};
textureDesc.width = 1;
textureDesc.height = 1;
textureDesc.depth = 1;
textureDesc.mipLevels = 1;
textureDesc.arraySize = 1;
textureDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
textureDesc.textureType = static_cast<uint32_t>(XCEngine::RHI::TextureType::Texture2D);
textureDesc.sampleCount = 1;
textureDesc.sampleQuality = 0;
textureDesc.flags = 0;
return device->CreateTexture(textureDesc, pixels, sizeof(pixels), 4);
}
bool CreateTextureViewFromResourceTexture(RHIDevice* device,
Texture* resourceTexture,
RHITexture*& outTexture,
RHIResourceView*& outView) {
outTexture = nullptr;
outView = nullptr;
if (device == nullptr || resourceTexture == nullptr) {
return false;
}
const Format rhiFormat = ToRHITextureFormat(resourceTexture->GetFormat());
if (rhiFormat == Format::Unknown || resourceTexture->GetPixelData() == nullptr) {
return false;
}
TextureDesc textureDesc = {};
textureDesc.width = resourceTexture->GetWidth();
textureDesc.height = resourceTexture->GetHeight();
textureDesc.depth = resourceTexture->GetDepth();
textureDesc.mipLevels = resourceTexture->GetMipLevels();
textureDesc.arraySize = resourceTexture->GetArraySize();
textureDesc.format = static_cast<uint32_t>(rhiFormat);
textureDesc.textureType = static_cast<uint32_t>(XCEngine::RHI::TextureType::Texture2D);
textureDesc.sampleCount = 1;
textureDesc.sampleQuality = 0;
textureDesc.flags = 0;
outTexture = device->CreateTexture(
textureDesc,
resourceTexture->GetPixelData(),
resourceTexture->GetPixelDataSize(),
resourceTexture->GetWidth() * 4u);
if (outTexture == nullptr) {
return false;
}
ResourceViewDesc textureViewDesc = {};
textureViewDesc.format = static_cast<uint32_t>(rhiFormat);
textureViewDesc.dimension = ResourceViewDimension::Texture2D;
textureViewDesc.mipLevel = 0;
outView = device->CreateShaderResourceView(outTexture, textureViewDesc);
if (outView == nullptr) {
outTexture->Shutdown();
delete outTexture;
outTexture = nullptr;
return false;
}
return true;
}
SceneConstants CreateSceneConstants(const Mesh& mesh) {
const Vector3 min = mesh.GetBounds().GetMin();
const Vector3 max = mesh.GetBounds().GetMax();
const Vector3 center(
(min.x + max.x) * 0.5f,
(min.y + max.y) * 0.5f,
(min.z + max.z) * 0.5f);
const float sizeX = std::max(max.x - min.x, 0.001f);
const float sizeY = std::max(max.y - min.y, 0.001f);
const float sizeZ = std::max(max.z - min.z, 0.001f);
const float maxExtent = std::max(sizeX, std::max(sizeY, sizeZ));
const float uniformScale = 1.8f / maxExtent;
const Matrix4x4 centerTranslation =
Matrix4x4::Translation(Vector3(-center.x, -center.y, -center.z));
const Matrix4x4 rotation =
Matrix4x4::RotationY(-0.35f) * Matrix4x4::RotationX(-0.18f);
const Matrix4x4 scale =
Matrix4x4::Scale(Vector3(uniformScale, uniformScale, uniformScale));
const Matrix4x4 world =
Matrix4x4::Translation(Vector3(0.0f, -0.25f, 3.0f)) *
rotation *
scale *
centerTranslation;
SceneConstants constants = {};
constants.projection =
Matrix4x4::Perspective(45.0f * kPi / 180.0f,
static_cast<float>(kFrameWidth) / static_cast<float>(kFrameHeight),
0.1f,
100.0f)
.Transpose();
constants.view = Matrix4x4::Identity().Transpose();
constants.model = world.Transpose();
constants.cameraPosition = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
constants.lightDirection = Vector4(-0.30f, 0.85f, -0.40f, 0.0f);
constants.lightColor = Vector4(0.95f, 0.95f, 0.95f, 1.0f);
constants.ambientColor = Vector4(0.22f, 0.22f, 0.22f, 1.0f);
return constants;
}
GraphicsPipelineDesc CreateBackpackPipelineDesc(RHIType type, 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::D24_UNorm_S8_UInt);
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 = true;
desc.depthStencilState.depthWriteEnable = true;
desc.depthStencilState.depthFunc = static_cast<uint32_t>(ComparisonFunc::Less);
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 normal = {};
normal.semanticName = "NORMAL";
normal.semanticIndex = 0;
normal.format = static_cast<uint32_t>(Format::R32G32B32A32_Float);
normal.inputSlot = 0;
normal.alignedByteOffset = sizeof(float) * 4;
desc.inputLayout.elements.push_back(normal);
InputElementDesc texcoord = {};
texcoord.semanticName = "TEXCOORD";
texcoord.semanticIndex = 0;
texcoord.format = static_cast<uint32_t>(Format::R32G32_Float);
texcoord.inputSlot = 0;
texcoord.alignedByteOffset = sizeof(float) * 8;
desc.inputLayout.elements.push_back(texcoord);
const char kBackpackHlsl[] = R"(
Texture2D gBaseColorTexture : register(t0);
Texture2D gSpecularTexture : register(t1);
SamplerState gBaseColorSampler : register(s0);
SamplerState gSpecularSampler : register(s1);
cbuffer SceneData : register(b0) {
float4x4 gProjectionMatrix;
float4x4 gViewMatrix;
float4x4 gModelMatrix;
float4 gCameraPosition;
float4 gLightDirection;
float4 gLightColor;
float4 gAmbientColor;
};
struct VSInput {
float4 position : POSITION;
float4 normal : NORMAL;
float2 texcoord : TEXCOORD0;
};
struct PSInput {
float4 position : SV_POSITION;
float3 worldPosition : TEXCOORD0;
float3 worldNormal : TEXCOORD1;
float2 texcoord : TEXCOORD2;
};
PSInput MainVS(VSInput input) {
PSInput output;
float4 worldPosition = mul(gModelMatrix, input.position);
float4 viewPosition = mul(gViewMatrix, worldPosition);
output.position = mul(gProjectionMatrix, viewPosition);
output.worldPosition = worldPosition.xyz;
output.worldNormal = mul((float3x3)gModelMatrix, input.normal.xyz);
output.texcoord = input.texcoord;
return output;
}
float4 MainPS(PSInput input, bool isFrontFace : SV_IsFrontFace) : SV_TARGET {
float3 normal = normalize(isFrontFace ? input.worldNormal : -input.worldNormal);
float3 lightDir = normalize(gLightDirection.xyz);
float3 viewDir = normalize(gCameraPosition.xyz - input.worldPosition);
float3 halfDir = normalize(lightDir + viewDir);
float3 baseColor = gBaseColorTexture.Sample(gBaseColorSampler, input.texcoord).rgb;
float specularMask = gSpecularTexture.Sample(gSpecularSampler, input.texcoord).r;
float diffuse = saturate(dot(normal, lightDir));
float specular = pow(saturate(dot(normal, halfDir)), 32.0f) * specularMask;
float3 color = baseColor * (gAmbientColor.rgb + gLightColor.rgb * diffuse);
color += gLightColor.rgb * (specular * 0.35f);
return float4(saturate(color), 1.0f);
}
)";
const char kBackpackVertexShader[] = R"(#version 430
layout(location = 0) in vec4 aPosition;
layout(location = 1) in vec4 aNormal;
layout(location = 2) in vec2 aTexCoord;
layout(std140, binding = 0) uniform SceneData {
mat4 gProjectionMatrix;
mat4 gViewMatrix;
mat4 gModelMatrix;
vec4 gCameraPosition;
vec4 gLightDirection;
vec4 gLightColor;
vec4 gAmbientColor;
};
out vec3 vWorldPosition;
out vec3 vWorldNormal;
out vec2 vTexCoord;
void main() {
vec4 worldPosition = gModelMatrix * aPosition;
vec4 viewPosition = gViewMatrix * worldPosition;
gl_Position = gProjectionMatrix * viewPosition;
vWorldPosition = worldPosition.xyz;
vWorldNormal = mat3(gModelMatrix) * aNormal.xyz;
vTexCoord = aTexCoord;
}
)";
const char kBackpackFragmentShader[] = R"(#version 430
layout(std140, binding = 0) uniform SceneData {
mat4 gProjectionMatrix;
mat4 gViewMatrix;
mat4 gModelMatrix;
vec4 gCameraPosition;
vec4 gLightDirection;
vec4 gLightColor;
vec4 gAmbientColor;
};
layout(binding = 0) uniform sampler2D uBaseColorTexture;
layout(binding = 1) uniform sampler2D uSpecularTexture;
in vec3 vWorldPosition;
in vec3 vWorldNormal;
in vec2 vTexCoord;
layout(location = 0) out vec4 fragColor;
void main() {
vec3 normal = normalize(gl_FrontFacing ? vWorldNormal : -vWorldNormal);
vec3 lightDir = normalize(gLightDirection.xyz);
vec3 viewDir = normalize(gCameraPosition.xyz - vWorldPosition);
vec3 halfDir = normalize(lightDir + viewDir);
vec3 baseColor = texture(uBaseColorTexture, vTexCoord).rgb;
float specularMask = texture(uSpecularTexture, vTexCoord).r;
float diffuse = max(dot(normal, lightDir), 0.0);
float specular = pow(max(dot(normal, halfDir), 0.0), 32.0) * specularMask;
vec3 color = baseColor * (gAmbientColor.rgb + gLightColor.rgb * diffuse);
color += gLightColor.rgb * (specular * 0.35);
fragColor = vec4(clamp(color, 0.0, 1.0), 1.0);
}
)";
if (type == RHIType::D3D12) {
desc.vertexShader.source.assign(kBackpackHlsl, kBackpackHlsl + strlen(kBackpackHlsl));
desc.vertexShader.sourceLanguage = XCEngine::RHI::ShaderLanguage::HLSL;
desc.vertexShader.entryPoint = L"MainVS";
desc.vertexShader.profile = L"vs_5_0";
desc.fragmentShader.source.assign(kBackpackHlsl, kBackpackHlsl + strlen(kBackpackHlsl));
desc.fragmentShader.sourceLanguage = XCEngine::RHI::ShaderLanguage::HLSL;
desc.fragmentShader.entryPoint = L"MainPS";
desc.fragmentShader.profile = L"ps_5_0";
} else {
desc.vertexShader.source.assign(kBackpackVertexShader,
kBackpackVertexShader + strlen(kBackpackVertexShader));
desc.vertexShader.sourceLanguage = XCEngine::RHI::ShaderLanguage::GLSL;
desc.vertexShader.profile = L"vs_4_30";
desc.fragmentShader.source.assign(kBackpackFragmentShader,
kBackpackFragmentShader + strlen(kBackpackFragmentShader));
desc.fragmentShader.sourceLanguage = XCEngine::RHI::ShaderLanguage::GLSL;
desc.fragmentShader.profile = L"fs_4_30";
}
return desc;
}
class BackpackTest : public RHIIntegrationFixture {
protected:
void SetUp() override;
void TearDown() override;
void RenderFrame() override;
private:
Texture* GetMaterialTexture(Material* material, const char* propertyName) const;
void InitializeBackpackResources();
void ShutdownBackpackResources();
void LoadBackpackMesh();
void InitializeGeometryResources();
void InitializeFallbackTextures();
void InitializeMaterialResources();
Mesh* mMesh = nullptr;
std::vector<BackpackVertex> mVertices;
RHIBuffer* mVertexBuffer = nullptr;
RHIResourceView* mVertexBufferView = nullptr;
RHIBuffer* mIndexBuffer = nullptr;
RHIResourceView* mIndexBufferView = nullptr;
RHITexture* mFallbackBaseColorTexture = nullptr;
RHIResourceView* mFallbackBaseColorView = nullptr;
RHITexture* mFallbackSpecularTexture = nullptr;
RHIResourceView* mFallbackSpecularView = nullptr;
RHISampler* mSampler = nullptr;
RHIDescriptorPool* mConstantPool = nullptr;
RHIDescriptorSet* mConstantSet = nullptr;
RHIDescriptorPool* mSamplerPool = nullptr;
RHIDescriptorSet* mSamplerSet = nullptr;
std::vector<MaterialResources> mMaterialResources;
RHIPipelineLayout* mPipelineLayout = nullptr;
RHIPipelineState* mPipelineState = nullptr;
};
void BackpackTest::SetUp() {
RHIIntegrationFixture::SetUp();
InitializeBackpackResources();
}
void BackpackTest::TearDown() {
ShutdownBackpackResources();
RHIIntegrationFixture::TearDown();
}
Texture* BackpackTest::GetMaterialTexture(Material* material, const char* propertyName) const {
if (material == nullptr) {
return nullptr;
}
const ResourceHandle<Texture> textureHandle = material->GetTexture(String(propertyName));
return textureHandle.IsValid() ? textureHandle.Get() : nullptr;
}
void BackpackTest::LoadBackpackMesh() {
const std::filesystem::path meshPath = ResolveRuntimePath("Res/models/backpack/backpack.obj");
const std::string meshPathString = meshPath.string();
MeshLoader loader;
MeshImportSettings settings;
settings.AddImportFlag(MeshImportFlags::FlipUVs);
LoadResult result = loader.Load(meshPathString.c_str(), &settings);
ASSERT_TRUE(result);
ASSERT_NE(result.resource, nullptr);
mMesh = static_cast<Mesh*>(result.resource);
ASSERT_NE(mMesh, nullptr);
ASSERT_GT(mMesh->GetVertexCount(), 0u);
ASSERT_GT(mMesh->GetIndexCount(), 0u);
ASSERT_GT(mMesh->GetSections().Size(), 0u);
Log("[TEST] Backpack mesh loaded: vertices=%u indices=%u sections=%u materials=%u textures=%u use32Bit=%d",
mMesh->GetVertexCount(),
mMesh->GetIndexCount(),
static_cast<uint32_t>(mMesh->GetSections().Size()),
static_cast<uint32_t>(mMesh->GetMaterials().Size()),
static_cast<uint32_t>(mMesh->GetTextures().Size()),
mMesh->IsUse32BitIndex() ? 1 : 0);
}
void BackpackTest::InitializeGeometryResources() {
ASSERT_NE(mMesh, nullptr);
ASSERT_EQ(mMesh->GetVertexStride(), sizeof(StaticMeshVertex));
const auto* sourceVertices = static_cast<const StaticMeshVertex*>(mMesh->GetVertexData());
ASSERT_NE(sourceVertices, nullptr);
mVertices.resize(mMesh->GetVertexCount());
for (uint32_t vertexIndex = 0; vertexIndex < mMesh->GetVertexCount(); ++vertexIndex) {
const StaticMeshVertex& source = sourceVertices[vertexIndex];
BackpackVertex& destination = mVertices[vertexIndex];
destination.position[0] = source.position.x;
destination.position[1] = source.position.y;
destination.position[2] = source.position.z;
destination.position[3] = 1.0f;
destination.normal[0] = source.normal.x;
destination.normal[1] = source.normal.y;
destination.normal[2] = source.normal.z;
destination.normal[3] = 0.0f;
destination.uv[0] = source.uv0.x;
destination.uv[1] = source.uv0.y;
}
BufferDesc vertexBufferDesc = {};
vertexBufferDesc.size = static_cast<uint64_t>(mVertices.size() * sizeof(BackpackVertex));
vertexBufferDesc.stride = sizeof(BackpackVertex);
vertexBufferDesc.bufferType = static_cast<uint32_t>(BufferType::Vertex);
mVertexBuffer = GetDevice()->CreateBuffer(vertexBufferDesc);
ASSERT_NE(mVertexBuffer, nullptr);
mVertexBuffer->SetData(mVertices.data(), mVertices.size() * sizeof(BackpackVertex));
mVertexBuffer->SetStride(sizeof(BackpackVertex));
mVertexBuffer->SetBufferType(BufferType::Vertex);
ResourceViewDesc vertexViewDesc = {};
vertexViewDesc.dimension = ResourceViewDimension::Buffer;
vertexViewDesc.structureByteStride = sizeof(BackpackVertex);
mVertexBufferView = GetDevice()->CreateVertexBufferView(mVertexBuffer, vertexViewDesc);
ASSERT_NE(mVertexBufferView, nullptr);
BufferDesc indexBufferDesc = {};
indexBufferDesc.size = static_cast<uint64_t>(mMesh->GetIndexDataSize());
indexBufferDesc.stride = mMesh->IsUse32BitIndex() ? sizeof(uint32_t) : sizeof(uint16_t);
indexBufferDesc.bufferType = static_cast<uint32_t>(BufferType::Index);
mIndexBuffer = GetDevice()->CreateBuffer(indexBufferDesc);
ASSERT_NE(mIndexBuffer, nullptr);
mIndexBuffer->SetData(mMesh->GetIndexData(), mMesh->GetIndexDataSize());
mIndexBuffer->SetStride(indexBufferDesc.stride);
mIndexBuffer->SetBufferType(BufferType::Index);
ResourceViewDesc indexViewDesc = {};
indexViewDesc.dimension = ResourceViewDimension::Buffer;
indexViewDesc.format = static_cast<uint32_t>(mMesh->IsUse32BitIndex() ? Format::R32_UInt : Format::R16_UInt);
mIndexBufferView = GetDevice()->CreateIndexBufferView(mIndexBuffer, indexViewDesc);
ASSERT_NE(mIndexBufferView, nullptr);
}
void BackpackTest::InitializeFallbackTextures() {
mFallbackBaseColorTexture = CreateSolidColorTexture(GetDevice(), 255, 255, 255, 255);
ASSERT_NE(mFallbackBaseColorTexture, nullptr);
ResourceViewDesc baseColorViewDesc = {};
baseColorViewDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
baseColorViewDesc.dimension = ResourceViewDimension::Texture2D;
baseColorViewDesc.mipLevel = 0;
mFallbackBaseColorView = GetDevice()->CreateShaderResourceView(mFallbackBaseColorTexture, baseColorViewDesc);
ASSERT_NE(mFallbackBaseColorView, nullptr);
mFallbackSpecularTexture = CreateSolidColorTexture(GetDevice(), 0, 0, 0, 255);
ASSERT_NE(mFallbackSpecularTexture, nullptr);
ResourceViewDesc specularViewDesc = {};
specularViewDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
specularViewDesc.dimension = ResourceViewDimension::Texture2D;
specularViewDesc.mipLevel = 0;
mFallbackSpecularView = GetDevice()->CreateShaderResourceView(mFallbackSpecularTexture, specularViewDesc);
ASSERT_NE(mFallbackSpecularView, nullptr);
}
void BackpackTest::InitializeMaterialResources() {
ASSERT_NE(mMesh, nullptr);
ASSERT_NE(mFallbackBaseColorView, nullptr);
ASSERT_NE(mFallbackSpecularView, nullptr);
DescriptorSetLayoutBinding textureBindings[kTextureBindingCount] = {};
textureBindings[0].binding = 0;
textureBindings[0].type = static_cast<uint32_t>(DescriptorType::SRV);
textureBindings[0].count = 1;
textureBindings[1].binding = 1;
textureBindings[1].type = static_cast<uint32_t>(DescriptorType::SRV);
textureBindings[1].count = 1;
DescriptorSetLayoutDesc textureLayoutDesc = {};
textureLayoutDesc.bindings = textureBindings;
textureLayoutDesc.bindingCount = kTextureBindingCount;
const uint32_t materialCount =
std::max(1u, static_cast<uint32_t>(mMesh->GetMaterials().Size()));
mMaterialResources.resize(materialCount);
for (uint32_t materialIndex = 0; materialIndex < materialCount; ++materialIndex) {
Material* material =
materialIndex < mMesh->GetMaterials().Size()
? mMesh->GetMaterials()[materialIndex]
: nullptr;
MaterialResources& resources = mMaterialResources[materialIndex];
Texture* baseColorTexture = GetMaterialTexture(material, "baseColorTexture");
if (baseColorTexture != nullptr) {
const bool created =
CreateTextureViewFromResourceTexture(
GetDevice(),
baseColorTexture,
resources.baseColorTexture,
resources.baseColorView);
ASSERT_TRUE(created);
}
Texture* specularTexture = GetMaterialTexture(material, "specularTexture");
if (specularTexture != nullptr) {
const bool created =
CreateTextureViewFromResourceTexture(
GetDevice(),
specularTexture,
resources.specularTexture,
resources.specularView);
ASSERT_TRUE(created);
}
DescriptorPoolDesc texturePoolDesc = {};
texturePoolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
texturePoolDesc.descriptorCount = kTextureBindingCount;
texturePoolDesc.shaderVisible = true;
resources.texturePool = GetDevice()->CreateDescriptorPool(texturePoolDesc);
ASSERT_NE(resources.texturePool, nullptr);
resources.textureSet = resources.texturePool->AllocateSet(textureLayoutDesc);
ASSERT_NE(resources.textureSet, nullptr);
resources.textureSet->Update(0, resources.baseColorView != nullptr ? resources.baseColorView : mFallbackBaseColorView);
resources.textureSet->Update(1, resources.specularView != nullptr ? resources.specularView : mFallbackSpecularView);
}
}
void BackpackTest::InitializeBackpackResources() {
LoadBackpackMesh();
InitializeGeometryResources();
InitializeFallbackTextures();
SamplerDesc samplerDesc = {};
samplerDesc.filter = static_cast<uint32_t>(FilterMode::Linear);
samplerDesc.addressU = static_cast<uint32_t>(TextureAddressMode::Wrap);
samplerDesc.addressV = static_cast<uint32_t>(TextureAddressMode::Wrap);
samplerDesc.addressW = static_cast<uint32_t>(TextureAddressMode::Wrap);
samplerDesc.mipLodBias = 0.0f;
samplerDesc.maxAnisotropy = 1;
samplerDesc.comparisonFunc = static_cast<uint32_t>(ComparisonFunc::Always);
samplerDesc.borderColorR = 0.0f;
samplerDesc.borderColorG = 0.0f;
samplerDesc.borderColorB = 0.0f;
samplerDesc.borderColorA = 0.0f;
samplerDesc.minLod = 0.0f;
samplerDesc.maxLod = 1000.0f;
mSampler = GetDevice()->CreateSampler(samplerDesc);
ASSERT_NE(mSampler, nullptr);
DescriptorPoolDesc constantPoolDesc = {};
constantPoolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
constantPoolDesc.descriptorCount = 1;
constantPoolDesc.shaderVisible = false;
mConstantPool = GetDevice()->CreateDescriptorPool(constantPoolDesc);
ASSERT_NE(mConstantPool, nullptr);
DescriptorSetLayoutBinding constantBinding = {};
constantBinding.binding = 0;
constantBinding.type = static_cast<uint32_t>(DescriptorType::CBV);
constantBinding.count = 1;
DescriptorSetLayoutDesc constantLayoutDesc = {};
constantLayoutDesc.bindings = &constantBinding;
constantLayoutDesc.bindingCount = 1;
mConstantSet = mConstantPool->AllocateSet(constantLayoutDesc);
ASSERT_NE(mConstantSet, nullptr);
const SceneConstants sceneConstants = CreateSceneConstants(*mMesh);
mConstantSet->WriteConstant(0, &sceneConstants, sizeof(sceneConstants));
DescriptorPoolDesc samplerPoolDesc = {};
samplerPoolDesc.type = DescriptorHeapType::Sampler;
samplerPoolDesc.descriptorCount = kSamplerBindingCount;
samplerPoolDesc.shaderVisible = true;
mSamplerPool = GetDevice()->CreateDescriptorPool(samplerPoolDesc);
ASSERT_NE(mSamplerPool, nullptr);
DescriptorSetLayoutBinding samplerBindings[kSamplerBindingCount] = {};
samplerBindings[0].binding = 0;
samplerBindings[0].type = static_cast<uint32_t>(DescriptorType::Sampler);
samplerBindings[0].count = 1;
samplerBindings[1].binding = 1;
samplerBindings[1].type = static_cast<uint32_t>(DescriptorType::Sampler);
samplerBindings[1].count = 1;
DescriptorSetLayoutDesc samplerLayoutDesc = {};
samplerLayoutDesc.bindings = samplerBindings;
samplerLayoutDesc.bindingCount = kSamplerBindingCount;
mSamplerSet = mSamplerPool->AllocateSet(samplerLayoutDesc);
ASSERT_NE(mSamplerSet, nullptr);
mSamplerSet->UpdateSampler(0, mSampler);
mSamplerSet->UpdateSampler(1, mSampler);
InitializeMaterialResources();
ASSERT_FALSE(mMaterialResources.empty());
DescriptorSetLayoutBinding textureBindings[kTextureBindingCount] = {};
textureBindings[0].binding = 0;
textureBindings[0].type = static_cast<uint32_t>(DescriptorType::SRV);
textureBindings[0].count = 1;
textureBindings[1].binding = 1;
textureBindings[1].type = static_cast<uint32_t>(DescriptorType::SRV);
textureBindings[1].count = 1;
DescriptorSetLayoutDesc textureLayoutDesc = {};
textureLayoutDesc.bindings = textureBindings;
textureLayoutDesc.bindingCount = kTextureBindingCount;
DescriptorSetLayoutDesc setLayouts[kDescriptorSetCount] = {};
setLayouts[0] = constantLayoutDesc;
setLayouts[1] = textureLayoutDesc;
setLayouts[2] = samplerLayoutDesc;
RHIPipelineLayoutDesc pipelineLayoutDesc = {};
pipelineLayoutDesc.setLayouts = setLayouts;
pipelineLayoutDesc.setLayoutCount = kDescriptorSetCount;
mPipelineLayout = GetDevice()->CreatePipelineLayout(pipelineLayoutDesc);
ASSERT_NE(mPipelineLayout, nullptr);
GraphicsPipelineDesc pipelineDesc =
CreateBackpackPipelineDesc(GetBackendType(), mPipelineLayout);
mPipelineState = GetDevice()->CreatePipelineState(pipelineDesc);
ASSERT_NE(mPipelineState, nullptr);
ASSERT_TRUE(mPipelineState->IsValid());
Log("[TEST] Backpack resources initialized for backend %d", static_cast<int>(GetBackendType()));
}
void BackpackTest::ShutdownBackpackResources() {
if (mPipelineState != nullptr) {
mPipelineState->Shutdown();
delete mPipelineState;
mPipelineState = nullptr;
}
if (mPipelineLayout != nullptr) {
mPipelineLayout->Shutdown();
delete mPipelineLayout;
mPipelineLayout = nullptr;
}
for (MaterialResources& resources : mMaterialResources) {
if (resources.textureSet != nullptr) {
resources.textureSet->Shutdown();
delete resources.textureSet;
resources.textureSet = nullptr;
}
if (resources.texturePool != nullptr) {
resources.texturePool->Shutdown();
delete resources.texturePool;
resources.texturePool = nullptr;
}
if (resources.baseColorView != nullptr) {
resources.baseColorView->Shutdown();
delete resources.baseColorView;
resources.baseColorView = nullptr;
}
if (resources.baseColorTexture != nullptr) {
resources.baseColorTexture->Shutdown();
delete resources.baseColorTexture;
resources.baseColorTexture = nullptr;
}
if (resources.specularView != nullptr) {
resources.specularView->Shutdown();
delete resources.specularView;
resources.specularView = nullptr;
}
if (resources.specularTexture != nullptr) {
resources.specularTexture->Shutdown();
delete resources.specularTexture;
resources.specularTexture = nullptr;
}
}
mMaterialResources.clear();
if (mSamplerSet != nullptr) {
mSamplerSet->Shutdown();
delete mSamplerSet;
mSamplerSet = nullptr;
}
if (mSamplerPool != nullptr) {
mSamplerPool->Shutdown();
delete mSamplerPool;
mSamplerPool = nullptr;
}
if (mConstantSet != nullptr) {
mConstantSet->Shutdown();
delete mConstantSet;
mConstantSet = nullptr;
}
if (mConstantPool != nullptr) {
mConstantPool->Shutdown();
delete mConstantPool;
mConstantPool = nullptr;
}
if (mSampler != nullptr) {
mSampler->Shutdown();
delete mSampler;
mSampler = nullptr;
}
if (mFallbackBaseColorView != nullptr) {
mFallbackBaseColorView->Shutdown();
delete mFallbackBaseColorView;
mFallbackBaseColorView = nullptr;
}
if (mFallbackBaseColorTexture != nullptr) {
mFallbackBaseColorTexture->Shutdown();
delete mFallbackBaseColorTexture;
mFallbackBaseColorTexture = nullptr;
}
if (mFallbackSpecularView != nullptr) {
mFallbackSpecularView->Shutdown();
delete mFallbackSpecularView;
mFallbackSpecularView = nullptr;
}
if (mFallbackSpecularTexture != nullptr) {
mFallbackSpecularTexture->Shutdown();
delete mFallbackSpecularTexture;
mFallbackSpecularTexture = nullptr;
}
if (mVertexBufferView != nullptr) {
mVertexBufferView->Shutdown();
delete mVertexBufferView;
mVertexBufferView = nullptr;
}
if (mIndexBufferView != nullptr) {
mIndexBufferView->Shutdown();
delete mIndexBufferView;
mIndexBufferView = nullptr;
}
if (mVertexBuffer != nullptr) {
mVertexBuffer->Shutdown();
delete mVertexBuffer;
mVertexBuffer = nullptr;
}
if (mIndexBuffer != nullptr) {
mIndexBuffer->Shutdown();
delete mIndexBuffer;
mIndexBuffer = nullptr;
}
mVertices.clear();
if (mMesh != nullptr) {
delete mMesh;
mMesh = nullptr;
}
}
void BackpackTest::RenderFrame() {
ASSERT_NE(mPipelineState, nullptr);
ASSERT_NE(mPipelineLayout, nullptr);
ASSERT_NE(mConstantSet, nullptr);
ASSERT_NE(mSamplerSet, nullptr);
ASSERT_NE(mVertexBufferView, nullptr);
ASSERT_NE(mIndexBufferView, nullptr);
ASSERT_NE(mMesh, nullptr);
ASSERT_FALSE(mMaterialResources.empty());
RHICommandList* cmdList = GetCommandList();
RHICommandQueue* cmdQueue = GetCommandQueue();
ASSERT_NE(cmdList, nullptr);
ASSERT_NE(cmdQueue, nullptr);
cmdList->Reset();
SetRenderTargetForClear(true);
Viewport viewport = { 0.0f, 0.0f, static_cast<float>(kFrameWidth), static_cast<float>(kFrameHeight), 0.0f, 1.0f };
Rect scissorRect = { 0, 0, static_cast<int32_t>(kFrameWidth), static_cast<int32_t>(kFrameHeight) };
cmdList->SetViewport(viewport);
cmdList->SetScissorRect(scissorRect);
cmdList->Clear(0.05f, 0.05f, 0.08f, 1.0f, 1 | 2);
cmdList->SetPipelineState(mPipelineState);
cmdList->SetPrimitiveTopology(PrimitiveTopology::TriangleList);
RHIResourceView* vertexBuffers[] = { mVertexBufferView };
uint64_t offsets[] = { 0 };
uint32_t strides[] = { sizeof(BackpackVertex) };
cmdList->SetVertexBuffers(0, 1, vertexBuffers, offsets, strides);
cmdList->SetIndexBuffer(mIndexBufferView, 0);
for (size_t sectionIndex = 0; sectionIndex < mMesh->GetSections().Size(); ++sectionIndex) {
const MeshSection& section = mMesh->GetSections()[sectionIndex];
const uint32_t materialIndex =
section.materialID < mMaterialResources.size()
? section.materialID
: 0u;
RHIDescriptorSet* descriptorSets[] = {
mConstantSet,
mMaterialResources[materialIndex].textureSet,
mSamplerSet
};
cmdList->SetGraphicsDescriptorSets(0, 3, descriptorSets, mPipelineLayout);
cmdList->DrawIndexed(section.indexCount, 1, section.startIndex, 0, 0);
}
EndRender();
cmdList->Close();
void* cmdLists[] = { cmdList };
cmdQueue->ExecuteCommandLists(1, cmdLists);
}
TEST_P(BackpackTest, RenderBackpack) {
RHICommandQueue* cmdQueue = GetCommandQueue();
RHISwapChain* swapChain = GetSwapChain();
ASSERT_NE(cmdQueue, nullptr);
ASSERT_NE(swapChain, nullptr);
const int targetFrameCount = 30;
const char* screenshotFilename = GetScreenshotFilename(GetBackendType());
const int comparisonThreshold = GetComparisonThreshold(GetBackendType());
for (int frameCount = 0; frameCount <= targetFrameCount; ++frameCount) {
if (frameCount > 0) {
cmdQueue->WaitForPreviousFrame();
}
Log("[TEST] Backpack MainLoop: frame %d", frameCount);
BeginRender();
RenderFrame();
if (frameCount >= targetFrameCount) {
cmdQueue->WaitForIdle();
Log("[TEST] Backpack MainLoop: frame %d reached, capturing %s", frameCount, screenshotFilename);
ASSERT_TRUE(TakeScreenshot(screenshotFilename));
ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", static_cast<float>(comparisonThreshold)));
Log("[TEST] Backpack MainLoop: frame %d compare passed", frameCount);
break;
}
swapChain->Present(0, 0);
}
}
} // namespace
INSTANTIATE_TEST_SUITE_P(D3D12, BackpackTest, ::testing::Values(RHIType::D3D12));
INSTANTIATE_TEST_SUITE_P(OpenGL, BackpackTest, ::testing::Values(RHIType::OpenGL));
GTEST_API_ int main(int argc, char** argv) {
Logger::Get().Initialize();
Logger::Get().AddSink(std::make_unique<ConsoleLogSink>());
Logger::Get().SetMinimumLevel(LogLevel::Debug);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -215,6 +215,38 @@ TEST_P(RHITestFixture, CommandList_SetIndexBuffer_WithRealView) {
delete buffer;
}
TEST_P(RHITestFixture, CommandList_SetIndexBuffer_WithUint16View) {
BufferDesc bufferDesc = {};
bufferDesc.size = 256;
bufferDesc.stride = sizeof(uint16_t);
bufferDesc.bufferType = static_cast<uint32_t>(BufferType::Index);
RHIBuffer* buffer = GetDevice()->CreateBuffer(bufferDesc);
ASSERT_NE(buffer, nullptr);
ResourceViewDesc viewDesc = {};
viewDesc.dimension = ResourceViewDimension::Buffer;
viewDesc.format = static_cast<uint32_t>(Format::R16_UInt);
RHIResourceView* ibv = GetDevice()->CreateIndexBufferView(buffer, viewDesc);
ASSERT_NE(ibv, nullptr);
CommandListDesc cmdDesc = {};
cmdDesc.commandListType = static_cast<uint32_t>(CommandQueueType::Direct);
RHICommandList* cmdList = GetDevice()->CreateCommandList(cmdDesc);
ASSERT_NE(cmdList, nullptr);
cmdList->Reset();
cmdList->SetIndexBuffer(ibv, 0);
cmdList->Close();
cmdList->Shutdown();
delete cmdList;
ibv->Shutdown();
delete ibv;
buffer->Shutdown();
delete buffer;
}
TEST_P(RHITestFixture, CommandList_SetStencilRef) {
CommandListDesc cmdDesc = {};
cmdDesc.commandListType = static_cast<uint32_t>(CommandQueueType::Direct);