Skip to content

Commit 617dafe

Browse files
kirill-titov-uEvergreen
authored andcommitted
Compact resource versioning
1 parent afa2ab5 commit 617dafe

7 files changed

Lines changed: 148 additions & 120 deletions

File tree

Packages/com.unity.render-pipelines.core/Editor/RenderGraph/RenderGraphTestsCore.cs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
using NUnit.Framework;
22
using System;
33
using System.Collections.Generic;
4+
using UnityEngine;
5+
using UnityEngine.Rendering;
46
using UnityEngine.Rendering.RenderGraphModule;
57

6-
#if UNITY_EDITOR
7-
using UnityEditor.Rendering;
8-
#endif
9-
10-
namespace UnityEngine.Rendering.Tests
8+
namespace UnityEditor.Rendering.Tests
119
{
1210
// TODO: Move this class to the Tests/Editor folder once the "IsolatedPackagesVerified" CI test correctly resolves all dependencies.
1311
// Currently, the URP package fails to locate the RenderGraphTestsCore class when it's placed under Tests/Editor,
@@ -130,9 +128,8 @@ public void Setup()
130128
{
131129
// Setting default global settings to the custom RG render pipeline type, no quality settings so we can rely on the default RP
132130
m_RenderGraphTestGlobalSettings = ScriptableObject.CreateInstance<RenderGraphTestGlobalSettings>();
133-
#if UNITY_EDITOR
134131
EditorGraphicsSettings.SetRenderPipelineGlobalSettingsAsset<RenderGraphTestPipelineInstance>(m_RenderGraphTestGlobalSettings);
135-
#endif
132+
136133
// Saving old render pipelines to set them back after testing
137134
m_OldDefaultRenderPipeline = GraphicsSettings.defaultRenderPipeline;
138135
m_OldQualityRenderPipeline = QualitySettings.renderPipeline;
@@ -166,14 +163,20 @@ public void Cleanup()
166163
QualitySettings.renderPipeline = m_OldQualityRenderPipeline;
167164
m_OldQualityRenderPipeline = null;
168165

169-
m_RenderGraph.Cleanup();
166+
try
167+
{
168+
m_RenderGraph.Cleanup();
169+
}
170+
catch (Exception e)
171+
{
172+
Debug.Log($"Tried to clean RenderGraph but exception was thrown: {e.Message}");
173+
}
170174

171-
Object.DestroyImmediate(m_RenderGraphTestPipeline);
172175

173-
#if UNITY_EDITOR
176+
UnityEngine.Object.DestroyImmediate(m_RenderGraphTestPipeline);
174177
EditorGraphicsSettings.SetRenderPipelineGlobalSettingsAsset<RenderGraphTestPipelineInstance>(null);
175-
#endif
176-
Object.DestroyImmediate(m_RenderGraphTestGlobalSettings);
178+
179+
UnityEngine.Object.DestroyImmediate(m_RenderGraphTestGlobalSettings);
177180

178181
GameObject.DestroyImmediate(m_GameObject);
179182
m_GameObject = null;

Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/Compiler/ResourcesData.cs

Lines changed: 69 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,26 @@ public ResourceReaderData(int _passId, int _inputSlot)
2424
// RenderGraphResourceRegistry was identified as slow in the profiler
2525
internal struct ResourceUnversionedData
2626
{
27-
public readonly bool isImported; // Imported graph resource
28-
public bool isShared; // Shared graph resource
29-
public int tag;
27+
public int versionedDataOffset; // Where this resource's versioned data starts in the packed array
28+
public int versionedDataCount; // Number of versions this resource actually has
29+
public int readerDataOffset; // Where this resource's reader data starts in the packed array
30+
public int maxReadersPerVersion; // Max readers across all versions of this resource
31+
3032
public int lastUsePassID; // Index of last used pass. The resource (if not imported) is destroyed after this pass.
3133
public int lastWritePassID; // The last pass writing it. After this other passes may still read the resource
32-
public int firstUsePassID; //First pas using the resource this may be reading or writing. If not imported the resource is allocated just before this pass.
34+
public int firstUsePassID; // First pass using the resource this may be reading or writing. If not imported the resource is allocated just before this pass.
35+
public int latestVersionNumber; // Mostly readonly, can be decremented only if all passes using the last version are culled
36+
37+
public readonly bool isImported; // Imported graph resource
3338
public bool memoryLess; // Never create the texture on GPU if it is allocated/freed within a renderpass
39+
public int tag;
3440

3541
public readonly int width;
3642
public readonly int height;
3743
public readonly int volumeDepth;
3844
public readonly int msaaSamples;
3945
public readonly GraphicsFormat graphicsFormat;
4046

41-
public int latestVersionNumber; // mostly readonly, can be decremented only if all passes using the last version are culled
42-
4347
public readonly bool clear; // graph.m_Resources.GetTextureResourceDesc(fragment.resource).clearBuffer;
4448
public readonly bool discard; // graph.m_Resources.GetTextureResourceDesc(fragment.resource).discardBuffer;
4549
public readonly bool bindMS;
@@ -53,7 +57,6 @@ internal struct ResourceUnversionedData
5357
public ResourceUnversionedData(TextureResource rll, ref RenderTargetInfo info, ref TextureDesc desc, bool isResBackBuffer)
5458
{
5559
isImported = rll.imported;
56-
isShared = false;
5760
tag = 0;
5861
firstUsePassID = -1;
5962
lastUsePassID = -1;
@@ -73,14 +76,18 @@ public ResourceUnversionedData(TextureResource rll, ref RenderTargetInfo info, r
7376
isBackBuffer = isResBackBuffer;
7477
textureUVOrigin = rll.textureUVOrigin;
7578
graphicsFormat = desc.format;
79+
80+
versionedDataOffset = 0;
81+
versionedDataCount = 0;
82+
readerDataOffset = 0;
83+
maxReadersPerVersion = 0;
7684
}
7785

7886
public ResourceUnversionedData(IRenderGraphResource rll, ref BufferDesc _, bool isResBackBuffer)
7987
{
8088
// We don't do anything with the BufferDesc for now. The compiler doesn't really need the details of the buffer like it does with textures
8189
// since for textures it needs the details to merge passes etc. Which is not relevant for buffers.
8290
isImported = rll.imported;
83-
isShared = false;
8491
tag = 0;
8592
firstUsePassID = -1;
8693
lastUsePassID = -1;
@@ -100,14 +107,18 @@ public ResourceUnversionedData(IRenderGraphResource rll, ref BufferDesc _, bool
100107
isBackBuffer = isResBackBuffer;
101108
textureUVOrigin = TextureUVOriginSelection.Unknown;
102109
graphicsFormat = GraphicsFormat.None;
110+
111+
versionedDataOffset = 0;
112+
versionedDataCount = 0;
113+
readerDataOffset = 0;
114+
maxReadersPerVersion = 0;
103115
}
104116

105117
public ResourceUnversionedData(IRenderGraphResource rll, ref RayTracingAccelerationStructureDesc _, bool isResBackBuffer)
106118
{
107119
// We don't do anything with the RayTracingAccelerationStructureDesc for now. The compiler doesn't really need the details of the acceleration structures like it does with textures
108120
// since for textures it needs the details to merge passes etc. Which is not relevant for acceleration structures.
109121
isImported = rll.imported;
110-
isShared = false;
111122
tag = 0;
112123
firstUsePassID = -1;
113124
lastUsePassID = -1;
@@ -127,6 +138,11 @@ public ResourceUnversionedData(IRenderGraphResource rll, ref RayTracingAccelerat
127138
isBackBuffer = isResBackBuffer;
128139
textureUVOrigin = TextureUVOriginSelection.Unknown;
129140
graphicsFormat = GraphicsFormat.None;
141+
142+
versionedDataOffset = 0;
143+
versionedDataCount = 0;
144+
readerDataOffset = 0;
145+
maxReadersPerVersion = 0;
130146
}
131147

132148
public void InitializeNullResource()
@@ -167,11 +183,12 @@ public void SetWritingPass(CompilerContextData ctx, in ResourceHandle h, int pas
167183
public void RegisterReadingPass(CompilerContextData ctx, in ResourceHandle h, int passId, int index)
168184
{
169185
#if DEVELOPMENT_BUILD || UNITY_EDITOR
170-
if (numReaders >= ctx.resources.MaxReaders[h.iType])
186+
ref var unversioned = ref ctx.resources.unversionedData[h.iType].ElementAt(h.index);
187+
if (numReaders >= unversioned.maxReadersPerVersion)
171188
{
172189
string passName = ctx.GetPassName(passId);
173190
string resourceName = ctx.GetResourceName(h);
174-
throw new Exception($"Maximum '{ctx.resources.MaxReaders}' passes can use a single graph output as input. Pass {passName} is trying to read {resourceName}.");
191+
throw new Exception($"Maximum '{unversioned.maxReadersPerVersion}' passes can use a single graph output as input. Pass {passName} is trying to read {resourceName}.");
175192
}
176193
#endif
177194
ctx.resources.readerData[h.iType][ctx.resources.IndexReader(h, numReaders)] = new ResourceReaderData(passId, index);
@@ -203,16 +220,14 @@ public void RemoveReadingPass(CompilerContextData ctx, in ResourceHandle h, int
203220
}
204221

205222
// This class allows quick lookups from ResourceHandle -> ResourceUnversionedData/ResourceVersionData/ResourceReaderData
206-
// This is implementing a fully allocated array, we assume there aren't too many resources & versions. This lookup is fast and doesn't
207-
// require GC allocs to fill.
223+
// Uses resource-level sparse allocation: each resource allocates only the versions it needs.
224+
// Reader allocation uses total read count as conservative upper bound (not true per-version sparse).
225+
// Allocation metadata is inlined in ResourceUnversionedData for optimal cache performance.
208226
internal class ResourcesData
209227
{
210-
public NativeList<ResourceUnversionedData>[] unversionedData; // Flattened fixed size array storing info per resource id shared between all versions.
211-
public NativeList<ResourceVersionedData>[] versionedData; // Flattened fixed size array storing up to MaxVersions versions per resource id.
212-
public NativeList<ResourceReaderData>[] readerData; // Flattened fixed size array storing up to MaxReaders per resource id per version.
213-
214-
public int[] MaxVersions;
215-
public int[] MaxReaders;
228+
public NativeList<ResourceUnversionedData>[] unversionedData; // Per-resource data (one per resource, includes allocation metadata)
229+
public NativeList<ResourceVersionedData>[] versionedData; // Packed versioned data (sparse)
230+
public NativeList<ResourceReaderData>[] readerData; // Partially packed reader data (semi-sparse)
216231

217232
public DynamicArray<Name>[] resourceNames;
218233

@@ -222,8 +237,6 @@ public ResourcesData()
222237
versionedData = new NativeList<ResourceVersionedData>[(int)RenderGraphResourceType.Count];
223238
readerData = new NativeList<ResourceReaderData>[(int)RenderGraphResourceType.Count];
224239
resourceNames = new DynamicArray<Name>[(int)RenderGraphResourceType.Count];
225-
MaxVersions = new int[(int)RenderGraphResourceType.Count];
226-
MaxReaders = new int[(int)RenderGraphResourceType.Count];
227240

228241
for (int t = 0; t < (int)RenderGraphResourceType.Count; t++)
229242
resourceNames[t] = new DynamicArray<Name>(0); // T in NativeList<T> cannot contain managed types, so the names are stored separately
@@ -249,12 +262,10 @@ public void Clear()
249262
void AllocateAndResizeNativeListIfNeeded<T>(ref NativeList<T> nativeList, int size, NativeArrayOptions options) where T : unmanaged
250263
{
251264
// Allocate the first time or if Dispose() has been called through RenderGraph.Cleanup()
252-
// Length remains 0, list is still empty
253265
if (!nativeList.IsCreated)
254266
nativeList = new NativeList<T>(size, AllocatorManager.Persistent);
255267

256268
// Resize the list (it will allocate if necessary)
257-
// List is not empty anymore
258269
nativeList.Resize(size, options);
259270
}
260271

@@ -265,9 +276,6 @@ public void Initialize(RenderGraphResourceRegistry resources)
265276
RenderGraphResourceType resourceType = (RenderGraphResourceType) t;
266277
var numResources = resources.GetResourceCount(resourceType);
267278

268-
uint maxReaders = 0;
269-
uint maxWriters = 0;
270-
271279
// We don't clear the list as we reinitialize it right after
272280
AllocateAndResizeNativeListIfNeeded(ref unversionedData[t], numResources, NativeArrayOptions.UninitializedMemory);
273281

@@ -281,16 +289,19 @@ public void Initialize(RenderGraphResourceRegistry resources)
281289
resourceNames[t][0] = new Name("");
282290
}
283291

284-
// Fill the buffer with any existing external info requested for NRP RG process
292+
// Compute allocation sizes and populate unversionedData in a single pass
293+
int totalVersionedDataCount = 0;
294+
int totalReaderDataCount = 0;
295+
296+
// Null resource at index 0 already initialized to 0 in constructors
297+
// Process all resources in one pass for better cache locality
285298
for (int r = 1; r < numResources; r++)
286299
{
287-
// We cache these values here
288-
// as getting them over and over from other
289-
// graph data structures external to NRP RG is costly
290300
var h = new ResourceHandle(r, resourceType, false);
291301
var rll = resources.GetResourceLowLevel(h);
292302
resourceNames[t][r] = new Name(rll.GetName());
293303

304+
// Initialize unversionedData based on resource type
294305
switch (t)
295306
{
296307
case (int)RenderGraphResourceType.Texture:
@@ -325,49 +336,57 @@ public void Initialize(RenderGraphResourceRegistry resources)
325336
throw new Exception("Unsupported resource type: " + t);
326337
}
327338

328-
maxReaders = Math.Max(maxReaders, rll.readCount);
329-
maxWriters = Math.Max(maxWriters, rll.writeCount);
339+
// Compute allocation metadata for this resource
340+
// +1 for versions: v0 exists even without writes
341+
int numVersions = (int)rll.writeCount + 1;
342+
// +1 for readers: transient resources don't call IncrementReadCount, but BuildGraph adds 1 implicit read
343+
// Note: rll.readCount is total across all versions (not per-version), so this is a conservative upper bound
344+
int numReaders = (int)rll.readCount + 1;
345+
346+
// Populate allocation metadata
347+
ref var unversioned = ref unversionedData[t].ElementAt(r);
348+
unversioned.versionedDataOffset = totalVersionedDataCount;
349+
unversioned.versionedDataCount = numVersions;
350+
unversioned.readerDataOffset = totalReaderDataCount;
351+
unversioned.maxReadersPerVersion = numReaders;
352+
353+
totalVersionedDataCount += numVersions;
354+
totalReaderDataCount += numVersions * numReaders;
330355
}
331356

332-
// The first resource is a null resource, so we need to add 1 to the count.
333-
MaxReaders[t] = (int)maxReaders + 1;
334-
MaxVersions[t] = (int)maxWriters + 1;
335-
336-
// Clear the other caching structures, they will be filled later
337-
AllocateAndResizeNativeListIfNeeded(ref versionedData[t], MaxVersions[t] * numResources, NativeArrayOptions.ClearMemory);
338-
AllocateAndResizeNativeListIfNeeded(ref readerData[t], MaxVersions[t] * MaxReaders[t] * numResources, NativeArrayOptions.ClearMemory);
357+
AllocateAndResizeNativeListIfNeeded(ref versionedData[t], totalVersionedDataCount, NativeArrayOptions.ClearMemory);
358+
AllocateAndResizeNativeListIfNeeded(ref readerData[t], totalReaderDataCount, NativeArrayOptions.ClearMemory);
339359
}
340360
}
341361

342-
// Flatten array index
362+
// Flatten array index using sparse allocation (uses inlined allocation metadata)
343363
[MethodImpl(MethodImplOptions.AggressiveInlining)]
344364
public int Index(in ResourceHandle h)
345365
{
366+
ref var unversioned = ref unversionedData[h.iType].ElementAt(h.index);
346367
#if UNITY_EDITOR // Hot path
347-
if (h.version < 0 || h.version >= MaxVersions[h.iType])
368+
if (h.version < 0 || h.version >= unversioned.versionedDataCount)
348369
throw new Exception("Invalid version: " + h.version);
349370
#endif
350-
return h.index * MaxVersions[h.iType] + h.version;
371+
return unversioned.versionedDataOffset + h.version;
351372
}
352373

353-
// Flatten array index
374+
// Flatten array index for readers using sparse allocation (uses inlined allocation metadata)
354375
[MethodImpl(MethodImplOptions.AggressiveInlining)]
355376
public int IndexReader(in ResourceHandle h, int readerID)
356377
{
378+
ref var unversioned = ref unversionedData[h.iType].ElementAt(h.index);
357379
#if UNITY_EDITOR // Hot path
358-
if (h.version < 0 || h.version >= MaxVersions[h.iType])
380+
if (h.version < 0 || h.version >= unversioned.versionedDataCount)
359381
throw new Exception("Invalid version");
360-
if (readerID < 0 || readerID >= MaxReaders[h.iType])
382+
if (readerID < 0 || readerID >= unversioned.maxReadersPerVersion)
361383
throw new Exception("Invalid reader");
362384
#endif
363-
return (h.index * MaxVersions[h.iType] + h.version) * MaxReaders[h.iType] + readerID;
385+
return unversioned.readerDataOffset + h.version * unversioned.maxReadersPerVersion + readerID;
364386
}
365387

366388
// Lookup data for a given handle
367-
public ref ResourceVersionedData this[ResourceHandle h]
368-
{
369-
get { return ref versionedData[h.iType].ElementAt(Index(h)); }
370-
}
389+
public ref ResourceVersionedData this[ResourceHandle h] => ref versionedData[h.iType].ElementAt(Index(h));
371390

372391
public void Dispose()
373392
{

Packages/com.unity.render-pipelines.core/Runtime/RenderGraph/RenderGraphDefaultResources.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ internal void InitializeForRendering(RenderGraph renderGraph)
8080
whiteTexture = renderGraph.ImportTexture(m_WhiteTexture2D, true);
8181
defaultShadowTexture = renderGraph.ImportTexture(m_ShadowTexture2D, true);
8282

83+
if (!TextureXR.initialized)
84+
return;
85+
8386
clearTextureXR = renderGraph.ImportTexture(TextureXR.GetClearTexture(), true);
8487
magentaTextureXR = renderGraph.ImportTexture(TextureXR.GetMagentaTexture(), true);
8588
blackTextureXR = renderGraph.ImportTexture(TextureXR.GetBlackTexture(), true);

0 commit comments

Comments
 (0)