View Issue Details

IDProjectCategoryView StatusLast Update
0003499NoesisGUIC++ SDKpublic2025-01-11 12:58
ReporterAnKor Assigned Tojsantos  
PrioritynormalSeverityminorReproducibilitysometimes
Status resolvedResolutionfixed 
Product Version3.2.4 
Target Version3.2.7Fixed in Version3.2.7 
Summary0003499: D3D12 debug layer error caused by split barriers
DescriptionWe have a scenario where render target texture is updated and resolved in one frame, but isn't actually used until the next frame.
Version 3.2.4 introduced split barriers and in our case BEGIN_ONLY and END_ONLY barrier halves happen to be in different command lists.

I haven't verified thoroughly but at least on one notebook with Win11, Intel 13700H with Xe graphics (no dedicated GPU) this causes debug layer errors:

D3D12 ERROR: ID3D12CommandQueue::ExecuteCommandLists: Begin split on ... has no matching END_ONLY barrier in the same ExecuteCommandLists scope. Splitting barriers acrossExecuteCommandLists boundaries is not supported. It also serves no benefit since all syncronization and cache flush operations are guaranteed to be completed in the preceding ExecuteCommandLists scope. [ RESOURCE_MANIPULATION ERROR #535: RESOURCE_BARRIER_UNMATCHED_BEGIN]

Discussed in https://www.noesisengine.com/forums/viewtopic.php?t=3377
Steps To ReproduceUpdate and resolve RT texture like this but don't use it in the same frame:
pDevice->SetRenderTarget(pRT);
pView->GetRenderer()->Render(false,false);
pDevice->ResolveRenderTarget(pRT,0,0);
TagsNo tags attached.
PlatformWindows

Activities

jsantos

jsantos

2025-01-11 12:34

manager   ~0010254

Last edited: 2025-01-11 12:38

The solution for this issue is straightforward: I've attached a patch that introduces a new EndPendingSplitBarriers() function in the device. You need to call this function before invoking ExecuteCommandLists().

However, I'm unsure if this should be officially added to our implementation, as it addresses a very specific aspect of your setup. In our case, the RenderTargets are exclusively used for offscreen rendering, where split barriers are always properly closed. Your use of RenderTargets for capturing a view could be managed on your end by binding and resolving the surface with your own implementation.

Adding this to the reference implementation feels like it introduces unnecessary complexity, which could negatively impact other users.

What are your thoughts?

-
EndSplitBarriers.patch (6,633 bytes)   
Index: Include/NsRender/D3D12Factory.h
===================================================================
--- Include/NsRender/D3D12Factory.h	(revision 14871)
+++ Include/NsRender/D3D12Factory.h	(working copy)
@@ -47,6 +47,8 @@
         const void* hlsl, uint32_t size);
     static void ClearPixelShaders(Noesis::RenderDevice* device);
 
+    static void EndPendingSplitBarriers(Noesis::RenderDevice* device);
+
     static ID3D12Resource* GetNativePtr(Noesis::Texture* texture);
     static D3D12_RESOURCE_STATES GetTextureState(Noesis::Texture* texture);
     static void SetTextureState(Noesis::Texture* texture, D3D12_RESOURCE_STATES state);
Index: Src/D3D12RenderDevice.cpp
===================================================================
--- Src/D3D12RenderDevice.cpp	(revision 14871)
+++ Src/D3D12RenderDevice.cpp	(working copy)
@@ -911,6 +911,7 @@
     mCommands = commands;
     mSafeFenceValue = safeFenceValue;
     InvalidateStateCache();
+    mPendingSplitBarriers.Clear();
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -919,6 +920,7 @@
     D3D12Texture* texture = (D3D12Texture*)texture_;
     PendingRelease p = { texture->srv, 0xFFFF, 0xFFFF, mSafeFenceValue, texture->obj };
     mPendingReleases.PushBack(p);
