Skip to content

Commit cf5d29a

Browse files
committed
Hybrid Renderer Command Buffer (#69)
* First pass at moving all hybrid renderer graphics work onto a single command buffer that HDRP submits * More cleanup to the HybridRendererCommandBuffer API. Added toggleable immediate mode for debugging, using int IDs instead of strings for systems, and automatic profile marker generation. * Testing a theory: Always submit the hybrid renderer command buffer, even if HDRP is not initialized or if no cameras are rendering. Hybrid Renderer Command Buffer: Unique systems on system name and world name - it is valid for a system to push commands independantly from multiple worlds. Force an immediate mode submit of the command buffer if we encounter a case where the HDRenderPipeline has not submitted the command buffer before the system updates again - this can happen in edge cases. (#70) Hybrid Renderer Command Buffer: Remove out-dated assert from RequestImmediateMode - the state it was guarding against is a valid state. (#71) Make HybridRendererCommandBuffer warnings sticky so that they do not spill every frame in the event that the command buffer is not getting flushed by OnRender() in the builds. HybridRendererCommandBuffer: Correct struct equality comparison
1 parent b21e6a7 commit cf5d29a

File tree

3 files changed

+227
-0
lines changed

3 files changed

+227
-0
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// custom-begin:
2+
using System.Collections.Generic;
3+
using UnityEngine.VFX;
4+
using System;
5+
using System.Diagnostics;
6+
using System.Linq;
7+
using UnityEngine.Experimental.GlobalIllumination;
8+
using UnityEngine.Experimental.Rendering;
9+
using UnityEngine.Experimental.Rendering.RenderGraphModule;
10+
11+
namespace UnityEngine.Rendering.HighDefinition
12+
{
13+
public partial class HDRenderPipeline : RenderPipeline
14+
{
15+
// This creates a public API for systems in the Hybrid Renderer to append rendering work onto a single, unified command buffer.
16+
// This command buffer will then be executed and submitted inside of HDRenderPipeline::Render() before any per-camera rendering is performed.
17+
// When HybridRendererCommandBuffer.RequestImmediateMode(isEnabled: true) is set, commands will be drained immediately at SystemEnd().
18+
// ImmediateMode exists for debugging purposes.
19+
public static class HybridRendererCommandBuffer
20+
{
21+
public static HybridRendererCommandBufferSystemHandle CreateSystemHandle(string name, string worldName)
22+
{
23+
// In lieu of any standard string interning system, we're going to simply use Shader.PropertyToID() to generate stable IDs.
24+
var handle = new HybridRendererCommandBufferSystemHandle()
25+
{
26+
systemID = Shader.PropertyToID(name),
27+
worldID = Shader.PropertyToID(worldName)
28+
};
29+
s_HybridRendererCommandBufferData.EnsureProfilingSampler(name, handle);
30+
return handle;
31+
}
32+
33+
public static CommandBuffer SystemBegin(HybridRendererCommandBufferSystemHandle handle)
34+
{
35+
return s_HybridRendererCommandBufferData.Begin(handle);
36+
}
37+
38+
public static void SystemEnd(HybridRendererCommandBufferSystemHandle handle)
39+
{
40+
s_HybridRendererCommandBufferData.End(handle);
41+
42+
var hdrp = (RenderPipelineManager.currentPipeline as HDRenderPipeline);
43+
if (hdrp == null)
44+
{
45+
// HDRP is not initialized yet, rather than throwing out any work that was requested,
46+
// to be safe, lets immediately submit the work, to ensure any global frame agnostic state changes get setup correctly.
47+
s_HybridRendererCommandBufferData.SubmitImmediate();
48+
}
49+
}
50+
51+
public static void RequestImmediateMode(bool enabled)
52+
{
53+
s_HybridRendererCommandBufferData.RequestImmediateMode(enabled);
54+
}
55+
}
56+
57+
private static HybridRendererCommandBufferData s_HybridRendererCommandBufferData = new HybridRendererCommandBufferData();
58+
59+
public struct HybridRendererCommandBufferSystemHandle : IEquatable<HybridRendererCommandBufferSystemHandle>
60+
{
61+
public int systemID;
62+
public int worldID;
63+
64+
public bool Equals(HybridRendererCommandBufferSystemHandle keyOther)
65+
{
66+
return (this.systemID == keyOther.systemID)
67+
&& (this.worldID == keyOther.worldID);
68+
}
69+
70+
public override bool Equals(object other)
71+
{
72+
return other is HybridRendererCommandBufferSystemHandle key && Equals(key);
73+
}
74+
75+
public override int GetHashCode()
76+
{
77+
var hash = systemID.GetHashCode();
78+
hash = hash * 23 + worldID.GetHashCode();
79+
80+
return hash;
81+
}
82+
83+
public static readonly HybridRendererCommandBufferSystemHandle zero = new HybridRendererCommandBufferSystemHandle()
84+
{
85+
systemID = 0,
86+
worldID = 0
87+
};
88+
}
89+
90+
private class HybridRendererCommandBufferData
91+
{
92+
private Dictionary<HybridRendererCommandBufferSystemHandle, bool> debugSubmittedSystemState = new Dictionary<HybridRendererCommandBufferSystemHandle, bool>();
93+
private Dictionary<int, ProfilingSampler> profilingSamplers = new Dictionary<int, ProfilingSampler>();
94+
private CommandBuffer cmd = null;
95+
private bool immediateModeEnabled = false;
96+
private bool immediateModeEnabledNext = false;
97+
98+
#if !UNITY_EDITOR
99+
private HybridRendererCommandBufferSystemHandle commandBufferSubmissionStickyWarningHandle = HybridRendererCommandBufferSystemHandle.zero;
100+
#endif
101+
102+
public void EnsureProfilingSampler(string name, HybridRendererCommandBufferSystemHandle handle)
103+
{
104+
if (!profilingSamplers.TryGetValue(handle.systemID, out var profilingSampler))
105+
{
106+
profilingSamplers.Add(handle.systemID, new ProfilingSampler(name));
107+
}
108+
}
109+
110+
public void RequestImmediateMode(bool enabled)
111+
{
112+
immediateModeEnabledNext = enabled;
113+
}
114+
115+
public void Clear()
116+
{
117+
debugSubmittedSystemState.Clear();
118+
if (cmd != null) { CommandBufferPool.Release(cmd); cmd = null; }
119+
immediateModeEnabled = immediateModeEnabledNext;
120+
}
121+
122+
public CommandBuffer Begin(HybridRendererCommandBufferSystemHandle handle)
123+
{
124+
if (debugSubmittedSystemState.ContainsKey(handle))
125+
{
126+
// Encountered previous frame data, or a system from another world updating.
127+
// In the editor, there are a few edge cases where this can happen, such as changes to Project Settings (oddly enough).
128+
// Simply submit the command buffer immediately, and do not log any warnings.
129+
// If we are in a build, this case is unexpected, so we log a warning.
130+
SubmitImmediate();
131+
132+
#if !UNITY_EDITOR
133+
if (!commandBufferSubmissionStickyWarningHandle.Equals(handle))
134+
{
135+
commandBufferSubmissionStickyWarningHandle = handle;
136+
Debug.LogWarning("Warning: HybridRendererCommandBuffer: Encountered unexpected case of a command buffer having not been submitted between Simulation Update loops. It should have been submitted in the HDRenderPipeline::Render() loop.");
137+
}
138+
#endif
139+
}
140+
#if !UNITY_EDITOR
141+
else if (handle.Equals(commandBufferSubmissionStickyWarningHandle))
142+
{
143+
commandBufferSubmissionStickyWarningHandle = HybridRendererCommandBufferSystemHandle.zero;
144+
}
145+
#endif
146+
debugSubmittedSystemState.Add(handle, false);
147+
148+
var cmd = EnsureCommandBuffer();
149+
profilingSamplers[handle.systemID].Begin(cmd);
150+
return cmd;
151+
}
152+
153+
public void End(HybridRendererCommandBufferSystemHandle handle)
154+
{
155+
if (debugSubmittedSystemState.TryGetValue(handle, out bool submitted))
156+
{
157+
if (submitted)
158+
{
159+
// Guard assert in if block so that we do not pay the GC.Alloc cost of accessing a ProfilingSampler.name.
160+
Debug.AssertFormat(false, "Error: Encountered Hybrid Rendering System {0} with an already submitted command buffer. Was End() already called in this Simulation Update?", profilingSamplers[handle.systemID].name);
161+
}
162+
163+
}
164+
else
165+
{
166+
Debug.AssertFormat(false, "Error: Encountered Hybrid Rendering System {0} End() call with no Begin() call.", profilingSamplers[handle.systemID].name);
167+
}
168+
169+
debugSubmittedSystemState[handle] = true;
170+
171+
profilingSamplers[handle.systemID].End(cmd);
172+
173+
if (immediateModeEnabled)
174+
{
175+
SubmitImmediate();
176+
}
177+
}
178+
179+
public void Submit(ScriptableRenderContext renderContext)
180+
{
181+
if (cmd != null)
182+
{
183+
renderContext.ExecuteCommandBuffer(cmd);
184+
renderContext.Submit();
185+
cmd.Clear();
186+
}
187+
188+
Clear();
189+
}
190+
191+
public void SubmitImmediate()
192+
{
193+
if (cmd != null)
194+
{
195+
Graphics.ExecuteCommandBuffer(cmd);
196+
cmd.Clear();
197+
}
198+
199+
Clear();
200+
}
201+
202+
private CommandBuffer EnsureCommandBuffer()
203+
{
204+
if (cmd == null) { cmd = CommandBufferPool.Get("HybridRendererCommandBuffer"); }
205+
return cmd;
206+
}
207+
208+
}
209+
}
210+
}

com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.HybridRendererCommandBuffer.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDRenderPipeline.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,12 @@ protected override void Render(ScriptableRenderContext renderContext, List<Camer
16661666
protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
16671667
#endif
16681668
{
1669+
// Currently testing this theory:
1670+
// Always submit the hybrid renderer command buffer, even if HDRP isn't initialized or if no cameras are rendering.
1671+
// This seems to be needed because the sparse uploader may upload state changes on frames when a camera has not been streamed in yet.
1672+
// Skipping these state changes results in state / corrupt global state.
1673+
s_HybridRendererCommandBufferData.Submit(renderContext);
1674+
16691675
#if UNITY_EDITOR
16701676
if (!m_ResourcesInitialized)
16711677
return;

0 commit comments

Comments
 (0)