Game.Rendering.ProceduralEmissiveSystem
Assembly: Assembly-CSharp (game)
Namespace: Game.Rendering
Type: class
Base: GameSystemBase, IPreDeserialize
Summary:
ProceduralEmissiveSystem manages GPU-side storage and upload of procedurally generated emissive lights for the rendering pipeline. It uses a NativeHeapAllocator for compact allocation of float4 entries (each light encoded as float4), and a SparseUploader / ThreadedSparseUploader to perform sparse GPU uploads. The system keeps an "identity" entry at index 0 and exposes APIs for other systems/jobs to allocate GPU space, schedule uploads, and report memory statistics. It also defers freeing allocations via a removal queue to avoid race conditions with GPU usage and uses a small job (RemoveAllocationsJob) to process delayed frees.
Fields
-
public const uint EMISSIVE_MEMORY_DEFAULT
Default GPU memory reserved for emissive storage (2097152 bytes). -
public const uint EMISSIVE_MEMORY_INCREMENT
Increment size used when growing emissive memory (1048576 bytes). -
public const uint UPLOADER_CHUNK_SIZE
Chunk size used by the SparseUploader (131072 bytes). -
private RenderingSystem m_RenderingSystem
Reference to the global RenderingSystem (used for time deltas and LOD timer). -
private NativeHeapAllocator m_HeapAllocator
Allocator that manages float4 slots used to store per-light GPU data. Allocations are in terms of float4 slots. -
private SparseUploader m_SparseUploader
Uploader that performs sparse/partial updates to the GPU buffer. -
private ThreadedSparseUploader m_ThreadedSparseUploader
Threaded handle returned by SparseUploader.Begin to write data from worker threads. -
private NativeReference<AllocationInfo> m_AllocationInfo
Native reference storing allocation metadata (AllocationInfo struct), primarily allocation count. -
private NativeQueue<AllocationRemove> m_AllocationRemoves
Queue of pending allocation removals. Entries are processed over time to delay freeing until safe. -
private bool m_IsAllocating
Flag indicating that allocations took place and buffer resize may be required on next update. -
private bool m_IsUploading
Flag indicating an upload is currently in progress (BeginUpload called and not yet completed). -
private GraphicsBuffer m_ComputeBuffer
GPU buffer (Raw) bound as global buffer "_LightInfo" to be read by shaders. -
private JobHandle m_HeapDeps
JobHandle tracking scheduled jobs that wrote to the heap allocator (must be completed before heap access / resize). -
private JobHandle m_UploadDeps
JobHandle tracking upload writer jobs; used by CompleteUpload to wait for upload work to finish. -
private int m_HeapAllocatorByteSize
Cached byte size of the underlying heap allocator (Size * sizeof(float4)) used to determine when buffer needs replacement. -
private int m_CurrentTime
Wrap-around (16-bit) time counter used to determine when queued removals can be freed. -
private consts and nested types (AllocationInfo, AllocationRemove, RemoveAllocationsJob)
See below for descriptions of the nested structs and the job used to process delayed frees.
Nested types (important):
public struct AllocationInfo
Holds allocation metadata:-
uint m_AllocationCount
— number of active allocations (excluding the reserved identity entry). -
public struct AllocationRemove
Represents a pending removal: NativeHeapBlock m_Allocation
— the heap block to be released.-
int m_RemoveTime
— time when the removal was enqueued; removal is processed after a time threshold. -
[BurstCompile] private struct RemoveAllocationsJob : IJob
Job that processes m_AllocationRemoves queue, checking each removal against current time and freeing underlying heap blocks via m_HeapAllocator when safe. It decrements AllocationInfo.m_AllocationCount as removals are committed.
Properties
- None (this system exposes behavior via methods; internal state is private and exposed through out parameters in GetHeapAllocator/GetMemoryStats).
Constructors
public ProceduralEmissiveSystem()
Default managed constructor. The real initialization happens in OnCreate override (the constructor is preserved for ECS system instantiation).
Methods
protected override void OnCreate()
Initializes the system:- Grabs RenderingSystem from world.
- Creates NativeHeapAllocator with default EMISSIVE_MEMORY_DEFAULT / sizeof(float4) slots.
- Creates SparseUploader and persistent NativeReference/NativeQueue.
-
Calls AllocateIdentityEntry to reserve entry 0 and initialize allocation metadata. Notes: marked with [Preserve] in original to ensure not stripped.
-
protected override void OnDestroy()
Cleans up resources: - Completes outstanding upload and heap jobs.
- Disposes heap allocator, sparse uploader, native reference/queue.
-
Releases the GPU compute buffer if present.
-
protected override void OnUpdate()
Runs each frame: - Completes ongoing uploads and heap jobs as needed.
- If allocations occurred (m_IsAllocating), compute new required buffer size and replace or create GraphicsBuffer, set it globally (_LightInfo), and tell m_SparseUploader to use it.
-
Schedules RemoveAllocationsJob if m_AllocationRemoves is not empty to process delayed frees. Notes: uses m_RenderingSystem.lodTimerDelta to update m_CurrentTime for removal timing.
-
public ThreadedSparseUploader BeginUpload(int opCount, uint dataSize, uint maxOpSize)
Starts an upload session on the SparseUploader. Returns a ThreadedSparseUploader handle to write upload commands. Sets m_IsUploading true. Use AddUploadWriter with the job handle produced by threaded writers and then call CompleteUpload() to commit. -
public void AddUploadWriter(JobHandle handle)
Records a JobHandle (m_UploadDeps) representing jobs that produce upload data; CompleteUpload will wait on this handle. -
public void CompleteUpload()
If an upload is in progress: - Completes m_UploadDeps.
- Calls m_SparseUploader.EndAndCommit to finalize and commit the sparse uploads.
-
Clears m_IsUploading.
-
public void PreDeserialize(Context context)
Called before deserialization (IPreDeserialize). Clears allocator state: - Completes outstanding heap jobs.
- Clears the heap allocator and removal queue.
-
Re-allocates the reserved identity entry.
-
public NativeHeapAllocator GetHeapAllocator(out NativeReference<AllocationInfo> allocationInfo, out NativeQueue<AllocationRemove> allocationRemoves, out int currentTime, out JobHandle dependencies)
Provides callers direct access to the NativeHeapAllocator for making allocations. Outputs: - allocationInfo — native reference with AllocationInfo (for incrementing allocation count, etc).
- allocationRemoves — queue to enqueue AllocationRemove entries for delayed frees.
- currentTime — the system's m_CurrentTime (used to set remove time).
-
dependencies — current m_HeapDeps that callers should respect. This method marks m_IsAllocating true so OnUpdate will resize GPU buffer if allocator size changed.
-
public void AddHeapWriter(JobHandle handle)
Records the job handle that modified the heap allocator (m_HeapDeps) so the system can wait on it before resizing/reading. -
public void GetMemoryStats(out uint allocatedSize, out uint bufferSize, out uint currentUpload, out uint uploadSize, out int allocationCount)
Fills out memory stats: - allocatedSize — UsedSpace * sizeof(float4)
- bufferSize — Size * sizeof(float4)
- currentUpload — current bytes being uploaded (from SparseUploader stats)
- uploadSize — total bytes used by SparseUploader
-
allocationCount — current allocation count from AllocationInfo Completes m_HeapDeps before reading allocator state.
-
private void AllocateIdentityEntry()
Allocates a single float4 slot reserved as an identity/empty light at index 0 and resets AllocationInfo.m_AllocationCount = 0. Marks m_IsAllocating to ensure the GPU buffer exists. -
public static void GetGpuLights(Emissive emissive, in DynamicBuffer<ProceduralLight> proceduralLights, in DynamicBuffer<LightState> lights, NativeList<float4> gpuLights)
Helper to fill gpuLights (NativeList) with float4 entries representing GPU light data: - gpuLights[0] is set to default (reserved identity entry).
-
Iterates proceduralLights and corresponding LightState entries and computes a float4 color (lerp between two color values weighted by lightState.m_Color and multiplied in .w by intensity). Writes these into gpuLights starting at index 1.
-
RemoveAllocationsJob.Execute() (in nested RemoveAllocationsJob)
Processes the m_AllocationRemoves queue, computing elapsed time relative to m_CurrentTime and freeing entries once they are older than the threshold (uses wrap-around math and threshold of 255 ticks). Decrements AllocationInfo.m_AllocationCount for each freed allocation.
Usage Example
// Example: allocate GPU slots and schedule an upload (conceptual)
ProceduralEmissiveSystem emissiveSystem = world.GetOrCreateSystemManaged<ProceduralEmissiveSystem>();
// 1) Acquire heap allocator & helpers to allocate blocks from a job or main thread.
NativeReference<ProceduralEmissiveSystem.AllocationInfo> allocInfo;
NativeQueue<ProceduralEmissiveSystem.AllocationRemove> allocRemoves;
int currentTime;
JobHandle heapDeps;
NativeHeapAllocator allocator = emissiveSystem.GetHeapAllocator(out allocInfo, out allocRemoves, out currentTime, out heapDeps);
// Use allocator.Allocate(...) to reserve float4 slots, enqueue removal entries into allocRemoves when freeing,
// and update allocInfo.m_AllocationCount appropriately from job/main thread as needed.
// Remember to call emissiveSystem.AddHeapWriter(jobHandle) if allocations are performed in jobs.
// 2) Prepare GPU upload (from worker jobs)
var uploader = emissiveSystem.BeginUpload(opCount: 1, dataSize: (uint)bytesToUpload, maxOpSize: (uint)maxChunk);
// write into uploader via ThreadedSparseUploader APIs on worker threads
emissiveSystem.AddUploadWriter(uploadJobHandle);
// 3) Finalize uploads (main thread, before render)
emissiveSystem.CompleteUpload();
Notes and tips for modders: - The system reserves index 0 as a neutral/identity entry — user light arrays must write starting at index 1. - When allocating from jobs, keep track of and enqueue an AllocationRemove with a remove time (use currentTime returned by GetHeapAllocator) instead of freeing immediately to avoid GPU race conditions. - Always add job handles via AddHeapWriter / AddUploadWriter so the system can correctly synchronize and resize buffers safely. - Call CompleteUpload() on the main thread (or ensure uploads are committed) before relying on newly uploaded data in rendering.