+    mPendingSplitBarriers.EraseIf([&](Texture* _) { return _ == texture; });
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -941,6 +943,41 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
+static void EnsureShaderResourceState(ID3D12GraphicsCommandList* commands, D3D12Texture* texture)
+{
+    if (texture->state != D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)
+    {
+        D3D12_RESOURCE_BARRIER barrier;
+        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
+        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
+        barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
+        barrier.Transition.pResource = texture->obj;
+        barrier.Transition.StateBefore = texture->state;
+        barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
+
+        if (texture->pendingBarrierToSRV)
+        {
+            barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_END_ONLY;
+            texture->pendingBarrierToSRV = false;
+        }
+
+        commands->ResourceBarrier(1, &barrier);
+        texture->state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+void D3D12RenderDevice::EndPendingSplitBarriers()
+{
+    for (Texture* texture : mPendingSplitBarriers)
+    {
+        EnsureShaderResourceState(mCommands, (D3D12Texture*)texture);
+    }
+
+    mPendingSplitBarriers.Clear();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
 ID3D12Resource* D3D12RenderDevice::GetNativePtr(Noesis::Texture* texture)
 {
     return ((D3D12Texture*)texture)->obj;
@@ -1288,30 +1325,6 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
-static void EnsureShaderResourceState(ID3D12GraphicsCommandList* commands, D3D12Texture* texture)
-{
-    if (texture->state != D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)
-    {
-        D3D12_RESOURCE_BARRIER barrier;
-        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
-        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
-        barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
-        barrier.Transition.pResource = texture->obj;
-        barrier.Transition.StateBefore = texture->state;
-        barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
-
-        if (texture->pendingBarrierToSRV)
-        {
-            barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_END_ONLY;
-            texture->pendingBarrierToSRV = false;
-        }
-
-        commands->ResourceBarrier(1, &barrier);
-        texture->state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
-    }
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
 void D3D12RenderDevice::SetRenderTarget(RenderTarget* surface_)
 {
     D3D12RenderTarget* surface = (D3D12RenderTarget*)surface_;
@@ -1440,6 +1453,7 @@
 
         mCommands->ResourceBarrier(1, &barrier);
         surface->texture->pendingBarrierToSRV = true;
+        mPendingSplitBarriers.PushBack(surface->texture);
 
         DX_RELEASE(commands1);
         DX_END_EVENT();
@@ -1456,6 +1470,7 @@
 
         mCommands->ResourceBarrier(1, &barrier);
         surface->texture->pendingBarrierToSRV = true;
+        mPendingSplitBarriers.PushBack(surface->texture);
     }
 
     if (surface->stencil)
Index: Src/D3D12RenderDevice.h
===================================================================
--- Src/D3D12RenderDevice.h	(revision 14871)
+++ Src/D3D12RenderDevice.h	(working copy)
@@ -54,6 +54,8 @@
     void SafeReleaseRenderTarget(Noesis::RenderTarget* surface);
     void SafeReleaseResource(ID3D12Resource* resource);
 
+    void EndPendingSplitBarriers();
+
     static ID3D12Resource* GetNativePtr(Noesis::Texture* texture);
     static D3D12_RESOURCE_STATES GetTextureState(Noesis::Texture* texture);
     static void SetTextureState(Noesis::Texture* texture, D3D12_RESOURCE_STATES state);
@@ -250,6 +252,7 @@
     };
 
     Noesis::Vector<PendingRelease> mPendingReleases;
+    Noesis::Vector<Noesis::Texture*> mPendingSplitBarriers;
 
     ID3D12RootSignature* mCachedRootSignature;
     ID3D12PipelineState* mCachedPipelineState;
Index: Src/Render.D3D12RenderDevice.cpp
===================================================================
--- Src/Render.D3D12RenderDevice.cpp	(revision 14871)
+++ Src/Render.D3D12RenderDevice.cpp	(working copy)
@@ -54,6 +54,13 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
+void D3D12Factory::EndPendingSplitBarriers(RenderDevice* device_)
+{
+    D3D12RenderDevice* device = (D3D12RenderDevice*)device_;
+    device->EndPendingSplitBarriers();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
 ID3D12Resource* D3D12Factory::GetNativePtr(Texture* texture)
 {
     return D3D12RenderDevice::GetNativePtr(texture);
EndSplitBarriers.patch (6,633 bytes)   
jsantos

jsantos

2025-01-11 12:58

manager   ~0010256

Ok, never mind, I have decided to commit this (r14888)

Thanks for the feedback!

Issue History

Date Modified Username Field Change
2024-07-15 11:43 AnKor New Issue
2024-07-16 14:23 jsantos Assigned To => jsantos
2024-07-16 14:23 jsantos Status new => assigned
2024-07-16 14:23 jsantos Target Version => 3.2.5
2024-10-24 12:22 jsantos Target Version 3.2.5 => 3.2.6
2024-11-22 18:19 jsantos Target Version 3.2.6 => 3.2.7
2025-01-11 12:34 jsantos Note Added: 0010254
2025-01-11 12:34 jsantos File Added: EndSplitBarriers.patch
2025-01-11 12:34 jsantos Status assigned => feedback
2025-01-11 12:34 jsantos Target Version 3.2.7 =>
2025-01-11 12:34 jsantos Note Edited: 0010254
2025-01-11 12:38 jsantos Note Edited: 0010254
2025-01-11 12:38 jsantos Note Edited: 0010254
2025-01-11 12:58 jsantos Target Version => 3.2.7
2025-01-11 12:58 jsantos Status feedback => resolved
2025-01-11 12:58 jsantos Resolution open => fixed
2025-01-11 12:58 jsantos Fixed in Version => 3.2.7
2025-01-11 12:58 jsantos Note Added: 0010256