984 lines
35 KiB
C++
984 lines
35 KiB
C++
#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();
|
|
}
|