RaytracingContext

This class manages emitters, primitives, raytracing and multithreading.

public class RaytracingContext
{
    // Adds an Emitter to the raytracing scene. This method is thread-safe and will not affect the current raytracing threads.
    public Emitter AddEmitter(Emitter emitter);

    // Create a new material
    public void AddMaterial(MaterialType type, MaterialProperties properties, Color colour);

    // Adds a 3D object to the raytracing scene. This method is thread-safe and will not affect the current raytracing threads. Primitives completely outside the world bounds will be ignored during raytracing.
    public void AddPrimitive(Primitive primitive);

    // Air absorption settings. Set to null to disable air absorption.
    public AirAbsorptionSettings AirAbsorption;

    // The average time (in milliseconds) spent in the analysing thread after the raytracing threads complete. This phase runs after raytracing completes and calculate reverb properties. Use this to monitor raytracing performance and adjust ray counts if needed.
    public float AnalysisTime;

    // The pitch of the camera in the debug window.
    public float CameraPitch;

    // The position of the camera in the debug window.
    public Vector3F CameraPosition;

    // The yaw of the camera in the debug window.
    public float CameraYaw;

    // Custom formulas for calculating EAX properties (diffusion, density, etc). Set to null to use default formulas.
    public CustomEAXFormulas CustomEAXFormulas;

    // Waits for background thread to complete, then disposes everything. After calling this method, this context cannot be reused.
    public void Dispose();

    // Whether Emitters outside the world have 0 occlusion/permeation energy (true) or maximum energy (false).
    public bool EmittersOutsideTheWorldAreMuffled;

    // The field of view (in radians) of the camera in the debug window.
    public float FieldOfView;

    // Get properties for a specific material. You must set context.materialsDirty = true; when editing materials
    public MaterialProperties GetMaterial(MaterialType type);

    // List of grouped EAX reverb properties for all emitters. Contains parameters compatible with EAX reverb effects.
    public List<EAXReverbResults> GroupedEAX = [];

    // Returns true if a material exists
    public bool HasMaterial(MaterialType type);

    // Inverse speed of sound in seconds per meter. Defaults to 1.0f / 343.0f. Affects reverb calculation
    public float InverseSpeedOfSound;

    // A custom logging callback. Defaults to WriteLine()
    public Action<string> LogCallback;

    // Whether to log memory allocation warnings
    public bool LogMemoryAllocationWarnings;

    // The average time (in milliseconds) spent by Update on the main thread. This includes time spent handling previous raytracing results, applying new settings, processing primitive updates and submitting work to background threads. Use this metric to monitor main thread performance impact.
    public float MainThreadTime;

    // Set this to true after updating materials or creating new materials. This will cause all rays to be re-cast.
    public bool MaterialsDirty;

    // The maximum amount of threads that can run at any one time
    public int MaximumConcurrencyLevel;

    // The maximum number of grouped EAX reverb properties created for all emitters. Higher values increase accuracy but are more expensive to run.
    public int MaximumGroupedEAXCount;

    // Gets meters per world unit. Affects air absorption and reverb calculation.
    public float MetersPerUnit;

    // This is invoked after EAX reverb results are updated, and before emitter.OnRaytracingComplete callbacks are invoked. This gives you a chance to update your EAX effects, so they can be applied to an emitter in it's OnRaytracingComplete callback.
    public Action OnReverbUpdated = null;

    // The average time (in milliseconds) spent in the preparation thread before the raytracing threads begin. This phase updates the BVH (Bounding Volume Hierarchy) acceleration structure with new, modified, and removed primitives. Higher values are a result of complex scene changes.
    public float PreparationTime;

    // The percentage of rays that were reused from previous frames, rather than being cast this frame. Higher values indicate better performance.
    public float RayCachePercent;

    // The number of rays cast this frame.
    public int RaysCastThisFrame = 0;

    // Creates a new raytracing context
    public RaytracingContext();

    // The average time (in milliseconds) spent in the raytracing threads. This is the real-world elapsed time it takes for raytracing to complete, not the sum of all thread times. This value is automatically adjusted based on how many threads perform raytracing. Use this to monitor raytracing performance and adjust ray counts if needed.
    public float RaytracingTime;

    // High-frequency reference (Hz) for air absorption, reverb, and material scattering
    public float ReferenceFrequencyHF;

    // Low-frequency reference (Hz) for air absorption, reverb, and material scattering
    public float ReferenceFrequencyLF;

    // Remove an Emitter from the raytracing simulation. This method is thread-safe and will not affect the current raytracing threads. This emitter's OnRaytracingComplete callback will not be invoked.
    public void RemoveEmitter(Emitter emitter);

    // Removes a 3D object from the raytracing scene. This method is thread-safe and will not affect the current raytracing threads.
    public void RemovePrimitive(Primitive primitive);

    // Returns true when raytracing has run at least once, and it is safe to access reverb objects
    public bool ReverbCalculated;

    // The total number of rays that could potentially be cast each frame, including trail rays, reverb rays, occlusion rays, permeation rays and visualisation rays
    public int TotalPossibleRayCount;

    // Updates the raytracing simulation. Call this method regularly to process raytracing results and submit new work. This method does nothing if background raytracing threads are still running. When threads are idle, it performs the following operations: - Handles the last raytracing results, updating reverb objects and invoking OnRaytracedByAnotherEmitter callbacks - Applies new settings and resizes memory buffers if needed (e.g., if ray counts were changed) - Processes new, modified, and removed primitives - Starts raytracing again on background threads This method must be called from the main thread. The actual raytracing runs on background threads. Calling this more frequently (multiple times per frame) is safe and can reduce latency for emitter updates.
    public void Update();

    // Blocks the calling thread until all background raytracing threads complete, then handles the results (updates reverb objects and invokes OnRaytracingComplete and OnRaytracedByAnotherEmitter callbacks for each emitter).
    public void Wait();

    // The number of work items to split trails across for load balancing. A higher workItemCount helps evenly distribute work across all threads.
    public int WorkItemCount;

    // Whether the entire world is considered indoors or outdoors. When false, reverb rays stop checking for line-of-sight after hitting the world edge. Defaults to false.
    public bool WorldIsIndoors;

    // The maximum bounds of the world (WorldPosition + WorldSize)
    public Vector3F WorldMax;

    // The minimum bounds of the world. Emitters outside the world will not be raytraced, and Primitives that are fully outside these bounds will not affect raytracing.
    public Vector3F WorldPosition;

    // The size of the world. Emitters outside the world will not be raytraced, and Primitives that are fully outside these bounds will be ignored
    public Vector3F WorldSize;
}

WorkItemCount

This setting controls how raytracing work is divided across multiple background threads. If you have 8 threads, it seems logical to split the work into 8 work items (one for each thread). However some threads might finish earlier than others, meaning not all threads will be fully utilised.

To prevent threads from idling while other threads run, you can divide the raytracing work into smaller work items. To do this, set workItemCount to a number higher than maximumConcurrencyLevel:

var context = new RaytracingContext()
{
    OcclusionRayCount = 1024,
    OcclusionBounceCount = 8,
    WorkItemCount = 128,
    MaximumConcurrencyLevel = 8,
};

This means 8 background threads will perform raytracing, and will take work from a pool of 128 work items. On average each thread should process 128/8 = 16 work items each.