1111 lines
40 KiB
C++
1111 lines
40 KiB
C++
#define NOMINMAX
|
|
#include <windows.h>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "../RenderingIntegrationMain.h"
|
|
|
|
#include <XCEngine/Components/CameraComponent.h>
|
|
#include <XCEngine/Components/GameObject.h>
|
|
#include <XCEngine/Components/LightComponent.h>
|
|
#include <XCEngine/Components/MeshFilterComponent.h>
|
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
|
#include <XCEngine/Core/Asset/IResource.h>
|
|
#include <XCEngine/Core/Math/Color.h>
|
|
#include <XCEngine/Core/Math/Quaternion.h>
|
|
#include <XCEngine/Core/Math/Vector2.h>
|
|
#include <XCEngine/Core/Math/Vector3.h>
|
|
#include <XCEngine/Debug/ConsoleLogSink.h>
|
|
#include <XCEngine/Debug/Logger.h>
|
|
#include <XCEngine/Rendering/RenderContext.h>
|
|
#include <XCEngine/Rendering/RenderPass.h>
|
|
#include <XCEngine/Rendering/RenderSurface.h>
|
|
#include <XCEngine/Rendering/SceneRenderer.h>
|
|
#include <XCEngine/Resources/BuiltinResources.h>
|
|
#include <XCEngine/Resources/Material/Material.h>
|
|
#include <XCEngine/Resources/Mesh/Mesh.h>
|
|
#include <XCEngine/Resources/Shader/Shader.h>
|
|
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
|
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
|
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
|
#include <XCEngine/RHI/RHIPipelineState.h>
|
|
#include <XCEngine/RHI/RHISampler.h>
|
|
#include <XCEngine/RHI/RHITexture.h>
|
|
#include <XCEngine/Scene/Scene.h>
|
|
|
|
#include "../../../RHI/integration/fixtures/RHIIntegrationFixture.h"
|
|
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
using namespace XCEngine::Components;
|
|
using namespace XCEngine::Debug;
|
|
using namespace XCEngine::Math;
|
|
using namespace XCEngine::Rendering;
|
|
using namespace XCEngine::Resources;
|
|
using namespace XCEngine::RHI;
|
|
using namespace XCEngine::RHI::Integration;
|
|
|
|
namespace {
|
|
|
|
constexpr const char* kD3D12Screenshot = "directional_shadow_scene_d3d12.ppm";
|
|
constexpr const char* kOpenGLScreenshot = "directional_shadow_scene_opengl.ppm";
|
|
constexpr const char* kVulkanScreenshot = "directional_shadow_scene_vulkan.ppm";
|
|
constexpr const char* kD3D12ShadowMapScreenshot = "directional_shadow_map_d3d12.ppm";
|
|
constexpr const char* kOpenGLShadowMapScreenshot = "directional_shadow_map_opengl.ppm";
|
|
constexpr const char* kVulkanShadowMapScreenshot = "directional_shadow_map_vulkan.ppm";
|
|
constexpr uint32_t kFrameWidth = 1280;
|
|
constexpr uint32_t kFrameHeight = 720;
|
|
|
|
const char kShadowMapDebugHlsl[] = R"(
|
|
Texture2D<float> gShadowMap : register(t0);
|
|
SamplerState gShadowSampler : register(s0);
|
|
|
|
struct VSOutput {
|
|
float4 position : SV_POSITION;
|
|
float2 uv : TEXCOORD0;
|
|
};
|
|
|
|
VSOutput MainVS(uint vertexId : SV_VertexID) {
|
|
VSOutput output;
|
|
|
|
const float2 positions[3] = {
|
|
float2(-1.0f, -1.0f),
|
|
float2(-1.0f, 3.0f),
|
|
float2( 3.0f, -1.0f)
|
|
};
|
|
const float2 uvs[3] = {
|
|
float2(0.0f, 0.0f),
|
|
float2(0.0f, 2.0f),
|
|
float2(2.0f, 0.0f)
|
|
};
|
|
|
|
output.position = float4(positions[vertexId], 0.0f, 1.0f);
|
|
output.uv = uvs[vertexId];
|
|
return output;
|
|
}
|
|
|
|
float4 MainPS(VSOutput input) : SV_TARGET {
|
|
const float depth = gShadowMap.Sample(gShadowSampler, input.uv);
|
|
return float4(depth, depth, depth, 1.0f);
|
|
}
|
|
)";
|
|
|
|
const char kShadowMapDebugVertexShader[] = R"(#version 430
|
|
out vec2 vTexCoord;
|
|
|
|
void main() {
|
|
const vec2 positions[3] = vec2[](
|
|
vec2(-1.0, -1.0),
|
|
vec2(-1.0, 3.0),
|
|
vec2( 3.0, -1.0)
|
|
);
|
|
const vec2 uvs[3] = vec2[](
|
|
vec2(0.0, 0.0),
|
|
vec2(0.0, 2.0),
|
|
vec2(2.0, 0.0)
|
|
);
|
|
|
|
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
|
vTexCoord = uvs[gl_VertexID];
|
|
}
|
|
)";
|
|
|
|
const char kShadowMapDebugFragmentShader[] = R"(#version 430
|
|
layout(binding = 0) uniform sampler2D uShadowMap;
|
|
|
|
in vec2 vTexCoord;
|
|
|
|
layout(location = 0) out vec4 fragColor;
|
|
|
|
void main() {
|
|
const float depth = texture(uShadowMap, vTexCoord).r;
|
|
fragColor = vec4(depth, depth, depth, 1.0);
|
|
}
|
|
)";
|
|
|
|
void AppendQuadFace(
|
|
std::vector<StaticMeshVertex>& vertices,
|
|
std::vector<uint32_t>& indices,
|
|
const Vector3& v0,
|
|
const Vector3& v1,
|
|
const Vector3& v2,
|
|
const Vector3& v3,
|
|
const Vector3& normal) {
|
|
const uint32_t baseIndex = static_cast<uint32_t>(vertices.size());
|
|
|
|
StaticMeshVertex vertex = {};
|
|
vertex.normal = normal;
|
|
|
|
vertex.position = v0;
|
|
vertex.uv0 = Vector2(0.0f, 1.0f);
|
|
vertices.push_back(vertex);
|
|
|
|
vertex.position = v1;
|
|
vertex.uv0 = Vector2(1.0f, 1.0f);
|
|
vertices.push_back(vertex);
|
|
|
|
vertex.position = v2;
|
|
vertex.uv0 = Vector2(0.0f, 0.0f);
|
|
vertices.push_back(vertex);
|
|
|
|
vertex.position = v3;
|
|
vertex.uv0 = Vector2(1.0f, 0.0f);
|
|
vertices.push_back(vertex);
|
|
|
|
indices.push_back(baseIndex + 0);
|
|
indices.push_back(baseIndex + 2);
|
|
indices.push_back(baseIndex + 1);
|
|
indices.push_back(baseIndex + 1);
|
|
indices.push_back(baseIndex + 2);
|
|
indices.push_back(baseIndex + 3);
|
|
}
|
|
|
|
Mesh* CreateGroundMesh() {
|
|
auto* mesh = new Mesh();
|
|
IResource::ConstructParams params = {};
|
|
params.name = "DirectionalShadowGroundMesh";
|
|
params.path = "Tests/Rendering/DirectionalShadowGround.mesh";
|
|
params.guid = ResourceGUID::Generate(params.path);
|
|
mesh->Initialize(params);
|
|
|
|
StaticMeshVertex vertices[4] = {};
|
|
vertices[0].position = Vector3(-12.0f, 0.0f, 1.0f);
|
|
vertices[0].normal = Vector3::Up();
|
|
vertices[0].uv0 = Vector2(0.0f, 1.0f);
|
|
vertices[1].position = Vector3(-12.0f, 0.0f, 20.0f);
|
|
vertices[1].normal = Vector3::Up();
|
|
vertices[1].uv0 = Vector2(0.0f, 0.0f);
|
|
vertices[2].position = Vector3(12.0f, 0.0f, 1.0f);
|
|
vertices[2].normal = Vector3::Up();
|
|
vertices[2].uv0 = Vector2(1.0f, 1.0f);
|
|
vertices[3].position = Vector3(12.0f, 0.0f, 20.0f);
|
|
vertices[3].normal = Vector3::Up();
|
|
vertices[3].uv0 = Vector2(1.0f, 0.0f);
|
|
|
|
const uint32_t indices[6] = { 0, 1, 2, 2, 1, 3 };
|
|
|
|
mesh->SetVertexData(
|
|
vertices,
|
|
sizeof(vertices),
|
|
4,
|
|
sizeof(StaticMeshVertex),
|
|
VertexAttribute::Position | VertexAttribute::Normal | VertexAttribute::UV0);
|
|
mesh->SetIndexData(indices, sizeof(indices), 6, true);
|
|
const Bounds bounds(Vector3(0.0f, 0.0f, 10.5f), Vector3(24.0f, 0.1f, 19.0f));
|
|
mesh->SetBounds(bounds);
|
|
|
|
MeshSection section = {};
|
|
section.baseVertex = 0;
|
|
section.vertexCount = 4;
|
|
section.startIndex = 0;
|
|
section.indexCount = 6;
|
|
section.materialID = 0;
|
|
section.bounds = bounds;
|
|
mesh->AddSection(section);
|
|
return mesh;
|
|
}
|
|
|
|
Mesh* CreateCubeMesh() {
|
|
auto* mesh = new Mesh();
|
|
IResource::ConstructParams params = {};
|
|
params.name = "DirectionalShadowCubeMesh";
|
|
params.path = "Tests/Rendering/DirectionalShadowCube.mesh";
|
|
params.guid = ResourceGUID::Generate(params.path);
|
|
mesh->Initialize(params);
|
|
|
|
std::vector<StaticMeshVertex> vertices;
|
|
std::vector<uint32_t> indices;
|
|
vertices.reserve(24);
|
|
indices.reserve(36);
|
|
|
|
constexpr float half = 0.5f;
|
|
AppendQuadFace(
|
|
vertices, indices,
|
|
Vector3(-half, -half, half),
|
|
Vector3(half, -half, half),
|
|
Vector3(-half, half, half),
|
|
Vector3(half, half, half),
|
|
Vector3::Forward());
|
|
AppendQuadFace(
|
|
vertices, indices,
|
|
Vector3(half, -half, -half),
|
|
Vector3(-half, -half, -half),
|
|
Vector3(half, half, -half),
|
|
Vector3(-half, half, -half),
|
|
Vector3::Back());
|
|
AppendQuadFace(
|
|
vertices, indices,
|
|
Vector3(-half, -half, -half),
|
|
Vector3(-half, -half, half),
|
|
Vector3(-half, half, -half),
|
|
Vector3(-half, half, half),
|
|
Vector3::Left());
|
|
AppendQuadFace(
|
|
vertices, indices,
|
|
Vector3(half, -half, half),
|
|
Vector3(half, -half, -half),
|
|
Vector3(half, half, half),
|
|
Vector3(half, half, -half),
|
|
Vector3::Right());
|
|
AppendQuadFace(
|
|
vertices, indices,
|
|
Vector3(-half, half, half),
|
|
Vector3(half, half, half),
|
|
Vector3(-half, half, -half),
|
|
Vector3(half, half, -half),
|
|
Vector3::Up());
|
|
AppendQuadFace(
|
|
vertices, indices,
|
|
Vector3(-half, -half, -half),
|
|
Vector3(half, -half, -half),
|
|
Vector3(-half, -half, half),
|
|
Vector3(half, -half, half),
|
|
Vector3::Down());
|
|
|
|
mesh->SetVertexData(
|
|
vertices.data(),
|
|
vertices.size() * sizeof(StaticMeshVertex),
|
|
static_cast<uint32_t>(vertices.size()),
|
|
sizeof(StaticMeshVertex),
|
|
VertexAttribute::Position | VertexAttribute::Normal | VertexAttribute::UV0);
|
|
mesh->SetIndexData(
|
|
indices.data(),
|
|
indices.size() * sizeof(uint32_t),
|
|
static_cast<uint32_t>(indices.size()),
|
|
true);
|
|
const Bounds bounds(Vector3::Zero(), Vector3::One());
|
|
mesh->SetBounds(bounds);
|
|
|
|
MeshSection section = {};
|
|
section.baseVertex = 0;
|
|
section.vertexCount = static_cast<uint32_t>(vertices.size());
|
|
section.startIndex = 0;
|
|
section.indexCount = static_cast<uint32_t>(indices.size());
|
|
section.materialID = 0;
|
|
section.bounds = bounds;
|
|
mesh->AddSection(section);
|
|
return mesh;
|
|
}
|
|
|
|
Material* CreateForwardLitMaterial(
|
|
const char* name,
|
|
const char* path,
|
|
const Vector4& baseColor) {
|
|
auto* material = new Material();
|
|
IResource::ConstructParams params = {};
|
|
params.name = name;
|
|
params.path = path;
|
|
params.guid = ResourceGUID::Generate(params.path);
|
|
material->Initialize(params);
|
|
material->SetShader(ResourceManager::Get().Load<Shader>(GetBuiltinForwardLitShaderPath()));
|
|
material->SetShaderPass("ForwardLit");
|
|
material->SetFloat4("_BaseColor", baseColor);
|
|
return material;
|
|
}
|
|
|
|
const char* GetScreenshotFilename(RHIType backendType) {
|
|
switch (backendType) {
|
|
case RHIType::D3D12:
|
|
return kD3D12Screenshot;
|
|
case RHIType::Vulkan:
|
|
return kVulkanScreenshot;
|
|
case RHIType::OpenGL:
|
|
default:
|
|
return kOpenGLScreenshot;
|
|
}
|
|
}
|
|
|
|
const char* GetShadowMapScreenshotFilename(RHIType backendType) {
|
|
switch (backendType) {
|
|
case RHIType::D3D12:
|
|
return kD3D12ShadowMapScreenshot;
|
|
case RHIType::Vulkan:
|
|
return kVulkanShadowMapScreenshot;
|
|
case RHIType::OpenGL:
|
|
default:
|
|
return kOpenGLShadowMapScreenshot;
|
|
}
|
|
}
|
|
|
|
int GetComparisonThreshold(RHIType backendType) {
|
|
return backendType == RHIType::D3D12 ? 10 : 10;
|
|
}
|
|
|
|
struct ShadowProjectionDebugPoint {
|
|
Vector4 clip = Vector4::Zero();
|
|
Vector3 ndc = Vector3::Zero();
|
|
Vector2 uvFlipY = Vector2::Zero();
|
|
Vector2 uvNoFlipY = Vector2::Zero();
|
|
};
|
|
|
|
ShadowProjectionDebugPoint ProjectShadowPoint(
|
|
const Matrix4x4& gpuWorldToShadowMatrix,
|
|
const Vector3& worldPoint) {
|
|
ShadowProjectionDebugPoint result = {};
|
|
const Matrix4x4 worldToShadow = gpuWorldToShadowMatrix.Transpose();
|
|
result.clip = worldToShadow * Vector4(worldPoint.x, worldPoint.y, worldPoint.z, 1.0f);
|
|
if (std::abs(result.clip.w) > EPSILON) {
|
|
result.ndc = Vector3(
|
|
result.clip.x / result.clip.w,
|
|
result.clip.y / result.clip.w,
|
|
result.clip.z / result.clip.w);
|
|
result.uvFlipY = Vector2(
|
|
result.ndc.x * 0.5f + 0.5f,
|
|
result.ndc.y * -0.5f + 0.5f);
|
|
result.uvNoFlipY = Vector2(
|
|
result.ndc.x * 0.5f + 0.5f,
|
|
result.ndc.y * 0.5f + 0.5f);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void LogShadowProjection(
|
|
const char* label,
|
|
const ShadowProjectionDebugPoint& point,
|
|
uint32_t shadowMapWidth,
|
|
uint32_t shadowMapHeight) {
|
|
Log(
|
|
"[TEST] ShadowProbe %s clip=(%.4f, %.4f, %.4f, %.4f) "
|
|
"ndc=(%.4f, %.4f, %.4f) uvFlip=(%.4f, %.4f) uvNoFlip=(%.4f, %.4f) "
|
|
"texelFlip=(%d, %d) texelNoFlip=(%d, %d)",
|
|
label,
|
|
point.clip.x, point.clip.y, point.clip.z, point.clip.w,
|
|
point.ndc.x, point.ndc.y, point.ndc.z,
|
|
point.uvFlipY.x, point.uvFlipY.y,
|
|
point.uvNoFlipY.x, point.uvNoFlipY.y,
|
|
static_cast<int>(point.uvFlipY.x * static_cast<float>(shadowMapWidth)),
|
|
static_cast<int>(point.uvFlipY.y * static_cast<float>(shadowMapHeight)),
|
|
static_cast<int>(point.uvNoFlipY.x * static_cast<float>(shadowMapWidth)),
|
|
static_cast<int>(point.uvNoFlipY.y * static_cast<float>(shadowMapHeight)));
|
|
}
|
|
|
|
void LogShadowAlignmentAnalysis(
|
|
const Scene& scene,
|
|
const CameraRenderRequest& request) {
|
|
if (!request.directionalShadow.IsValid()) {
|
|
Log("[TEST] ShadowProbe skipped because directional shadow plan was invalid");
|
|
return;
|
|
}
|
|
|
|
const GameObject* casterObject = scene.Find("CasterCube");
|
|
const GameObject* groundObject = scene.Find("Ground");
|
|
if (casterObject == nullptr || groundObject == nullptr) {
|
|
Log("[TEST] ShadowProbe skipped because test scene objects were missing");
|
|
return;
|
|
}
|
|
|
|
const Vector3 casterCenter = casterObject->GetTransform()->GetPosition();
|
|
const Vector3 casterScale = casterObject->GetTransform()->GetScale();
|
|
const Vector3 groundCenter = groundObject->GetTransform()->GetPosition();
|
|
const Vector3 groundScale = groundObject->GetTransform()->GetScale();
|
|
const float groundTopY = groundCenter.y + groundScale.y * 0.5f;
|
|
|
|
const Vector3 lightDirectionToLight = request.directionalShadow.lightDirection.Normalized();
|
|
const Vector3 shadowRayDirection = lightDirectionToLight * -1.0f;
|
|
if (std::abs(shadowRayDirection.y) <= EPSILON) {
|
|
Log("[TEST] ShadowProbe skipped because shadow ray direction was parallel to the ground");
|
|
return;
|
|
}
|
|
|
|
const Vector3 casterBottomCenter = casterCenter - Vector3(0.0f, casterScale.y * 0.5f, 0.0f);
|
|
const float centerTravel = (groundTopY - casterCenter.y) / shadowRayDirection.y;
|
|
const float bottomTravel = (groundTopY - casterBottomCenter.y) / shadowRayDirection.y;
|
|
const Vector3 receiverFromCenter = casterCenter + shadowRayDirection * centerTravel;
|
|
const Vector3 receiverFromBottom = casterBottomCenter + shadowRayDirection * bottomTravel;
|
|
|
|
const ShadowProjectionDebugPoint casterCenterProjection =
|
|
ProjectShadowPoint(request.directionalShadow.cameraData.viewProjection, casterCenter);
|
|
const ShadowProjectionDebugPoint casterBottomProjection =
|
|
ProjectShadowPoint(request.directionalShadow.cameraData.viewProjection, casterBottomCenter);
|
|
const ShadowProjectionDebugPoint receiverFromCenterProjection =
|
|
ProjectShadowPoint(request.directionalShadow.cameraData.viewProjection, receiverFromCenter);
|
|
const ShadowProjectionDebugPoint receiverFromBottomProjection =
|
|
ProjectShadowPoint(request.directionalShadow.cameraData.viewProjection, receiverFromBottom);
|
|
|
|
Log(
|
|
"[TEST] ShadowProbe lightDirToLight=(%.4f, %.4f, %.4f) shadowRay=(%.4f, %.4f, %.4f) "
|
|
"groundTopY=%.4f centerTravel=%.4f bottomTravel=%.4f",
|
|
lightDirectionToLight.x, lightDirectionToLight.y, lightDirectionToLight.z,
|
|
shadowRayDirection.x, shadowRayDirection.y, shadowRayDirection.z,
|
|
groundTopY,
|
|
centerTravel,
|
|
bottomTravel);
|
|
LogShadowProjection(
|
|
"CasterCenter",
|
|
casterCenterProjection,
|
|
request.directionalShadow.mapWidth,
|
|
request.directionalShadow.mapHeight);
|
|
LogShadowProjection(
|
|
"CasterBottom",
|
|
casterBottomProjection,
|
|
request.directionalShadow.mapWidth,
|
|
request.directionalShadow.mapHeight);
|
|
LogShadowProjection(
|
|
"ReceiverFromCenter",
|
|
receiverFromCenterProjection,
|
|
request.directionalShadow.mapWidth,
|
|
request.directionalShadow.mapHeight);
|
|
LogShadowProjection(
|
|
"ReceiverFromBottom",
|
|
receiverFromBottomProjection,
|
|
request.directionalShadow.mapWidth,
|
|
request.directionalShadow.mapHeight);
|
|
|
|
Log(
|
|
"[TEST] ShadowProbe deltaUvFlip center=%.6f bottom=%.6f deltaUvNoFlip center=%.6f bottom=%.6f",
|
|
(receiverFromCenterProjection.uvFlipY - casterCenterProjection.uvFlipY).Magnitude(),
|
|
(receiverFromBottomProjection.uvFlipY - casterBottomProjection.uvFlipY).Magnitude(),
|
|
(receiverFromCenterProjection.uvNoFlipY - casterCenterProjection.uvNoFlipY).Magnitude(),
|
|
(receiverFromBottomProjection.uvNoFlipY - casterBottomProjection.uvNoFlipY).Magnitude());
|
|
}
|
|
|
|
class ShadowMapDebugOverlayPass final : public RenderPass {
|
|
public:
|
|
~ShadowMapDebugOverlayPass() override {
|
|
DestroyResources();
|
|
}
|
|
|
|
const char* GetName() const override {
|
|
return "ShadowMapDebugOverlayPass";
|
|
}
|
|
|
|
bool Initialize(const RenderContext& context) override {
|
|
return EnsureInitialized(context);
|
|
}
|
|
|
|
void ReleaseResources() {
|
|
DestroyResources();
|
|
}
|
|
|
|
void Shutdown() override {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: Shutdown (deferred)");
|
|
}
|
|
|
|
bool Execute(const RenderPassContext& context) override {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: Execute begin");
|
|
if (!context.renderContext.IsValid() ||
|
|
!context.sceneData.lighting.HasMainDirectionalShadow()) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: invalid context or missing shadow");
|
|
return false;
|
|
}
|
|
|
|
const std::vector<RHIResourceView*>& colorAttachments = context.surface.GetColorAttachments();
|
|
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: missing color attachment");
|
|
return false;
|
|
}
|
|
|
|
if (!EnsureInitialized(context.renderContext)) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: EnsureInitialized failed");
|
|
return false;
|
|
}
|
|
|
|
RHIResourceView* shadowMap = context.sceneData.lighting.mainDirectionalShadow.shadowMap;
|
|
if (shadowMap == nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: shadowMap null");
|
|
return false;
|
|
}
|
|
|
|
Log("[TEST] ShadowMapDebugOverlayPass: updating descriptors");
|
|
mTextureSet->Update(0, shadowMap);
|
|
|
|
RHICommandList* commandList = context.renderContext.commandList;
|
|
RHIResourceView* renderTarget = colorAttachments[0];
|
|
const XCEngine::Math::RectInt renderArea = context.surface.GetRenderArea();
|
|
const XCEngine::RHI::ResourceStates restoredColorState = context.surface.GetColorStateAfter();
|
|
|
|
if (context.surface.IsAutoTransitionEnabled()) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: transition color -> RT");
|
|
commandList->TransitionBarrier(
|
|
renderTarget,
|
|
restoredColorState,
|
|
XCEngine::RHI::ResourceStates::RenderTarget);
|
|
}
|
|
Log("[TEST] ShadowMapDebugOverlayPass: transition shadow -> SRV");
|
|
commandList->TransitionBarrier(
|
|
shadowMap,
|
|
XCEngine::RHI::ResourceStates::DepthWrite,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
|
|
Log("[TEST] ShadowMapDebugOverlayPass: bind RT");
|
|
commandList->SetRenderTargets(1, &renderTarget, nullptr);
|
|
|
|
const XCEngine::RHI::Viewport viewport = {
|
|
static_cast<float>(renderArea.x),
|
|
static_cast<float>(renderArea.y),
|
|
static_cast<float>(renderArea.width),
|
|
static_cast<float>(renderArea.height),
|
|
0.0f,
|
|
1.0f
|
|
};
|
|
const XCEngine::RHI::Rect scissorRect = {
|
|
renderArea.x,
|
|
renderArea.y,
|
|
renderArea.x + renderArea.width,
|
|
renderArea.y + renderArea.height
|
|
};
|
|
Log("[TEST] ShadowMapDebugOverlayPass: set viewport/scissor");
|
|
commandList->SetViewport(viewport);
|
|
commandList->SetScissorRect(scissorRect);
|
|
commandList->SetPrimitiveTopology(XCEngine::RHI::PrimitiveTopology::TriangleList);
|
|
Log("[TEST] ShadowMapDebugOverlayPass: set pipeline");
|
|
commandList->SetPipelineState(mPipelineState);
|
|
|
|
RHIDescriptorSet* descriptorSets[] = { mTextureSet, mSamplerSet };
|
|
Log("[TEST] ShadowMapDebugOverlayPass: bind descriptors");
|
|
commandList->SetGraphicsDescriptorSets(0, 2, descriptorSets, mPipelineLayout);
|
|
Log("[TEST] ShadowMapDebugOverlayPass: draw");
|
|
commandList->Draw(3, 1, 0, 0);
|
|
Log("[TEST] ShadowMapDebugOverlayPass: end render pass");
|
|
commandList->EndRenderPass();
|
|
|
|
Log("[TEST] ShadowMapDebugOverlayPass: transition shadow -> DepthWrite");
|
|
commandList->TransitionBarrier(
|
|
shadowMap,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource,
|
|
XCEngine::RHI::ResourceStates::DepthWrite);
|
|
if (context.surface.IsAutoTransitionEnabled()) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: transition color -> restore");
|
|
commandList->TransitionBarrier(
|
|
renderTarget,
|
|
XCEngine::RHI::ResourceStates::RenderTarget,
|
|
restoredColorState);
|
|
}
|
|
|
|
Log("[TEST] ShadowMapDebugOverlayPass: Execute end");
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
bool EnsureInitialized(const RenderContext& context) {
|
|
if (mPipelineLayout != nullptr &&
|
|
mPipelineState != nullptr &&
|
|
mTexturePool != nullptr &&
|
|
mTextureSet != nullptr &&
|
|
mSamplerPool != nullptr &&
|
|
mSamplerSet != nullptr &&
|
|
mSampler != nullptr &&
|
|
mDevice == context.device &&
|
|
mBackendType == context.backendType) {
|
|
return true;
|
|
}
|
|
|
|
DestroyResources();
|
|
return CreateResources(context);
|
|
}
|
|
|
|
bool CreateResources(const RenderContext& context) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: CreateResources backend=%d", static_cast<int>(context.backendType));
|
|
mDevice = context.device;
|
|
mBackendType = context.backendType;
|
|
|
|
SamplerDesc samplerDesc = {};
|
|
samplerDesc.filter = static_cast<uint32_t>(FilterMode::Point);
|
|
samplerDesc.addressU = static_cast<uint32_t>(TextureAddressMode::Clamp);
|
|
samplerDesc.addressV = static_cast<uint32_t>(TextureAddressMode::Clamp);
|
|
samplerDesc.addressW = static_cast<uint32_t>(TextureAddressMode::Clamp);
|
|
samplerDesc.comparisonFunc = static_cast<uint32_t>(ComparisonFunc::Always);
|
|
samplerDesc.maxAnisotropy = 1;
|
|
samplerDesc.minLod = 0.0f;
|
|
samplerDesc.maxLod = 1000.0f;
|
|
mSampler = mDevice->CreateSampler(samplerDesc);
|
|
if (mSampler == nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: CreateSampler failed");
|
|
DestroyResources();
|
|
return false;
|
|
}
|
|
|
|
DescriptorSetLayoutBinding textureBinding = {};
|
|
textureBinding.binding = 0;
|
|
textureBinding.type = static_cast<uint32_t>(DescriptorType::SRV);
|
|
textureBinding.count = 1;
|
|
textureBinding.visibility = static_cast<uint32_t>(ShaderVisibility::Pixel);
|
|
|
|
DescriptorSetLayoutDesc textureLayout = {};
|
|
textureLayout.bindings = &textureBinding;
|
|
textureLayout.bindingCount = 1;
|
|
|
|
DescriptorPoolDesc texturePoolDesc = {};
|
|
texturePoolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
|
|
texturePoolDesc.descriptorCount = 1;
|
|
texturePoolDesc.shaderVisible = true;
|
|
mTexturePool = mDevice->CreateDescriptorPool(texturePoolDesc);
|
|
if (mTexturePool == nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: texture pool failed");
|
|
DestroyResources();
|
|
return false;
|
|
}
|
|
|
|
mTextureSet = mTexturePool->AllocateSet(textureLayout);
|
|
if (mTextureSet == nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: texture set failed");
|
|
DestroyResources();
|
|
return false;
|
|
}
|
|
|
|
DescriptorSetLayoutBinding samplerBinding = {};
|
|
samplerBinding.binding = 0;
|
|
samplerBinding.type = static_cast<uint32_t>(DescriptorType::Sampler);
|
|
samplerBinding.count = 1;
|
|
samplerBinding.visibility = static_cast<uint32_t>(ShaderVisibility::Pixel);
|
|
|
|
DescriptorSetLayoutDesc samplerLayout = {};
|
|
samplerLayout.bindings = &samplerBinding;
|
|
samplerLayout.bindingCount = 1;
|
|
|
|
DescriptorPoolDesc samplerPoolDesc = {};
|
|
samplerPoolDesc.type = DescriptorHeapType::Sampler;
|
|
samplerPoolDesc.descriptorCount = 1;
|
|
samplerPoolDesc.shaderVisible = true;
|
|
mSamplerPool = mDevice->CreateDescriptorPool(samplerPoolDesc);
|
|
if (mSamplerPool == nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: sampler pool failed");
|
|
DestroyResources();
|
|
return false;
|
|
}
|
|
|
|
mSamplerSet = mSamplerPool->AllocateSet(samplerLayout);
|
|
if (mSamplerSet == nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: sampler set failed");
|
|
DestroyResources();
|
|
return false;
|
|
}
|
|
mSamplerSet->UpdateSampler(0, mSampler);
|
|
|
|
DescriptorSetLayoutDesc setLayouts[] = { textureLayout, samplerLayout };
|
|
RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
|
pipelineLayoutDesc.setLayouts = setLayouts;
|
|
pipelineLayoutDesc.setLayoutCount = 2;
|
|
mPipelineLayout = mDevice->CreatePipelineLayout(pipelineLayoutDesc);
|
|
if (mPipelineLayout == nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: pipeline layout failed");
|
|
DestroyResources();
|
|
return false;
|
|
}
|
|
|
|
GraphicsPipelineDesc pipelineDesc = {};
|
|
pipelineDesc.pipelineLayout = mPipelineLayout;
|
|
pipelineDesc.topologyType = static_cast<uint32_t>(PrimitiveTopologyType::Triangle);
|
|
pipelineDesc.renderTargetCount = 1;
|
|
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
|
|
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(Format::Unknown);
|
|
pipelineDesc.sampleCount = 1;
|
|
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(FillMode::Solid);
|
|
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(CullMode::None);
|
|
pipelineDesc.rasterizerState.frontFace = static_cast<uint32_t>(FrontFace::CounterClockwise);
|
|
pipelineDesc.rasterizerState.depthClipEnable = true;
|
|
pipelineDesc.blendState.blendEnable = false;
|
|
pipelineDesc.blendState.colorWriteMask = static_cast<uint8_t>(ColorWriteMask::All);
|
|
pipelineDesc.depthStencilState.depthTestEnable = false;
|
|
pipelineDesc.depthStencilState.depthWriteEnable = false;
|
|
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(ComparisonFunc::Always);
|
|
|
|
if (mBackendType == RHIType::D3D12) {
|
|
pipelineDesc.vertexShader.source.assign(
|
|
kShadowMapDebugHlsl,
|
|
kShadowMapDebugHlsl + strlen(kShadowMapDebugHlsl));
|
|
pipelineDesc.vertexShader.sourceLanguage = XCEngine::RHI::ShaderLanguage::HLSL;
|
|
pipelineDesc.vertexShader.entryPoint = L"MainVS";
|
|
pipelineDesc.vertexShader.profile = L"vs_5_0";
|
|
|
|
pipelineDesc.fragmentShader.source.assign(
|
|
kShadowMapDebugHlsl,
|
|
kShadowMapDebugHlsl + strlen(kShadowMapDebugHlsl));
|
|
pipelineDesc.fragmentShader.sourceLanguage = XCEngine::RHI::ShaderLanguage::HLSL;
|
|
pipelineDesc.fragmentShader.entryPoint = L"MainPS";
|
|
pipelineDesc.fragmentShader.profile = L"ps_5_0";
|
|
} else {
|
|
pipelineDesc.vertexShader.source.assign(
|
|
kShadowMapDebugVertexShader,
|
|
kShadowMapDebugVertexShader + strlen(kShadowMapDebugVertexShader));
|
|
pipelineDesc.vertexShader.sourceLanguage = XCEngine::RHI::ShaderLanguage::GLSL;
|
|
pipelineDesc.vertexShader.entryPoint = L"main";
|
|
if (mBackendType == RHIType::OpenGL) {
|
|
pipelineDesc.vertexShader.profile = L"vs_4_30";
|
|
}
|
|
|
|
pipelineDesc.fragmentShader.source.assign(
|
|
kShadowMapDebugFragmentShader,
|
|
kShadowMapDebugFragmentShader + strlen(kShadowMapDebugFragmentShader));
|
|
pipelineDesc.fragmentShader.sourceLanguage = XCEngine::RHI::ShaderLanguage::GLSL;
|
|
pipelineDesc.fragmentShader.entryPoint = L"main";
|
|
if (mBackendType == RHIType::OpenGL) {
|
|
pipelineDesc.fragmentShader.profile = L"fs_4_30";
|
|
}
|
|
}
|
|
|
|
mPipelineState = mDevice->CreatePipelineState(pipelineDesc);
|
|
if (mPipelineState == nullptr || !mPipelineState->IsValid()) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: pipeline state failed");
|
|
DestroyResources();
|
|
return false;
|
|
}
|
|
|
|
Log("[TEST] ShadowMapDebugOverlayPass: CreateResources success");
|
|
return true;
|
|
}
|
|
|
|
void DestroyResources() {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: DestroyResources begin");
|
|
if (mPipelineState != nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: destroy pipeline state");
|
|
mPipelineState->Shutdown();
|
|
delete mPipelineState;
|
|
mPipelineState = nullptr;
|
|
}
|
|
|
|
if (mPipelineLayout != nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: destroy pipeline layout");
|
|
mPipelineLayout->Shutdown();
|
|
delete mPipelineLayout;
|
|
mPipelineLayout = nullptr;
|
|
}
|
|
|
|
if (mTextureSet != nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: destroy texture set");
|
|
mTextureSet->Shutdown();
|
|
delete mTextureSet;
|
|
mTextureSet = nullptr;
|
|
}
|
|
|
|
if (mTexturePool != nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: destroy texture pool");
|
|
mTexturePool->Shutdown();
|
|
delete mTexturePool;
|
|
mTexturePool = nullptr;
|
|
}
|
|
|
|
if (mSamplerSet != nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: destroy sampler set");
|
|
mSamplerSet->Shutdown();
|
|
delete mSamplerSet;
|
|
mSamplerSet = nullptr;
|
|
}
|
|
|
|
if (mSamplerPool != nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: destroy sampler pool");
|
|
mSamplerPool->Shutdown();
|
|
delete mSamplerPool;
|
|
mSamplerPool = nullptr;
|
|
}
|
|
|
|
if (mSampler != nullptr) {
|
|
Log("[TEST] ShadowMapDebugOverlayPass: destroy sampler");
|
|
mSampler->Shutdown();
|
|
delete mSampler;
|
|
mSampler = nullptr;
|
|
}
|
|
|
|
mDevice = nullptr;
|
|
mBackendType = RHIType::D3D12;
|
|
Log("[TEST] ShadowMapDebugOverlayPass: DestroyResources end");
|
|
}
|
|
|
|
RHIDevice* mDevice = nullptr;
|
|
RHIType mBackendType = RHIType::D3D12;
|
|
RHIPipelineLayout* mPipelineLayout = nullptr;
|
|
RHIPipelineState* mPipelineState = nullptr;
|
|
RHIDescriptorPool* mTexturePool = nullptr;
|
|
RHIDescriptorSet* mTextureSet = nullptr;
|
|
RHIDescriptorPool* mSamplerPool = nullptr;
|
|
RHIDescriptorSet* mSamplerSet = nullptr;
|
|
RHISampler* mSampler = nullptr;
|
|
};
|
|
|
|
class DirectionalShadowSceneTest : public RHIIntegrationFixture {
|
|
protected:
|
|
void SetUp() override;
|
|
void TearDown() override;
|
|
void RenderFrame() override;
|
|
void RenderSceneFrame(bool visualizeShadowMap);
|
|
|
|
private:
|
|
void BuildScene();
|
|
RHIResourceView* GetCurrentBackBufferView();
|
|
|
|
std::unique_ptr<Scene> mScene;
|
|
std::unique_ptr<SceneRenderer> mSceneRenderer;
|
|
RenderPassSequence mShadowMapDebugPasses;
|
|
ShadowMapDebugOverlayPass* mShadowMapDebugOverlayPass = nullptr;
|
|
bool mLoggedShadowProbe = false;
|
|
std::vector<RHIResourceView*> mBackBufferViews;
|
|
RHITexture* mDepthTexture = nullptr;
|
|
RHIResourceView* mDepthView = nullptr;
|
|
Mesh* mGroundMesh = nullptr;
|
|
Mesh* mCubeMesh = nullptr;
|
|
Material* mGroundMaterial = nullptr;
|
|
Material* mCasterMaterial = nullptr;
|
|
};
|
|
|
|
void DirectionalShadowSceneTest::SetUp() {
|
|
RHIIntegrationFixture::SetUp();
|
|
|
|
mSceneRenderer = std::make_unique<SceneRenderer>();
|
|
mScene = std::make_unique<Scene>("DirectionalShadowScene");
|
|
|
|
mGroundMesh = CreateGroundMesh();
|
|
ASSERT_NE(mGroundMesh, nullptr);
|
|
|
|
mCubeMesh = CreateCubeMesh();
|
|
ASSERT_NE(mCubeMesh, nullptr);
|
|
|
|
mGroundMaterial = CreateForwardLitMaterial(
|
|
"DirectionalShadowGround",
|
|
"Tests/Rendering/DirectionalShadowGround.material",
|
|
Vector4(0.92f, 0.93f, 0.96f, 1.0f));
|
|
mCasterMaterial = CreateForwardLitMaterial(
|
|
"DirectionalShadowCaster",
|
|
"Tests/Rendering/DirectionalShadowCaster.material",
|
|
Vector4(0.72f, 0.22f, 0.16f, 1.0f));
|
|
|
|
auto shadowMapDebugPass = std::make_unique<ShadowMapDebugOverlayPass>();
|
|
mShadowMapDebugOverlayPass = shadowMapDebugPass.get();
|
|
mShadowMapDebugPasses.AddPass(std::move(shadowMapDebugPass));
|
|
|
|
BuildScene();
|
|
|
|
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;
|
|
mDepthTexture = GetDevice()->CreateTexture(depthDesc);
|
|
ASSERT_NE(mDepthTexture, nullptr);
|
|
|
|
ResourceViewDesc depthViewDesc = {};
|
|
depthViewDesc.format = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
|
|
depthViewDesc.dimension = ResourceViewDimension::Texture2D;
|
|
depthViewDesc.mipLevel = 0;
|
|
mDepthView = GetDevice()->CreateDepthStencilView(mDepthTexture, depthViewDesc);
|
|
ASSERT_NE(mDepthView, nullptr);
|
|
|
|
mBackBufferViews.resize(2, nullptr);
|
|
}
|
|
|
|
void DirectionalShadowSceneTest::TearDown() {
|
|
mSceneRenderer.reset();
|
|
|
|
if (mShadowMapDebugOverlayPass != nullptr) {
|
|
mShadowMapDebugOverlayPass->ReleaseResources();
|
|
mShadowMapDebugOverlayPass = nullptr;
|
|
}
|
|
|
|
if (mDepthView != nullptr) {
|
|
mDepthView->Shutdown();
|
|
delete mDepthView;
|
|
mDepthView = nullptr;
|
|
}
|
|
|
|
if (mDepthTexture != nullptr) {
|
|
mDepthTexture->Shutdown();
|
|
delete mDepthTexture;
|
|
mDepthTexture = nullptr;
|
|
}
|
|
|
|
for (RHIResourceView*& backBufferView : mBackBufferViews) {
|
|
if (backBufferView != nullptr) {
|
|
backBufferView->Shutdown();
|
|
delete backBufferView;
|
|
backBufferView = nullptr;
|
|
}
|
|
}
|
|
mBackBufferViews.clear();
|
|
|
|
mScene.reset();
|
|
|
|
delete mGroundMaterial;
|
|
mGroundMaterial = nullptr;
|
|
delete mCasterMaterial;
|
|
mCasterMaterial = nullptr;
|
|
delete mGroundMesh;
|
|
mGroundMesh = nullptr;
|
|
delete mCubeMesh;
|
|
mCubeMesh = nullptr;
|
|
|
|
RHIIntegrationFixture::TearDown();
|
|
}
|
|
|
|
void DirectionalShadowSceneTest::BuildScene() {
|
|
ASSERT_NE(mScene, nullptr);
|
|
ASSERT_NE(mGroundMesh, nullptr);
|
|
ASSERT_NE(mCubeMesh, nullptr);
|
|
ASSERT_NE(mGroundMaterial, nullptr);
|
|
ASSERT_NE(mCasterMaterial, nullptr);
|
|
|
|
GameObject* cameraObject = mScene->CreateGameObject("MainCamera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetFieldOfView(42.0f);
|
|
camera->SetNearClipPlane(0.1f);
|
|
camera->SetFarClipPlane(40.0f);
|
|
camera->SetClearColor(XCEngine::Math::Color(0.03f, 0.03f, 0.05f, 1.0f));
|
|
cameraObject->GetTransform()->SetLocalPosition(Vector3(0.2f, 3.4f, -7.2f));
|
|
cameraObject->GetTransform()->SetLocalRotation(
|
|
Quaternion::LookRotation(Vector3(-0.12f, -0.14f, 1.0f).Normalized()));
|
|
|
|
GameObject* lightObject = mScene->CreateGameObject("MainDirectionalLight");
|
|
auto* light = lightObject->AddComponent<LightComponent>();
|
|
light->SetLightType(LightType::Directional);
|
|
light->SetColor(XCEngine::Math::Color(1.0f, 1.0f, 0.97f, 1.0f));
|
|
light->SetIntensity(2.3f);
|
|
light->SetCastsShadows(true);
|
|
lightObject->GetTransform()->SetLocalRotation(
|
|
Quaternion::LookRotation(Vector3(0.84f, -0.52f, -0.14f).Normalized()));
|
|
|
|
GameObject* groundObject = mScene->CreateGameObject("Ground");
|
|
auto* groundMeshFilter = groundObject->AddComponent<MeshFilterComponent>();
|
|
auto* groundMeshRenderer = groundObject->AddComponent<MeshRendererComponent>();
|
|
groundObject->GetTransform()->SetLocalPosition(Vector3(0.0f, -0.18f, 10.5f));
|
|
groundObject->GetTransform()->SetLocalScale(Vector3(12.0f, 0.18f, 9.5f));
|
|
groundMeshFilter->SetMesh(ResourceHandle<Mesh>(mCubeMesh));
|
|
groundMeshRenderer->SetMaterial(0, mGroundMaterial);
|
|
groundMeshRenderer->SetCastShadows(false);
|
|
groundMeshRenderer->SetReceiveShadows(true);
|
|
|
|
GameObject* casterObject = mScene->CreateGameObject("CasterCube");
|
|
casterObject->GetTransform()->SetLocalPosition(Vector3(-1.2f, 1.71f, 8.8f));
|
|
casterObject->GetTransform()->SetLocalScale(Vector3(1.8f, 3.6f, 1.8f));
|
|
auto* casterMeshFilter = casterObject->AddComponent<MeshFilterComponent>();
|
|
auto* casterMeshRenderer = casterObject->AddComponent<MeshRendererComponent>();
|
|
casterMeshFilter->SetMesh(ResourceHandle<Mesh>(mCubeMesh));
|
|
casterMeshRenderer->SetMaterial(0, mCasterMaterial);
|
|
casterMeshRenderer->SetCastShadows(true);
|
|
casterMeshRenderer->SetReceiveShadows(true);
|
|
}
|
|
|
|
RHIResourceView* DirectionalShadowSceneTest::GetCurrentBackBufferView() {
|
|
const int backBufferIndex = GetCurrentBackBufferIndex();
|
|
if (backBufferIndex < 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (static_cast<size_t>(backBufferIndex) >= mBackBufferViews.size()) {
|
|
mBackBufferViews.resize(static_cast<size_t>(backBufferIndex) + 1, nullptr);
|
|
}
|
|
|
|
if (mBackBufferViews[backBufferIndex] == nullptr) {
|
|
ResourceViewDesc viewDesc = {};
|
|
viewDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
|
|
viewDesc.dimension = ResourceViewDimension::Texture2D;
|
|
viewDesc.mipLevel = 0;
|
|
mBackBufferViews[backBufferIndex] = GetDevice()->CreateRenderTargetView(GetCurrentBackBuffer(), viewDesc);
|
|
}
|
|
|
|
return mBackBufferViews[backBufferIndex];
|
|
}
|
|
|
|
void DirectionalShadowSceneTest::RenderFrame() {
|
|
RenderSceneFrame(false);
|
|
}
|
|
|
|
void DirectionalShadowSceneTest::RenderSceneFrame(bool visualizeShadowMap) {
|
|
ASSERT_NE(mScene, nullptr);
|
|
ASSERT_NE(mSceneRenderer, nullptr);
|
|
|
|
RHICommandList* commandList = GetCommandList();
|
|
ASSERT_NE(commandList, nullptr);
|
|
|
|
commandList->Reset();
|
|
|
|
RenderSurface surface(kFrameWidth, kFrameHeight);
|
|
surface.SetColorAttachment(GetCurrentBackBufferView());
|
|
surface.SetDepthAttachment(mDepthView);
|
|
|
|
RenderContext renderContext = {};
|
|
renderContext.device = GetDevice();
|
|
renderContext.commandList = commandList;
|
|
renderContext.commandQueue = GetCommandQueue();
|
|
renderContext.backendType = GetBackendType();
|
|
|
|
std::vector<CameraRenderRequest> requests =
|
|
mSceneRenderer->BuildRenderRequests(*mScene, nullptr, renderContext, surface);
|
|
ASSERT_FALSE(requests.empty());
|
|
|
|
if (!mLoggedShadowProbe) {
|
|
LogShadowAlignmentAnalysis(*mScene, requests[0]);
|
|
mLoggedShadowProbe = true;
|
|
}
|
|
|
|
if (visualizeShadowMap) {
|
|
requests[0].overlayPasses = &mShadowMapDebugPasses;
|
|
}
|
|
ASSERT_TRUE(mSceneRenderer->Render(requests));
|
|
|
|
Log("[TEST] DirectionalShadowSceneTest: closing command list");
|
|
commandList->Close();
|
|
void* commandLists[] = { commandList };
|
|
Log("[TEST] DirectionalShadowSceneTest: executing command list");
|
|
GetCommandQueue()->ExecuteCommandLists(1, commandLists);
|
|
Log("[TEST] DirectionalShadowSceneTest: execute submitted");
|
|
}
|
|
|
|
TEST_P(DirectionalShadowSceneTest, RenderDirectionalShadowScene) {
|
|
RHICommandQueue* commandQueue = GetCommandQueue();
|
|
RHISwapChain* swapChain = GetSwapChain();
|
|
const int targetFrameCount = 30;
|
|
const char* screenshotFilename = GetScreenshotFilename(GetBackendType());
|
|
const int comparisonThreshold = GetComparisonThreshold(GetBackendType());
|
|
|
|
for (int frameCount = 0; frameCount <= targetFrameCount; ++frameCount) {
|
|
if (frameCount > 0) {
|
|
commandQueue->WaitForPreviousFrame();
|
|
}
|
|
|
|
BeginRender();
|
|
RenderFrame();
|
|
|
|
if (frameCount >= targetFrameCount) {
|
|
commandQueue->WaitForIdle();
|
|
ASSERT_TRUE(TakeScreenshot(screenshotFilename));
|
|
ASSERT_TRUE(CompareWithGoldenTemplate(screenshotFilename, "GT.ppm", static_cast<float>(comparisonThreshold)));
|
|
break;
|
|
}
|
|
|
|
swapChain->Present(0, 0);
|
|
}
|
|
}
|
|
|
|
TEST_P(DirectionalShadowSceneTest, RenderDirectionalShadowMapDebug) {
|
|
RHICommandQueue* commandQueue = GetCommandQueue();
|
|
RHISwapChain* swapChain = GetSwapChain();
|
|
const int targetFrameCount = 30;
|
|
const char* screenshotFilename = GetShadowMapScreenshotFilename(GetBackendType());
|
|
|
|
for (int frameCount = 0; frameCount <= targetFrameCount; ++frameCount) {
|
|
if (frameCount > 0) {
|
|
commandQueue->WaitForPreviousFrame();
|
|
}
|
|
|
|
BeginRender();
|
|
RenderSceneFrame(true);
|
|
|
|
if (frameCount >= targetFrameCount) {
|
|
commandQueue->WaitForIdle();
|
|
ASSERT_TRUE(TakeScreenshot(screenshotFilename));
|
|
break;
|
|
}
|
|
|
|
swapChain->Present(0, 0);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
INSTANTIATE_TEST_SUITE_P(D3D12, DirectionalShadowSceneTest, ::testing::Values(RHIType::D3D12));
|
|
INSTANTIATE_TEST_SUITE_P(OpenGL, DirectionalShadowSceneTest, ::testing::Values(RHIType::OpenGL));
|
|
#if defined(XCENGINE_SUPPORT_VULKAN)
|
|
INSTANTIATE_TEST_SUITE_P(Vulkan, DirectionalShadowSceneTest, ::testing::Values(RHIType::Vulkan));
|
|
#endif
|
|
|
|
GTEST_API_ int main(int argc, char** argv) {
|
|
return RunRenderingIntegrationTestMain(argc, argv);
|
|
}
|