Add shadow surface reuse coverage for camera renderer

This commit is contained in:
2026-04-05 14:07:13 +08:00
parent 551eefbaa1
commit daa54e0230
2 changed files with 147 additions and 19 deletions

View File

@@ -20,7 +20,7 @@ struct CameraRenderer::DirectionalShadowSurfaceResources {
uint32_t height = 0;
RHI::RHITexture* depthTexture = nullptr;
RHI::RHIResourceView* depthView = nullptr;
RHI::RHIResourceView* shaderView = nullptr;
RHI::RHIResourceView* depthShaderView = nullptr;
RenderSurface surface = {};
DirectionalShadowSurfaceResources() = default;
@@ -37,22 +37,22 @@ struct CameraRenderer::DirectionalShadowSurfaceResources {
height == plan.mapHeight &&
depthTexture != nullptr &&
depthView != nullptr &&
shaderView != nullptr;
depthShaderView != nullptr;
}
void Reset() {
if (shaderView != nullptr) {
shaderView->Shutdown();
delete shaderView;
shaderView = nullptr;
}
if (depthView != nullptr) {
depthView->Shutdown();
delete depthView;
depthView = nullptr;
}
if (depthShaderView != nullptr) {
depthShaderView->Shutdown();
delete depthShaderView;
depthShaderView = nullptr;
}
if (depthTexture != nullptr) {
depthTexture->Shutdown();
delete depthTexture;
@@ -68,6 +68,8 @@ struct CameraRenderer::DirectionalShadowSurfaceResources {
namespace {
constexpr RHI::Format kDirectionalShadowMapFormat = RHI::Format::D32_Float;
bool InitializePassSequence(
RenderPassSequence* sequence,
const RenderContext& context,
@@ -315,7 +317,7 @@ CameraRenderer::DirectionalShadowSurfaceResources* CameraRenderer::AcquireDirect
depthDesc.depth = 1;
depthDesc.mipLevels = 1;
depthDesc.arraySize = 1;
depthDesc.format = static_cast<uint32_t>(RHI::Format::D24_UNorm_S8_UInt);
depthDesc.format = static_cast<uint32_t>(kDirectionalShadowMapFormat);
depthDesc.textureType = static_cast<uint32_t>(RHI::TextureType::Texture2D);
depthDesc.sampleCount = 1;
depthDesc.sampleQuality = 0;
@@ -327,7 +329,7 @@ CameraRenderer::DirectionalShadowSurfaceResources* CameraRenderer::AcquireDirect
}
RHI::ResourceViewDesc depthViewDesc = {};
depthViewDesc.format = static_cast<uint32_t>(RHI::Format::D24_UNorm_S8_UInt);
depthViewDesc.format = static_cast<uint32_t>(kDirectionalShadowMapFormat);
depthViewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
depthViewDesc.mipLevel = 0;
RHI::RHIResourceView* depthView =
@@ -338,13 +340,12 @@ CameraRenderer::DirectionalShadowSurfaceResources* CameraRenderer::AcquireDirect
return nullptr;
}
RHI::ResourceViewDesc shaderViewDesc = {};
shaderViewDesc.format = depthDesc.format;
shaderViewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
shaderViewDesc.mipLevel = 0;
RHI::RHIResourceView* shaderView =
context.device->CreateShaderResourceView(depthTexture, shaderViewDesc);
if (shaderView == nullptr) {
RHI::ResourceViewDesc depthShaderViewDesc = {};
depthShaderViewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
depthShaderViewDesc.mipLevel = 0;
RHI::RHIResourceView* depthShaderView =
context.device->CreateShaderResourceView(depthTexture, depthShaderViewDesc);
if (depthShaderView == nullptr) {
depthView->Shutdown();
delete depthView;
depthTexture->Shutdown();
@@ -358,7 +359,7 @@ CameraRenderer::DirectionalShadowSurfaceResources* CameraRenderer::AcquireDirect
m_directionalShadowSurface->height = plan.mapHeight;
m_directionalShadowSurface->depthTexture = depthTexture;
m_directionalShadowSurface->depthView = depthView;
m_directionalShadowSurface->shaderView = shaderView;
m_directionalShadowSurface->depthShaderView = depthShaderView;
m_directionalShadowSurface->surface = RenderSurface(plan.mapWidth, plan.mapHeight);
m_directionalShadowSurface->surface.SetDepthAttachment(depthView);
}
@@ -400,6 +401,7 @@ bool CameraRenderer::Render(
}
resolvedShadowCaster.surface = directionalShadowSurface->surface;
resolvedShadowCaster.clearFlags = RenderClearFlags::Depth;
if (!resolvedShadowCaster.hasCameraDataOverride) {
resolvedShadowCaster.hasCameraDataOverride = true;
resolvedShadowCaster.cameraDataOverride = request.directionalShadow.cameraData;
@@ -417,7 +419,7 @@ bool CameraRenderer::Render(
if (request.directionalShadow.IsValid()) {
RHI::RHIResourceView* shadowMapView =
directionalShadowSurface != nullptr ? directionalShadowSurface->shaderView : nullptr;
directionalShadowSurface != nullptr ? directionalShadowSurface->depthShaderView : nullptr;
sceneData.lighting.mainDirectionalShadow =
BuildDirectionalShadowData(request.directionalShadow, shadowMapView);
}

View File

@@ -756,6 +756,132 @@ TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) {
EXPECT_EQ(allocationState->destroyTextureCalls, 1);
}
TEST(CameraRenderer_Test, ReusesDirectionalShadowSurfaceWhenPlanMatches) {
Scene scene("CameraRendererDirectionalShadowReuseScene");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
camera->SetDepth(2.0f);
auto pipelineState = std::make_shared<MockPipelineState>();
auto allocationState = std::make_shared<MockShadowAllocationState>();
MockShadowDevice device(allocationState);
RenderContext context = CreateValidContext();
context.device = &device;
CameraRenderer renderer(
std::make_unique<MockPipeline>(pipelineState),
std::make_unique<MockObjectIdPass>(pipelineState));
auto shadowPass = std::make_unique<MockScenePass>(pipelineState, "shadowCaster");
renderer.SetShadowCasterPass(std::move(shadowPass));
CameraRenderRequest request;
request.scene = &scene;
request.camera = camera;
request.context = context;
request.surface = RenderSurface(320, 180);
request.cameraDepth = camera->GetDepth();
request.directionalShadow.enabled = true;
request.directionalShadow.mapWidth = 256;
request.directionalShadow.mapHeight = 128;
request.directionalShadow.cameraData.viewportWidth = 256;
request.directionalShadow.cameraData.viewportHeight = 128;
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
ASSERT_TRUE(renderer.Render(request));
XCEngine::RHI::RHIResourceView* firstShadowMap = pipelineState->lastShadowMap;
ASSERT_NE(firstShadowMap, nullptr);
EXPECT_EQ(allocationState->createTextureCalls, 1);
EXPECT_EQ(allocationState->createDepthViewCalls, 1);
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 0);
EXPECT_EQ(allocationState->destroyTextureCalls, 0);
ASSERT_TRUE(renderer.Render(request));
EXPECT_EQ(allocationState->createTextureCalls, 1);
EXPECT_EQ(allocationState->createDepthViewCalls, 1);
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 0);
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 0);
EXPECT_EQ(allocationState->shutdownTextureCalls, 0);
EXPECT_EQ(allocationState->destroyDepthViewCalls, 0);
EXPECT_EQ(allocationState->destroyShaderViewCalls, 0);
EXPECT_EQ(allocationState->destroyTextureCalls, 0);
EXPECT_EQ(pipelineState->lastShadowMap, firstShadowMap);
}
TEST(CameraRenderer_Test, RecreatesDirectionalShadowSurfaceWhenPlanSizeChanges) {
Scene scene("CameraRendererDirectionalShadowResizeScene");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
camera->SetDepth(2.0f);
auto pipelineState = std::make_shared<MockPipelineState>();
auto allocationState = std::make_shared<MockShadowAllocationState>();
MockShadowDevice device(allocationState);
RenderContext context = CreateValidContext();
context.device = &device;
{
CameraRenderer renderer(
std::make_unique<MockPipeline>(pipelineState),
std::make_unique<MockObjectIdPass>(pipelineState));
auto shadowPass = std::make_unique<MockScenePass>(pipelineState, "shadowCaster");
renderer.SetShadowCasterPass(std::move(shadowPass));
CameraRenderRequest request;
request.scene = &scene;
request.camera = camera;
request.context = context;
request.surface = RenderSurface(320, 180);
request.cameraDepth = camera->GetDepth();
request.directionalShadow.enabled = true;
request.directionalShadow.mapWidth = 256;
request.directionalShadow.mapHeight = 128;
request.directionalShadow.cameraData.viewportWidth = 256;
request.directionalShadow.cameraData.viewportHeight = 128;
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
ASSERT_TRUE(renderer.Render(request));
XCEngine::RHI::RHIResourceView* firstShadowMap = pipelineState->lastShadowMap;
ASSERT_NE(firstShadowMap, nullptr);
request.directionalShadow.mapWidth = 512;
request.directionalShadow.mapHeight = 256;
request.directionalShadow.cameraData.viewportWidth = 512;
request.directionalShadow.cameraData.viewportHeight = 256;
ASSERT_TRUE(renderer.Render(request));
EXPECT_EQ(allocationState->createTextureCalls, 2);
EXPECT_EQ(allocationState->createDepthViewCalls, 2);
EXPECT_EQ(allocationState->createShaderViewCalls, 2);
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 1);
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 1);
EXPECT_EQ(allocationState->shutdownTextureCalls, 1);
EXPECT_EQ(allocationState->destroyDepthViewCalls, 1);
EXPECT_EQ(allocationState->destroyShaderViewCalls, 1);
EXPECT_EQ(allocationState->destroyTextureCalls, 1);
EXPECT_NE(pipelineState->lastShadowMap, firstShadowMap);
EXPECT_EQ(allocationState->lastTextureWidth, 512u);
EXPECT_EQ(allocationState->lastTextureHeight, 256u);
EXPECT_EQ(allocationState->lastTextureFormat, XCEngine::RHI::Format::D32_Float);
}
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 2);
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 2);
EXPECT_EQ(allocationState->shutdownTextureCalls, 2);
EXPECT_EQ(allocationState->destroyDepthViewCalls, 2);
EXPECT_EQ(allocationState->destroyShaderViewCalls, 2);
EXPECT_EQ(allocationState->destroyTextureCalls, 2);
}
TEST(CameraRenderer_Test, StopsRenderingWhenShadowCasterRequestIsInvalid) {
Scene scene("CameraRendererInvalidShadowScene");