// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "AudioBusSubsystem.h" #include "Containers/Map.h" #include "Tasks/Task.h" DECLARE_LOG_CATEGORY_EXTERN(LogAudioRenderScheduler, Display, All); namespace Audio { // Forward declarations struct FAudioBusKey; class FMixerDevice; struct IAudioMixerRenderStep; // An identifier that uniquely identifies a given render step to the scheduler. class FAudioRenderStepId { public: FAudioRenderStepId() : Value(INDEX_NONE) { } static FAudioRenderStepId FromTransmitterID(const uint64 TransmitterID); static FAudioRenderStepId FromAudioBusKey(const FAudioBusKey BusKey); uint64 GetRawValue() const { return Value; } friend uint32 GetTypeHash(FAudioRenderStepId Id) { return GetTypeHash(Id.Value); } friend bool operator==(const FAudioRenderStepId InLHS, const FAudioRenderStepId InRHS) { return InLHS.Value == InRHS.Value; } friend bool operator!=(const FAudioRenderStepId InLHS, const FAudioRenderStepId InRHS) { return InLHS.Value != InRHS.Value; } bool IsValid() const { return Value != INDEX_NONE; } protected: FAudioRenderStepId(uint64 InValue) : Value(InValue) { } uint64 Value; }; // FAudioRenderScheduler allows individual audio render work items ("steps") to be added to be run as part of each rendering block. // Dependencies can be declared to enforce ordering and synchronization between steps when needed. There can be cyclical // dependencies, in which case the scheduler will do its best to consistently break the cycles in the same way every block; // but cycles should be avoided when possible. // // Render steps are executed on worker threads using the UE Tasks system, unless single-threaded rendering is requested. Currently // only source rendering uses the scheduler. // // All methods of this class are to be called from the audio render thread only. class FAudioRenderScheduler { public: UE_NONCOPYABLE(FAudioRenderScheduler); FAudioRenderScheduler(FMixerDevice* InMixerDevice); // Add/remove a render step. void AddStep(const FAudioRenderStepId Id, IAudioMixerRenderStep* Step); void RemoveStep(const FAudioRenderStepId Id); // Declare that one step is dependent on another. It's possible to add a dependency without // having previously called AddStep; in this case the dependency will still be respected with // an empty placeholder step. void AddDependency(const FAudioRenderStepId FirstStep, const FAudioRenderStepId SecondStep); // All dependencies declared with AddDependency() must be removed by a corresponding call to RemoveDependency(). void RemoveDependency(const FAudioRenderStepId FirstStep, const FAudioRenderStepId SecondStep); // Execute all steps and block until they're done. void RenderBlock(const bool bSingleThreaded); private: // Internally the step id's are mapped to smaller internal indices to reduce // memory footprint. using FInternalIndex = int16; void ScheduleAll(const bool bSingleThreaded); FInternalIndex GetInternalIndex(const FAudioRenderStepId Id); void RemoveReference(FAudioRenderScheduler::FInternalIndex Index); void BreakCycle(const int FirstPendingStepIndex); void CheckBrokenLinks(); void BreakLink(FInternalIndex FirstStep, FInternalIndex SecondStep); // Helpers to verify that a link is still part of a cycle bool FindIndirectLink(const FInternalIndex FirstStep, const FInternalIndex SecondStep); bool FindIndirectLinkSkipping(const FInternalIndex FirstStep, const FInternalIndex SecondStep, TArray& StepsToSkip); struct FStepEntry { FAudioRenderStepId StepId; IAudioMixerRenderStep* StepInterface = nullptr; TArray> Prerequisites; int32 ReferenceCount = 0; bool bLaunched = false; bool bCheckBrokenLinks = false; UE::Tasks::FTask TaskHandle; // Unused step entries are added to a free list. FInternalIndex NextFreeIndex = INDEX_NONE; }; // Only used to check the current thread; may be null. FMixerDevice* MixerDevice; TMap IndexMap; TArray Steps; FInternalIndex FirstFreeIndex = INDEX_NONE; // Array of all used step indices. Ends up sorted at the end of RenderBlock(). TArray ActiveSteps; // Have any changes happened since the previous call to RenderBlock()? bool bDirty = true; // Links that have been broken to eliminate cycles. These are retained between // blocks to improve stability of step ordering. TArray> BrokenLinks; }; }