Skip to content

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.