/
redgpu
/
ezEngine
Обзор
Документация
Войти
/
redgpu
/
ezEngine
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
Code/UnitTests/RendererTest/DataStructures/ResourceStateTrackerTest.cpp
530 строк
26 KB
Sanakan8472
Render Graph (#1948)
03 июн 2026, 10:19
Не верифицирован
03 июн 2026, 10:19
60bb12c
Код
Авторство
О чём код?
#include <RendererTest/RendererTestPCH.h> #include <RendererFoundation/Descriptors/Descriptors.h> #include <RendererFoundation/Descriptors/Enumerations.h> #include <RendererFoundation/Device/Device.h> #include <RendererFoundation/Resources/Texture.h> #include <RendererFoundation/Utils/ResourceStateTracker.h> #include <RendererTest/TestClass/SimpleRendererTest.h> EZ_CREATE_SIMPLE_RENDERER_TEST_GROUP(ResourceStateTracker) // ============================================================ // IsTextureBarrierNeeded (static, no device needed) // ============================================================ EZ_CREATE_SIMPLE_RENDERER_TEST(ResourceStateTracker, IsTextureBarrierNeeded) { using SRS = ezGALResourceStateTracker::SubResourceState; EZ_TEST_BLOCK(ezTestBlock::Enabled, "Same read state - no barrier") { SRS oldState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsTextureBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Read to write - barrier needed") { SRS oldState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; SRS newState = {ezGALResourceState::RenderTarget, ezGALShaderStageFlags::Auto}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsTextureBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Write to read - barrier needed") { SRS oldState = {ezGALResourceState::RenderTarget, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsTextureBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Write to write - barrier needed") { SRS oldState = {ezGALResourceState::RenderTarget, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::CopyDestination, ezGALShaderStageFlags::Auto}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsTextureBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "UAV write-after-write - barrier needed") { SRS oldState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; SRS newState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsTextureBarrierNeeded(oldState, newState, true)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "UAV write-after-write without force - no barrier") { SRS oldState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; SRS newState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsTextureBarrierNeeded(oldState, newState, false)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Same read state but uncovered stage - barrier needed") { // PS is not covered by CS, so an execution dependency is needed. SRS oldState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::ComputeShader}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsTextureBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Same read state with Auto covers everything") { SRS oldState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsTextureBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "VS covers later graphics stages") { // VS is earlier in the pipeline than PS, so PS is implicitly covered. SRS oldState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::VertexShader}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsTextureBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "PS does not cover VS") { // PS is later in the pipeline than VS, so VS is NOT covered. SRS oldState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::VertexShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsTextureBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Different read states - barrier needed") { SRS oldState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; SRS newState = {ezGALResourceState::CopySource, ezGALShaderStageFlags::Auto}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsTextureBarrierNeeded(oldState, newState)); } } // ============================================================ // IsBufferBarrierNeeded (static, no device needed) // ============================================================ EZ_CREATE_SIMPLE_RENDERER_TEST(ResourceStateTracker, IsBufferBarrierNeeded) { using SRS = ezGALResourceStateTracker::SubResourceState; EZ_TEST_BLOCK(ezTestBlock::Enabled, "Same read state - no barrier") { SRS oldState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Superset old state covers subset new state - no barrier") { SRS oldState = {ezGALResourceState::ShaderResource | ezGALResourceState::ConstantBuffer, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::ConstantBuffer, ezGALShaderStageFlags::VertexShader}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Exact match with Auto covers explicit stage - no barrier") { SRS oldState = {ezGALResourceState::ConstantBuffer, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::ConstantBuffer, ezGALShaderStageFlags::VertexShader}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Read to write - barrier needed") { SRS oldState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; SRS newState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Write to read - barrier needed") { SRS oldState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Write to write - barrier needed") { SRS oldState = {ezGALResourceState::CopyDestination, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "UAV write-after-write - barrier needed") { SRS oldState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; SRS newState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState, true)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "UAV write-after-write without force - no barrier") { SRS oldState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; SRS newState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState, false)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "New state not subset of old - barrier needed") { SRS oldState = {ezGALResourceState::ConstantBuffer, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Same read state but uncovered stage - barrier needed") { SRS oldState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::ComputeShader}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "VS covers later graphics stages - no barrier") { SRS oldState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::VertexShader}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Combined SRV+CBV covers SRV subset - no barrier") { SRS oldState = {ezGALResourceState::ShaderResource | ezGALResourceState::ConstantBuffer, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Combined SRV+VB+IB covers VB subset - no barrier") { SRS oldState = {ezGALResourceState::ShaderResource | ezGALResourceState::VertexBuffer | ezGALResourceState::IndexBuffer, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::VertexBuffer, ezGALShaderStageFlags::Auto}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Combined SRV+VB+IB covers SRV+IB subset - no barrier") { SRS oldState = {ezGALResourceState::ShaderResource | ezGALResourceState::VertexBuffer | ezGALResourceState::IndexBuffer, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::ShaderResource | ezGALResourceState::IndexBuffer, ezGALShaderStageFlags::Auto}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Combined read old state does not cover extra new read state - barrier needed") { SRS oldState = {ezGALResourceState::ShaderResource | ezGALResourceState::ConstantBuffer, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::ShaderResource | ezGALResourceState::IndexBuffer, ezGALShaderStageFlags::Auto}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Combined read old state to write - barrier needed") { SRS oldState = {ezGALResourceState::ShaderResource | ezGALResourceState::VertexBuffer | ezGALResourceState::IndexBuffer, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Combined SRV+DrawIndirect covers DrawIndirect - no barrier") { SRS oldState = {ezGALResourceState::ShaderResource | ezGALResourceState::DrawIndirect, ezGALShaderStageFlags::Auto}; SRS newState = {ezGALResourceState::DrawIndirect, ezGALShaderStageFlags::Auto}; EZ_TEST_BOOL(!ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Combined read old state with uncovered stage - barrier needed") { SRS oldState = {ezGALResourceState::ShaderResource | ezGALResourceState::ConstantBuffer, ezGALShaderStageFlags::ComputeShader}; SRS newState = {ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader}; EZ_TEST_BOOL(ezGALResourceStateTracker::IsBufferBarrierNeeded(oldState, newState)); } } // ============================================================ // AreStagesCovered (static, no device needed) // ============================================================ EZ_CREATE_SIMPLE_RENDERER_TEST(ResourceStateTracker, AreStagesCovered) { EZ_TEST_BLOCK(ezTestBlock::Enabled, "Auto covers everything") { EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::Auto, ezGALShaderStageFlags::PixelShader)); EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::Auto, ezGALShaderStageFlags::ComputeShader)); EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::Auto, ezGALShaderStageFlags::VertexShader)); EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered( ezGALShaderStageFlags::Auto, ezGALShaderStageFlags::VertexShader | ezGALShaderStageFlags::PixelShader)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Requiring Auto is not covered by explicit stages") { EZ_TEST_BOOL(!ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::PixelShader, ezGALShaderStageFlags::Auto)); EZ_TEST_BOOL(!ezGALResourceStateTracker::AreStagesCovered( ezGALShaderStageFlags::VertexShader | ezGALShaderStageFlags::PixelShader, ezGALShaderStageFlags::Auto)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Exact match is covered") { EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::PixelShader, ezGALShaderStageFlags::PixelShader)); EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::ComputeShader, ezGALShaderStageFlags::ComputeShader)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Superset covers subset") { EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered( ezGALShaderStageFlags::VertexShader | ezGALShaderStageFlags::PixelShader, ezGALShaderStageFlags::PixelShader)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Earlier graphics stage covers later stages") { // VS is the earliest, it covers HS, DS, GS, PS. EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::VertexShader, ezGALShaderStageFlags::PixelShader)); EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::VertexShader, ezGALShaderStageFlags::GeometryShader)); EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::HullShader, ezGALShaderStageFlags::PixelShader)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Later graphics stage does not cover earlier stages") { EZ_TEST_BOOL(!ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::PixelShader, ezGALShaderStageFlags::VertexShader)); EZ_TEST_BOOL(!ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::GeometryShader, ezGALShaderStageFlags::VertexShader)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Compute is never covered by graphics and vice versa") { EZ_TEST_BOOL(!ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::VertexShader, ezGALShaderStageFlags::ComputeShader)); EZ_TEST_BOOL(!ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::PixelShader, ezGALShaderStageFlags::ComputeShader)); EZ_TEST_BOOL(!ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::ComputeShader, ezGALShaderStageFlags::PixelShader)); EZ_TEST_BOOL(!ezGALResourceStateTracker::AreStagesCovered(ezGALShaderStageFlags::ComputeShader, ezGALShaderStageFlags::VertexShader)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Combined graphics + compute does not cross-cover") { // Having both VS and CS covered still doesn't let CS cover PS (different pipelines). // But VS does cover PS, so this should be true. EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered( ezGALShaderStageFlags::VertexShader | ezGALShaderStageFlags::ComputeShader, ezGALShaderStageFlags::PixelShader)); // Requiring both CS and PS: VS covers PS, CS covers CS -> covered. EZ_TEST_BOOL(ezGALResourceStateTracker::AreStagesCovered( ezGALShaderStageFlags::VertexShader | ezGALShaderStageFlags::ComputeShader, ezGALShaderStageFlags::PixelShader | ezGALShaderStageFlags::ComputeShader)); } } // ============================================================ // Buffer state tracking (needs a device for resource creation) // ============================================================ EZ_CREATE_SIMPLE_RENDERER_TEST(ResourceStateTracker, BufferStateTracking) { ezGALDevice* pDevice = ezGALDevice::GetDefaultDevice(); ezGALResourceStateTracker tracker(pDevice); // Helper: count emitted barriers ezUInt32 uiBarrierCount = 0; ezGALBufferBarrier lastBarrier; auto bufferCallback = [&](const ezGALBufferBarrier& barrier) { uiBarrierCount++; lastBarrier = barrier; }; // Create a structured buffer for testing. ShaderResource gives it a default state of ShaderResource. ezGALBufferCreationDescription bufDesc; bufDesc.m_uiStructSize = sizeof(ezUInt32); bufDesc.m_uiTotalSize = 256; bufDesc.m_BufferFlags = ezGALBufferUsageFlags::StructuredBuffer | ezGALBufferUsageFlags::ShaderResource | ezGALBufferUsageFlags::UnorderedAccess; bufDesc.m_ResourceAccess.m_bImmutable = false; ezGALBufferHandle hBuffer = pDevice->CreateBuffer(bufDesc); EZ_TEST_BOOL(!hBuffer.IsInvalidated()); EZ_SCOPE_EXIT(pDevice->DestroyBuffer(hBuffer)); EZ_TEST_BLOCK(ezTestBlock::Enabled, "First access initializes from GetDefaultState") { // The buffer's default state is UnorderedAccess (because UAV flag is set and it's mutable). // Transitioning to ShaderResource should emit a barrier from the default. uiBarrierCount = 0; tracker.ChangeState(hBuffer, ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader, bufferCallback); EZ_TEST_INT(uiBarrierCount, 1); EZ_TEST_BOOL(lastBarrier.m_StateAfter.IsSet(ezGALResourceState::ShaderResource)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Same state does not emit barrier") { uiBarrierCount = 0; tracker.ChangeState(hBuffer, ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader, bufferCallback); EZ_TEST_INT(uiBarrierCount, 0); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "State change emits barrier") { uiBarrierCount = 0; tracker.ChangeState(hBuffer, ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader, bufferCallback); EZ_TEST_INT(uiBarrierCount, 1); EZ_TEST_BOOL(lastBarrier.m_StateBefore.IsSet(ezGALResourceState::ShaderResource)); EZ_TEST_BOOL(lastBarrier.m_StateAfter.IsSet(ezGALResourceState::UnorderedAccess)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "UAV to UAV emits write-after-write barrier") { uiBarrierCount = 0; tracker.ChangeState(hBuffer, ezGALResourceState::UnorderedAccess, ezGALShaderStageFlags::ComputeShader, bufferCallback); EZ_TEST_INT(uiBarrierCount, 1); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "SetInitialBufferState overrides tracked state") { tracker.SetInitialBufferState(hBuffer, ezGALResourceState::CopySource, ezGALShaderStageFlags::Auto); uiBarrierCount = 0; tracker.ChangeState(hBuffer, ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader, bufferCallback); EZ_TEST_INT(uiBarrierCount, 1); EZ_TEST_BOOL(lastBarrier.m_StateBefore.IsSet(ezGALResourceState::CopySource)); EZ_TEST_BOOL(lastBarrier.m_StateAfter.IsSet(ezGALResourceState::ShaderResource)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Clear removes all tracking") { tracker.Clear(); // After clear, accessing the buffer should re-initialize from default. uiBarrierCount = 0; tracker.ChangeState(hBuffer, ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader, bufferCallback); // Should emit a barrier from the default state. EZ_TEST_INT(uiBarrierCount, 1); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "RevertBufferState transitions back to default") { // Current state is ShaderResource. Revert should go back to the buffer's default (UAV). uiBarrierCount = 0; tracker.RevertBufferState(bufferCallback); EZ_TEST_INT(uiBarrierCount, 1); EZ_TEST_BOOL(lastBarrier.m_StateBefore.IsSet(ezGALResourceState::ShaderResource)); EZ_TEST_BOOL(lastBarrier.m_StateAfter.IsSet(ezGALResourceState::UnorderedAccess)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "GetBufferState returns tracked state") { tracker.Clear(); EZ_TEST_BOOL(tracker.GetBufferState(hBuffer) == nullptr); tracker.ChangeState(hBuffer, ezGALResourceState::CopyDestination, ezGALShaderStageFlags::Auto, bufferCallback); const auto* pState = tracker.GetBufferState(hBuffer); EZ_TEST_BOOL(pState != nullptr); EZ_TEST_BOOL(pState->m_State.IsSet(ezGALResourceState::CopyDestination)); } } // ============================================================ // Texture state tracking - compressed and sub-resource paths // ============================================================ EZ_CREATE_SIMPLE_RENDERER_TEST(ResourceStateTracker, TextureStateTracking) { ezGALDevice* pDevice = ezGALDevice::GetDefaultDevice(); ezGALResourceStateTracker tracker(pDevice); ezUInt32 uiBarrierCount = 0; ezGALTextureBarrier lastBarrier; auto textureCallback = [&](const ezGALTextureBarrier& barrier) { uiBarrierCount++; lastBarrier = barrier; }; // Create a 2-mip, 2-layer texture for sub-resource testing. ezGALTextureCreationDescription texDesc; texDesc.m_uiWidth = 16; texDesc.m_uiHeight = 16; texDesc.m_uiMipLevelCount = 2; texDesc.m_uiArraySize = 2; texDesc.m_Type = ezGALTextureType::Texture2DArray; texDesc.m_Format = ezGALResourceFormat::RGBAUByteNormalized; texDesc.m_TextureFlags = ezGALTextureUsageFlags::ShaderResource | ezGALTextureUsageFlags::RenderTarget; texDesc.m_ResourceAccess.m_bImmutable = false; ezGALTextureHandle hTexture = pDevice->CreateTexture(texDesc); EZ_TEST_BOOL(!hTexture.IsInvalidated()); EZ_SCOPE_EXIT(pDevice->DestroyTexture(hTexture)); EZ_TEST_BLOCK(ezTestBlock::Enabled, "Full-range transition stays compressed") { // Default state for this texture is ShaderResource (mutable RT+SRV textures default to SRV). // Transition to RenderTarget to trigger a barrier. uiBarrierCount = 0; tracker.ChangeState(hTexture, {}, ezGALResourceState::RenderTarget, ezGALShaderStageFlags::Auto, textureCallback); EZ_TEST_INT(uiBarrierCount, 1); EZ_TEST_BOOL(lastBarrier.m_bAllSubresources); EZ_TEST_BOOL(lastBarrier.m_StateAfter.IsSet(ezGALResourceState::RenderTarget)); // Verify internal state is still compressed (single entry). const auto* pState = tracker.GetTextureState(hTexture); EZ_TEST_BOOL(pState != nullptr); EZ_TEST_INT(pState->m_SubResourceStates.GetCount(), 1); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Same full-range write - barrier (WAW)") { // RenderTarget -> RenderTarget is a write-after-write on the same state, but write states always need barriers. uiBarrierCount = 0; tracker.ChangeState(hTexture, {}, ezGALResourceState::RenderTarget, ezGALShaderStageFlags::Auto, textureCallback); // State doesn't change but RenderTarget is a write state, not tracked via UAV WAW. No barrier expected. // Actually the tracker only forces WAV barrier for UAV. For same non-UAV write state, the state bits match so no barrier. // We just verify no crash and then transition to ShaderResource. } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Write to read emits barrier") { uiBarrierCount = 0; tracker.ChangeState(hTexture, {}, ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader, textureCallback); EZ_TEST_INT(uiBarrierCount, 1); EZ_TEST_BOOL(lastBarrier.m_bAllSubresources); EZ_TEST_BOOL(lastBarrier.m_StateBefore.IsSet(ezGALResourceState::RenderTarget)); EZ_TEST_BOOL(lastBarrier.m_StateAfter.IsSet(ezGALResourceState::ShaderResource)); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Partial-range transition expands to sub-resources") { // Current state is ShaderResource (full). Transition only mip 0, layer 0 to RenderTarget. // This should expand the compressed state to per-subresource tracking. ezGALTextureRange partialRange = {0, 1, 0, 1}; // layer 0, mip 0 uiBarrierCount = 0; tracker.ChangeState(hTexture, partialRange, ezGALResourceState::RenderTarget, ezGALShaderStageFlags::Auto, textureCallback); EZ_TEST_INT(uiBarrierCount, 1); EZ_TEST_BOOL(!lastBarrier.m_bAllSubresources); EZ_TEST_INT(lastBarrier.m_Subresource.m_uiMipLevel, 0); EZ_TEST_INT(lastBarrier.m_Subresource.m_uiArraySlice, 0); // Verify expanded: 2 mips * 2 layers = 4 sub-resources. const auto* pState = tracker.GetTextureState(hTexture); EZ_TEST_BOOL(pState != nullptr); EZ_TEST_INT(pState->m_SubResourceStates.GetCount(), 4); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "Subsequent partial transition only affects target sub-resource") { // Mip 1, layer 1 is still in ShaderResource (only mip 0, layer 0 was changed above). // Transition it to CopySource. Only that sub-resource should get a barrier. ezGALTextureRange otherRange = {1, 1, 1, 1}; // layer 1, mip 1 uiBarrierCount = 0; tracker.ChangeState(hTexture, otherRange, ezGALResourceState::CopySource, ezGALShaderStageFlags::Auto, textureCallback); EZ_TEST_INT(uiBarrierCount, 1); EZ_TEST_BOOL(!lastBarrier.m_bAllSubresources); EZ_TEST_INT(lastBarrier.m_Subresource.m_uiMipLevel, 1); EZ_TEST_INT(lastBarrier.m_Subresource.m_uiArraySlice, 1); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "RevertTextureState transitions all sub-resources to default") { uiBarrierCount = 0; tracker.RevertTextureState(textureCallback); // At least the sub-resources that differ from the default should get barriers. EZ_TEST_BOOL(uiBarrierCount > 0); } EZ_TEST_BLOCK(ezTestBlock::Enabled, "SetInitialTextureState resets to compressed") { tracker.SetInitialTextureState(hTexture, ezGALResourceState::CopySource, ezGALShaderStageFlags::Auto); const auto* pState = tracker.GetTextureState(hTexture); EZ_TEST_BOOL(pState != nullptr); EZ_TEST_INT(pState->m_SubResourceStates.GetCount(), 1); EZ_TEST_BOOL(pState->m_SubResourceStates[0].m_State.IsSet(ezGALResourceState::CopySource)); // Transition away to verify the overridden state is used as "before". uiBarrierCount = 0; tracker.ChangeState(hTexture, {}, ezGALResourceState::ShaderResource, ezGALShaderStageFlags::PixelShader, textureCallback); EZ_TEST_INT(uiBarrierCount, 1); EZ_TEST_BOOL(lastBarrier.m_StateBefore.IsSet(ezGALResourceState::CopySource)); } }