@@ -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 {
0 commit comments