Files
XCEngine/tests/Rendering/integration/nahida_preview_scene/main.cpp

753 lines
26 KiB
C++

#define NOMINMAX
#include <windows.h>
#include <gtest/gtest.h>
#include "../RenderingIntegrationImageAssert.h"
#include "../RenderingIntegrationMain.h"
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Components/LightComponent.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Rendering/Extraction/RenderSceneExtractor.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Scene/Scene.h>
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
#include <chrono>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <memory>
#include <limits>
#include <sstream>
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using namespace XCEngine::Components;
using namespace XCEngine::Rendering;
using namespace XCEngine::Resources;
using namespace XCEngine::RHI;
using namespace XCEngine::RHI::Integration;
using namespace RenderingIntegrationTestUtils;
namespace {
constexpr const char* kD3D12Screenshot = "nahida_preview_scene_d3d12.ppm";
constexpr uint32_t kFrameWidth = 1280;
constexpr uint32_t kFrameHeight = 720;
constexpr int kWarmupFrames = 45;
enum class DiagnosticMode {
Original,
NoShadows,
ForwardLit,
Unlit
};
DiagnosticMode GetDiagnosticMode() {
const char* value = std::getenv("XC_NAHIDA_DIAG_MODE");
if (value == nullptr) {
return DiagnosticMode::Original;
}
const std::string mode(value);
if (mode == "no_shadows") {
return DiagnosticMode::NoShadows;
}
if (mode == "forward_lit") {
return DiagnosticMode::ForwardLit;
}
if (mode == "unlit") {
return DiagnosticMode::Unlit;
}
return DiagnosticMode::Original;
}
const char* GetDiagnosticModeName(DiagnosticMode mode) {
switch (mode) {
case DiagnosticMode::Original: return "original";
case DiagnosticMode::NoShadows: return "no_shadows";
case DiagnosticMode::ForwardLit: return "forward_lit";
case DiagnosticMode::Unlit: return "unlit";
default: return "unknown";
}
}
std::unordered_set<std::string> GetIsolationObjectNames() {
std::unordered_set<std::string> result;
const char* value = std::getenv("XC_NAHIDA_DIAG_ONLY");
if (value == nullptr) {
return result;
}
std::stringstream stream(value);
std::string token;
while (std::getline(stream, token, ',')) {
const size_t first = token.find_first_not_of(" \t\r\n");
if (first == std::string::npos) {
continue;
}
const size_t last = token.find_last_not_of(" \t\r\n");
result.emplace(token.substr(first, last - first + 1));
}
return result;
}
std::string DescribeVertexAttributes(VertexAttribute attributes) {
std::string result;
const auto appendFlag = [&result](const char* name) {
if (!result.empty()) {
result += "|";
}
result += name;
};
if (HasVertexAttribute(attributes, VertexAttribute::Position)) {
appendFlag("Position");
}
if (HasVertexAttribute(attributes, VertexAttribute::Normal)) {
appendFlag("Normal");
}
if (HasVertexAttribute(attributes, VertexAttribute::Tangent)) {
appendFlag("Tangent");
}
if (HasVertexAttribute(attributes, VertexAttribute::Bitangent)) {
appendFlag("Bitangent");
}
if (HasVertexAttribute(attributes, VertexAttribute::Color)) {
appendFlag("Color");
}
if (HasVertexAttribute(attributes, VertexAttribute::UV0)) {
appendFlag("UV0");
}
if (HasVertexAttribute(attributes, VertexAttribute::UV1)) {
appendFlag("UV1");
}
return result;
}
void DumpMeshDiagnostics(const char* label, const Mesh* mesh) {
if (mesh == nullptr) {
std::printf("[NahidaDiag] %s mesh=null\n", label);
return;
}
const XCEngine::Math::Bounds& bounds = mesh->GetBounds();
std::printf(
"[NahidaDiag] %s meshPath=%s vertices=%u indices=%u stride=%u attrs=%s center=(%.4f, %.4f, %.4f) extents=(%.4f, %.4f, %.4f) sections=%zu\n",
label,
mesh->GetPath().CStr(),
mesh->GetVertexCount(),
mesh->GetIndexCount(),
mesh->GetVertexStride(),
DescribeVertexAttributes(mesh->GetVertexAttributes()).c_str(),
bounds.center.x,
bounds.center.y,
bounds.center.z,
bounds.extents.x,
bounds.extents.y,
bounds.extents.z,
mesh->GetSections().Size());
if (mesh->GetVertexStride() != sizeof(StaticMeshVertex) || mesh->GetVertexCount() == 0) {
return;
}
const auto* vertices = static_cast<const StaticMeshVertex*>(mesh->GetVertexData());
XCEngine::Math::Vector2 uvMin(
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max());
XCEngine::Math::Vector2 uvMax(
std::numeric_limits<float>::lowest(),
std::numeric_limits<float>::lowest());
for (uint32_t vertexIndex = 0; vertexIndex < mesh->GetVertexCount(); ++vertexIndex) {
uvMin.x = std::min(uvMin.x, vertices[vertexIndex].uv0.x);
uvMin.y = std::min(uvMin.y, vertices[vertexIndex].uv0.y);
uvMax.x = std::max(uvMax.x, vertices[vertexIndex].uv0.x);
uvMax.y = std::max(uvMax.y, vertices[vertexIndex].uv0.y);
}
const StaticMeshVertex& firstVertex = vertices[0];
std::printf(
"[NahidaDiag] %s firstVertex pos=(%.4f, %.4f, %.4f) normal=(%.4f, %.4f, %.4f) tangent=(%.4f, %.4f, %.4f) bitangent=(%.4f, %.4f, %.4f) uv0=(%.4f, %.4f) uv1=(%.4f, %.4f) color=(%.4f, %.4f, %.4f, %.4f)\n",
label,
firstVertex.position.x,
firstVertex.position.y,
firstVertex.position.z,
firstVertex.normal.x,
firstVertex.normal.y,
firstVertex.normal.z,
firstVertex.tangent.x,
firstVertex.tangent.y,
firstVertex.tangent.z,
firstVertex.bitangent.x,
firstVertex.bitangent.y,
firstVertex.bitangent.z,
firstVertex.uv0.x,
firstVertex.uv0.y,
firstVertex.uv1.x,
firstVertex.uv1.y,
firstVertex.color.x,
firstVertex.color.y,
firstVertex.color.z,
firstVertex.color.w);
std::printf(
"[NahidaDiag] %s uvRange min=(%.4f, %.4f) max=(%.4f, %.4f)\n",
label,
uvMin.x,
uvMin.y,
uvMax.x,
uvMax.y);
}
void DumpMaterialDiagnostics(const char* label, Material* material) {
if (material == nullptr) {
std::printf("[NahidaDiag] %s material=null\n", label);
return;
}
std::printf(
"[NahidaDiag] %s materialPath=%s shader=%s constants=%zu textures=%u keywords=%u renderQueue=%d\n",
label,
material->GetPath().CStr(),
material->GetShader() != nullptr ? material->GetShader()->GetPath().CStr() : "<null>",
material->GetConstantLayout().Size(),
material->GetTextureBindingCount(),
material->GetKeywordCount(),
material->GetRenderQueue());
std::printf(
"[NahidaDiag] %s props _BaseColor=(%.3f, %.3f, %.3f, %.3f) _ShadowColor=(%.3f, %.3f, %.3f, %.3f) _UseNormalMap=%.3f _UseSpecular=%.3f _UseEmission=%.3f _UseRim=%.3f _IsFace=%.3f _UseCustomMaterialType=%.3f\n",
label,
material->GetFloat4("_BaseColor").x,
material->GetFloat4("_BaseColor").y,
material->GetFloat4("_BaseColor").z,
material->GetFloat4("_BaseColor").w,
material->GetFloat4("_ShadowColor").x,
material->GetFloat4("_ShadowColor").y,
material->GetFloat4("_ShadowColor").z,
material->GetFloat4("_ShadowColor").w,
material->GetFloat("_UseNormalMap"),
material->GetFloat("_UseSpecular"),
material->GetFloat("_UseEmission"),
material->GetFloat("_UseRim"),
material->GetFloat("_IsFace"),
material->GetFloat("_UseCustomMaterialType"));
for (uint32_t keywordIndex = 0; keywordIndex < material->GetKeywordCount(); ++keywordIndex) {
std::printf("[NahidaDiag] %s keyword[%u]=%s\n", label, keywordIndex, material->GetKeyword(keywordIndex).CStr());
}
for (uint32_t bindingIndex = 0; bindingIndex < material->GetTextureBindingCount(); ++bindingIndex) {
const ResourceHandle<Texture> textureHandle = material->GetTextureBindingTexture(bindingIndex);
Texture* texture = textureHandle.Get();
std::printf(
"[NahidaDiag] %s texture[%u] name=%s path=%s loaded=%d resolved=%s\n",
label,
bindingIndex,
material->GetTextureBindingName(bindingIndex).CStr(),
material->GetTextureBindingPath(bindingIndex).CStr(),
texture != nullptr ? 1 : 0,
texture != nullptr ? texture->GetPath().CStr() : "<null>");
}
}
void DumpSceneDiagnostics(Scene& scene) {
RenderSceneExtractor extractor;
RenderSceneData sceneData = extractor.Extract(scene, nullptr, kFrameWidth, kFrameHeight);
std::printf(
"[NahidaDiag] extracted cameraPos=(%.3f, %.3f, %.3f) visibleItems=%zu additionalLights=%u\n",
sceneData.cameraData.worldPosition.x,
sceneData.cameraData.worldPosition.y,
sceneData.cameraData.worldPosition.z,
sceneData.visibleItems.size(),
sceneData.lighting.additionalLightCount);
for (size_t visibleIndex = 0; visibleIndex < sceneData.visibleItems.size(); ++visibleIndex) {
const VisibleRenderItem& item = sceneData.visibleItems[visibleIndex];
const char* objectName = item.gameObject != nullptr ? item.gameObject->GetName().c_str() : "<null>";
const char* meshPath = item.mesh != nullptr ? item.mesh->GetPath().CStr() : "<null>";
const char* materialPath = item.material != nullptr ? item.material->GetPath().CStr() : "<null>";
std::printf(
"[NahidaDiag] visible[%zu] object=%s section=%u hasSection=%d materialIndex=%u queue=%d mesh=%s material=%s\n",
visibleIndex,
objectName,
item.sectionIndex,
item.hasSection ? 1 : 0,
item.materialIndex,
item.renderQueue,
meshPath,
materialPath);
}
}
std::filesystem::path GetRepositoryRoot() {
return std::filesystem::path(__FILE__).parent_path().parent_path().parent_path().parent_path().parent_path();
}
std::filesystem::path GetProjectRoot() {
return GetRepositoryRoot() / "project";
}
std::filesystem::path GetScenePath() {
return GetProjectRoot() / "Assets" / "Scenes" / "NahidaPreview.xc";
}
std::filesystem::path GetAssimpDllPath() {
return GetRepositoryRoot() / "engine" / "third_party" / "assimp" / "bin" / "assimp-vc143-mt.dll";
}
class NahidaPreviewSceneTest : public RHIIntegrationFixture {
protected:
void SetUp() override;
void TearDown() override;
void RenderFrame() override;
private:
void InitializeProjectResources();
void PreloadSceneResources();
void PumpSceneLoads(std::chrono::milliseconds timeout);
void TouchSceneMaterialsAndTextures();
void ApplyIsolationFilter();
void ApplyDiagnosticOverrides();
void DumpTargetDiagnostics();
RHIResourceView* GetCurrentBackBufferView();
Material* CreateOverrideMaterial(Material* sourceMaterial, const char* shaderPath);
HMODULE m_assimpModule = nullptr;
std::unique_ptr<Scene> m_scene;
std::unique_ptr<SceneRenderer> m_sceneRenderer;
std::vector<std::unique_ptr<Material>> m_overrideMaterials;
std::vector<RHIResourceView*> m_backBufferViews;
RHITexture* m_depthTexture = nullptr;
RHIResourceView* m_depthView = nullptr;
};
void NahidaPreviewSceneTest::SetUp() {
RHIIntegrationFixture::SetUp();
const std::filesystem::path assimpDllPath = GetAssimpDllPath();
ASSERT_TRUE(std::filesystem::exists(assimpDllPath)) << assimpDllPath.string();
m_assimpModule = LoadLibraryW(assimpDllPath.wstring().c_str());
ASSERT_NE(m_assimpModule, nullptr);
InitializeProjectResources();
const std::filesystem::path scenePath = GetScenePath();
ASSERT_TRUE(std::filesystem::exists(scenePath)) << scenePath.string();
m_sceneRenderer = std::make_unique<SceneRenderer>();
m_scene = std::make_unique<Scene>("NahidaPreview");
m_scene->Load(scenePath.string());
ASSERT_NE(m_scene->Find("Preview Camera"), nullptr);
ASSERT_NE(m_scene->Find("NahidaUnityModel"), nullptr);
ASSERT_NE(m_scene->Find("Body_Mesh0"), nullptr);
ASSERT_NE(m_scene->Find("Face"), nullptr);
PreloadSceneResources();
auto* bodyMeshObject = m_scene->Find("Body_Mesh0");
ASSERT_NE(bodyMeshObject, nullptr);
auto* bodyMeshFilter = bodyMeshObject->GetComponent<MeshFilterComponent>();
ASSERT_NE(bodyMeshFilter, nullptr);
Mesh* bodyMesh = bodyMeshFilter->GetMesh();
ASSERT_NE(bodyMesh, nullptr);
EXPECT_TRUE(HasVertexAttribute(bodyMesh->GetVertexAttributes(), VertexAttribute::Color));
EXPECT_TRUE(HasVertexAttribute(bodyMesh->GetVertexAttributes(), VertexAttribute::UV1));
ApplyIsolationFilter();
ApplyDiagnosticOverrides();
DumpTargetDiagnostics();
TextureDesc depthDesc = {};
depthDesc.width = kFrameWidth;
depthDesc.height = kFrameHeight;
depthDesc.depth = 1;
depthDesc.mipLevels = 1;
depthDesc.arraySize = 1;
depthDesc.format = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
depthDesc.textureType = static_cast<uint32_t>(XCEngine::RHI::TextureType::Texture2D);
depthDesc.sampleCount = 1;
depthDesc.sampleQuality = 0;
depthDesc.flags = 0;
m_depthTexture = GetDevice()->CreateTexture(depthDesc);
ASSERT_NE(m_depthTexture, nullptr);
ResourceViewDesc depthViewDesc = {};
depthViewDesc.format = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
depthViewDesc.dimension = ResourceViewDimension::Texture2D;
depthViewDesc.mipLevel = 0;
m_depthView = GetDevice()->CreateDepthStencilView(m_depthTexture, depthViewDesc);
ASSERT_NE(m_depthView, nullptr);
m_backBufferViews.resize(2, nullptr);
}
void NahidaPreviewSceneTest::TearDown() {
m_sceneRenderer.reset();
if (m_depthView != nullptr) {
m_depthView->Shutdown();
delete m_depthView;
m_depthView = nullptr;
}
if (m_depthTexture != nullptr) {
m_depthTexture->Shutdown();
delete m_depthTexture;
m_depthTexture = nullptr;
}
for (RHIResourceView*& backBufferView : m_backBufferViews) {
if (backBufferView != nullptr) {
backBufferView->Shutdown();
delete backBufferView;
backBufferView = nullptr;
}
}
m_backBufferViews.clear();
m_scene.reset();
ResourceManager& manager = ResourceManager::Get();
manager.UnloadAll();
manager.SetResourceRoot("");
manager.Shutdown();
if (m_assimpModule != nullptr) {
FreeLibrary(m_assimpModule);
m_assimpModule = nullptr;
}
RHIIntegrationFixture::TearDown();
}
void NahidaPreviewSceneTest::InitializeProjectResources() {
ResourceManager& manager = ResourceManager::Get();
manager.Initialize();
manager.SetResourceRoot(GetProjectRoot().string().c_str());
}
void NahidaPreviewSceneTest::PreloadSceneResources() {
ASSERT_NE(m_scene, nullptr);
PumpSceneLoads(std::chrono::milliseconds(4000));
}
void NahidaPreviewSceneTest::PumpSceneLoads(std::chrono::milliseconds timeout) {
ResourceManager& manager = ResourceManager::Get();
const auto deadline = std::chrono::steady_clock::now() + timeout;
do {
TouchSceneMaterialsAndTextures();
manager.UpdateAsyncLoads();
if (!manager.IsAsyncLoading()) {
TouchSceneMaterialsAndTextures();
manager.UpdateAsyncLoads();
if (!manager.IsAsyncLoading()) {
break;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(5));
} while (std::chrono::steady_clock::now() < deadline);
}
void NahidaPreviewSceneTest::TouchSceneMaterialsAndTextures() {
ASSERT_NE(m_scene, nullptr);
const std::vector<MeshFilterComponent*> meshFilters = m_scene->FindObjectsOfType<MeshFilterComponent>();
for (MeshFilterComponent* meshFilter : meshFilters) {
if (meshFilter == nullptr) {
continue;
}
meshFilter->GetMesh();
}
const std::vector<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>();
for (MeshRendererComponent* meshRenderer : meshRenderers) {
if (meshRenderer == nullptr) {
continue;
}
for (size_t materialIndex = 0; materialIndex < meshRenderer->GetMaterialCount(); ++materialIndex) {
Material* material = meshRenderer->GetMaterial(materialIndex);
if (material == nullptr) {
continue;
}
for (uint32_t bindingIndex = 0; bindingIndex < material->GetTextureBindingCount(); ++bindingIndex) {
material->GetTextureBindingTexture(bindingIndex);
}
}
}
}
void NahidaPreviewSceneTest::ApplyIsolationFilter() {
ASSERT_NE(m_scene, nullptr);
const std::unordered_set<std::string> isolatedObjects = GetIsolationObjectNames();
if (isolatedObjects.empty()) {
return;
}
std::printf("[NahidaDiag] isolation=");
bool first = true;
for (const std::string& name : isolatedObjects) {
std::printf("%s%s", first ? "" : ",", name.c_str());
first = false;
}
std::printf("\n");
const std::vector<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>();
for (MeshRendererComponent* meshRenderer : meshRenderers) {
if (meshRenderer == nullptr || meshRenderer->GetGameObject() == nullptr) {
continue;
}
GameObject* gameObject = meshRenderer->GetGameObject();
const bool keep = isolatedObjects.find(gameObject->GetName()) != isolatedObjects.end();
gameObject->SetActive(keep);
}
}
Material* NahidaPreviewSceneTest::CreateOverrideMaterial(Material* sourceMaterial, const char* shaderPath) {
if (sourceMaterial == nullptr || shaderPath == nullptr) {
return nullptr;
}
auto material = std::make_unique<Material>();
IResource::ConstructParams params = {};
params.name = XCEngine::Containers::String("NahidaDiagnosticMaterial");
params.path = XCEngine::Containers::String(("memory://nahida/" + std::string(shaderPath)).c_str());
params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params);
material->SetShader(ResourceManager::Get().Load<Shader>(shaderPath));
const ResourceHandle<Texture> baseMap = sourceMaterial->GetTexture("_BaseMap");
if (baseMap.Get() != nullptr) {
material->SetTexture("_MainTex", baseMap);
}
if (std::string(shaderPath) == "builtin://shaders/forward-lit") {
material->EnableKeyword("XC_ALPHA_TEST");
material->SetFloat("_Cutoff", sourceMaterial->GetFloat("_Cutoff"));
}
material->SetFloat4("_BaseColor", sourceMaterial->GetFloat4("_BaseColor"));
m_overrideMaterials.push_back(std::move(material));
return m_overrideMaterials.back().get();
}
void NahidaPreviewSceneTest::ApplyDiagnosticOverrides() {
ASSERT_NE(m_scene, nullptr);
const DiagnosticMode mode = GetDiagnosticMode();
std::printf("[NahidaDiag] diagnosticMode=%s\n", GetDiagnosticModeName(mode));
if (mode == DiagnosticMode::Original) {
return;
}
if (mode == DiagnosticMode::NoShadows) {
const std::vector<LightComponent*> lights = m_scene->FindObjectsOfType<LightComponent>();
for (LightComponent* light : lights) {
if (light != nullptr) {
light->SetCastsShadows(false);
}
}
return;
}
const char* shaderPath = mode == DiagnosticMode::ForwardLit
? "builtin://shaders/forward-lit"
: "builtin://shaders/unlit";
std::unordered_map<Material*, Material*> overrideBySource;
const std::vector<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>();
for (MeshRendererComponent* meshRenderer : meshRenderers) {
if (meshRenderer == nullptr) {
continue;
}
for (size_t materialIndex = 0; materialIndex < meshRenderer->GetMaterialCount(); ++materialIndex) {
Material* sourceMaterial = meshRenderer->GetMaterial(materialIndex);
if (sourceMaterial == nullptr || sourceMaterial->GetShader() == nullptr) {
continue;
}
if (std::string(sourceMaterial->GetShader()->GetPath().CStr()) != "Assets/Shaders/XCCharacterToon.shader") {
continue;
}
Material* overrideMaterial = nullptr;
const auto found = overrideBySource.find(sourceMaterial);
if (found != overrideBySource.end()) {
overrideMaterial = found->second;
} else {
overrideMaterial = CreateOverrideMaterial(sourceMaterial, shaderPath);
overrideBySource.emplace(sourceMaterial, overrideMaterial);
}
if (overrideMaterial != nullptr) {
meshRenderer->SetMaterial(materialIndex, overrideMaterial);
}
}
}
}
void NahidaPreviewSceneTest::DumpTargetDiagnostics() {
ASSERT_NE(m_scene, nullptr);
DumpSceneDiagnostics(*m_scene);
const char* const targetObjects[] = {
"Body_Mesh0",
"Brow",
"EyeStar",
"Dress_Mesh0",
"Hair_Mesh0",
"Face",
"Face_Eye"
};
for (const char* objectName : targetObjects) {
GameObject* gameObject = m_scene->Find(objectName);
if (gameObject == nullptr) {
std::printf("[NahidaDiag] object=%s missing\n", objectName);
continue;
}
auto* meshFilter = gameObject->GetComponent<MeshFilterComponent>();
auto* meshRenderer = gameObject->GetComponent<MeshRendererComponent>();
std::printf(
"[NahidaDiag] object=%s materialCount=%zu active=%d\n",
objectName,
meshRenderer != nullptr ? meshRenderer->GetMaterialCount() : 0u,
gameObject->IsActiveInHierarchy() ? 1 : 0);
DumpMeshDiagnostics(objectName, meshFilter != nullptr ? meshFilter->GetMesh() : nullptr);
if (meshRenderer == nullptr) {
continue;
}
for (size_t materialIndex = 0; materialIndex < meshRenderer->GetMaterialCount(); ++materialIndex) {
std::string label = std::string(objectName) + "#mat" + std::to_string(materialIndex);
DumpMaterialDiagnostics(label.c_str(), meshRenderer->GetMaterial(materialIndex));
}
}
}
RHIResourceView* NahidaPreviewSceneTest::GetCurrentBackBufferView() {
const int backBufferIndex = GetCurrentBackBufferIndex();
if (backBufferIndex < 0) {
return nullptr;
}
if (static_cast<size_t>(backBufferIndex) >= m_backBufferViews.size()) {
m_backBufferViews.resize(static_cast<size_t>(backBufferIndex) + 1, nullptr);
}
if (m_backBufferViews[backBufferIndex] == nullptr) {
ResourceViewDesc viewDesc = {};
viewDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
viewDesc.dimension = ResourceViewDimension::Texture2D;
viewDesc.mipLevel = 0;
m_backBufferViews[backBufferIndex] = GetDevice()->CreateRenderTargetView(GetCurrentBackBuffer(), viewDesc);
}
return m_backBufferViews[backBufferIndex];
}
void NahidaPreviewSceneTest::RenderFrame() {
ASSERT_NE(m_scene, nullptr);
ASSERT_NE(m_sceneRenderer, nullptr);
TouchSceneMaterialsAndTextures();
ResourceManager::Get().UpdateAsyncLoads();
RHICommandList* commandList = GetCommandList();
ASSERT_NE(commandList, nullptr);
commandList->Reset();
RenderSurface surface(kFrameWidth, kFrameHeight);
surface.SetColorAttachment(GetCurrentBackBufferView());
surface.SetDepthAttachment(m_depthView);
RenderContext renderContext = {};
renderContext.device = GetDevice();
renderContext.commandList = commandList;
renderContext.commandQueue = GetCommandQueue();
renderContext.backendType = GetBackendType();
ASSERT_TRUE(m_sceneRenderer->Render(*m_scene, nullptr, renderContext, surface));
commandList->Close();
void* commandLists[] = { commandList };
GetCommandQueue()->ExecuteCommandLists(1, commandLists);
}
TEST_P(NahidaPreviewSceneTest, RenderNahidaPreviewScene) {
RHICommandQueue* commandQueue = GetCommandQueue();
RHISwapChain* swapChain = GetSwapChain();
ASSERT_NE(commandQueue, nullptr);
ASSERT_NE(swapChain, nullptr);
for (int frameIndex = 0; frameIndex <= kWarmupFrames; ++frameIndex) {
if (frameIndex > 0) {
commandQueue->WaitForPreviousFrame();
}
BeginRender();
RenderFrame();
if (frameIndex >= kWarmupFrames) {
commandQueue->WaitForIdle();
ASSERT_TRUE(TakeScreenshot(kD3D12Screenshot));
const PpmImage image = LoadPpmImage(kD3D12Screenshot);
ASSERT_EQ(image.width, kFrameWidth);
ASSERT_EQ(image.height, kFrameHeight);
const std::filesystem::path gtPath = ResolveRuntimePath("GT.ppm");
if (!std::filesystem::exists(gtPath)) {
GTEST_SKIP() << "GT.ppm missing, screenshot captured for manual review: " << kD3D12Screenshot;
}
ASSERT_TRUE(CompareWithGoldenTemplate(kD3D12Screenshot, "GT.ppm", 10.0f));
break;
}
swapChain->Present(0, 0);
}
}
} // namespace
INSTANTIATE_TEST_SUITE_P(D3D12, NahidaPreviewSceneTest, ::testing::Values(RHIType::D3D12));
GTEST_API_ int main(int argc, char** argv) {
return RunRenderingIntegrationTestMain(argc, argv);
}