#define NOMINMAX #include #include #include #include #include #include #include #include #include #include #include #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(Format::R8G8B8A8_UNorm); textureDesc.textureType = static_cast(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(rhiFormat); textureDesc.textureType = static_cast(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(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(kFrameWidth) / static_cast(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(PrimitiveTopologyType::Triangle); desc.renderTargetFormats[0] = static_cast(Format::R8G8B8A8_UNorm); desc.depthStencilFormat = static_cast(Format::D24_UNorm_S8_UInt); desc.sampleCount = 1; desc.rasterizerState.fillMode = static_cast(FillMode::Solid); desc.rasterizerState.cullMode = static_cast(CullMode::None); desc.rasterizerState.frontFace = static_cast(FrontFace::CounterClockwise); desc.rasterizerState.depthClipEnable = true; desc.depthStencilState.depthTestEnable = true; desc.depthStencilState.depthWriteEnable = true; desc.depthStencilState.depthFunc = static_cast(ComparisonFunc::Less); desc.depthStencilState.stencilEnable = false; InputElementDesc position = {}; position.semanticName = "POSITION"; position.semanticIndex = 0; position.format = static_cast(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(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(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 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 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 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(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(mMesh->GetSections().Size()), static_cast(mMesh->GetMaterials().Size()), static_cast(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(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(mVertices.size() * sizeof(BackpackVertex)); vertexBufferDesc.stride = sizeof(BackpackVertex); vertexBufferDesc.bufferType = static_cast(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(mMesh->GetIndexDataSize()); indexBufferDesc.stride = mMesh->IsUse32BitIndex() ? sizeof(uint32_t) : sizeof(uint16_t); indexBufferDesc.bufferType = static_cast(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(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(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(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(DescriptorType::SRV); textureBindings[0].count = 1; textureBindings[1].binding = 1; textureBindings[1].type = static_cast(DescriptorType::SRV); textureBindings[1].count = 1; DescriptorSetLayoutDesc textureLayoutDesc = {}; textureLayoutDesc.bindings = textureBindings; textureLayoutDesc.bindingCount = kTextureBindingCount; const uint32_t materialCount = std::max(1u, static_cast(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(FilterMode::Linear); samplerDesc.addressU = static_cast(TextureAddressMode::Wrap); samplerDesc.addressV = static_cast(TextureAddressMode::Wrap); samplerDesc.addressW = static_cast(TextureAddressMode::Wrap); samplerDesc.mipLodBias = 0.0f; samplerDesc.maxAnisotropy = 1; samplerDesc.comparisonFunc = static_cast(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(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(DescriptorType::Sampler); samplerBindings[0].count = 1; samplerBindings[1].binding = 1; samplerBindings[1].type = static_cast(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(DescriptorType::SRV); textureBindings[0].count = 1; textureBindings[1].binding = 1; textureBindings[1].type = static_cast(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(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(kFrameWidth), static_cast(kFrameHeight), 0.0f, 1.0f }; Rect scissorRect = { 0, 0, static_cast(kFrameWidth), static_cast(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(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()); Logger::Get().SetMinimumLevel(LogLevel::Debug); testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }