Files
2025-05-18 13:04:45 +08:00

353 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Misc/Timespan.h"
#include "Math/IntPoint.h"
#include "Templates/Atomic.h"
#include "Templates/SharedPointer.h"
#include "Templates/RefCounting.h"
#include "Containers/Queue.h"
#include "Misc/ScopeLock.h"
#include "HAL/PlatformProcess.h"
#include "HAL/PlatformMisc.h"
#include "Tickable.h"
#include "ParameterDictionary.h"
struct FDecoderTimeStamp
{
FDecoderTimeStamp() {}
FDecoderTimeStamp(FTimespan InTime, int64 InSequenceIndex) : Time(InTime), SequenceIndex(InSequenceIndex) {}
FTimespan Time;
int64 SequenceIndex;
};
class IDecoderOutputPoolable : public TSharedFromThis<IDecoderOutputPoolable, ESPMode::ThreadSafe>
{
public:
virtual void InitializePoolable() { }
virtual void ShutdownPoolable() { }
virtual bool IsReadyForReuse() { return true; }
public:
virtual ~IDecoderOutputPoolable() { }
};
template<typename ObjectType> class TElectraPoolDefaultObjectFactory
{
public:
static ObjectType* Create()
{
return new ObjectType;
}
};
/**
* This class defines a pool of objects that inherit from the `IDecoderOutputPoolable` interface.
* The pool has no bound on the number of objects created, it merely tracks which objects have
* been handed out and awaits their return when the pool is destroyed.
*/
template<typename ObjectType, typename ObjectFactory = TElectraPoolDefaultObjectFactory<ObjectType>>
class TDecoderOutputObjectPool
{
public:
/**
* Creates a new object pool.
* The pool has a custom deleter which only marks the pool as expired, but keeps it
* around until all the elements it has ever handed out are returned and indicate
* that they are no longer being used.
*/
static TSharedPtr<TDecoderOutputObjectPool, ESPMode::ThreadSafe> Create()
{
return MakeShareable(new TDecoderOutputObjectPool(), FPoolDeleter());
}
/**
* Acquires an object from the pool.
* This returns either a new object or one that was used before and has been
* returned to the pool. The object will not be re-initialized by default.
* For this your managed object needs to implement the `InitializePoolable()`
* method. Likewise, for object members that should be freed before the object
* enters the pool for re-use, the object needs to implement the `ShutdownPoolable()`
* method.
*/
TSharedRef<ObjectType, ESPMode::ThreadSafe> AcquireShared()
{
return MakeShareable(ObjectPool->Acquire(), TObjectDeleter(ObjectPool));
}
private:
static_assert(TPointerIsConvertibleFromTo<ObjectType, IDecoderOutputPoolable>::Value, "Poolable objects must implement the IDecoderOutputPoolable interface.");
/**
* This class defines the actual pool.
*/
template<typename T=ObjectFactory>
class TObjectPool : public FTickableGameObject
{
public:
TObjectPool() = default;
~TObjectPool() = default;
/** Called by the enclosing pool when it goes out of scope to let us handle cleanup of in-flight objects. */
void SetPendingDestruction(TSharedPtr<TObjectPool<T>, ESPMode::ThreadSafe> InSelf)
{
Self = MoveTemp(InSelf);
bIsPendingDestruction = true;
}
/** Acquire an object from the pool. */
ObjectType* Acquire()
{
// `Acquire()` cannot possibly be called when pool destruction is pending.
check(!bIsPendingDestruction);
ObjectType* Object = nullptr;
CriticalSection.Lock();
// Handle objects that have just become reusable again.
HandleNewReturns();
// Take an object from the available list, if there is one.
if (Available.Num() > 0)
{
Object = Available.Pop(EAllowShrinking::No);
}
CriticalSection.Unlock();
// If there is no object, we have to create a new one.
if (Object == nullptr)
{
Object = T::Create();
}
Object->InitializePoolable();
// Take note of the object we are handing out.
CriticalSection.Lock();
InFlight.Emplace(Object);
CriticalSection.Unlock();
return Object;
}
/** Return the given object to the pool. */
void Release(ObjectType* InObject)
{
if (InObject == nullptr)
{
return;
}
FScopeLock Lock(&CriticalSection);
check(InFlight.Contains(InObject));
InFlight.RemoveSwap(InObject, EAllowShrinking::No);
if (InObject->IsReadyForReuse())
{
InObject->ShutdownPoolable();
Available.Push(InObject);
}
else
{
Returned.Emplace(InObject);
}
}
bool IsTickableWhenPaused() const override
{ return true; }
bool IsTickableInEditor() const override
{ return true; }
void Tick(float /*InDeltaTime*/) override
{
if (!bIsPendingDestruction)
{
return;
}
CriticalSection.Lock();
// Handle objects that have just become reusable again.
HandleNewReturns();
CriticalSection.Unlock();
// Release all objects that are available now.
// We do not need to lock the mutex for this since `Acquire()` - which would need that -
// can no longer get called during a pending destruction as there is no user-code owner any more.
while(!Available.IsEmpty())
{
delete Available.Pop();
}
// If there are no more in-flight objects or objects that await reusability we can destroy ourselved.
if (InFlight.IsEmpty() && Returned.IsEmpty())
{
// Bye-bye.
Self.Reset();
}
}
bool IsTickable() const override
{ return true; }
TStatId GetStatId() const override
{ RETURN_QUICK_DECLARE_CYCLE_STAT(TObjectPool, STATGROUP_Tickables); }
private:
void HandleNewReturns()
{
// NOTE: This method expects the `CriticalSection` lock to have been locked already
// in order to reduce the number of lock/unlocks.
// Move all the returned objects that have become ready for reuse into a temporary list.
TArray<ObjectType*> NewlyReusableObjects;
for(int32 i=Returned.Num()-1; i>=0; --i)
{
if (Returned[i]->IsReadyForReuse())
{
NewlyReusableObjects.Emplace(Returned[i]);
Returned.RemoveAtSwap(i);
}
}
// If there are objects that have just become reusable we call their `ShutdownPoolable()`
// outside of our mutex lock in case what they are doing is costly.
if (NewlyReusableObjects.Num())
{
CriticalSection.Unlock();
for(int32 i=NewlyReusableObjects.Num()-1; i>=0; --i)
{
NewlyReusableObjects[i]->ShutdownPoolable();
}
CriticalSection.Lock();
Available.Append(MoveTemp(NewlyReusableObjects));
}
}
/** Critical section for synchronizing access to the free list. */
FCriticalSection CriticalSection;
/** List of available objects. */
TArray<ObjectType*> Available;
/** List of in-flight objects. */
TArray<ObjectType*> InFlight;
/** List of returned objects, waiting for reuseability. */
TArray<ObjectType*> Returned;
/** Pointer to self, which gets set when pool destruction is pending to lock it until complete. */
TSharedPtr<TObjectPool<T>, ESPMode::ThreadSafe> Self;
/** Flag indicating whether or not the pool is pending destruction. */
TAtomic<bool> bIsPendingDestruction { false };
};
/** Deleter for pooled objects. */
class TObjectDeleter
{
public:
/** Create and initialize a new instance. */
TObjectDeleter(const TSharedPtr<TObjectPool<ObjectFactory>, ESPMode::ThreadSafe>& InOwningPool)
: OwningPool(InOwningPool)
{ }
/** Function operator to execute deleter. */
void operator()(ObjectType* ObjectToDelete)
{
TSharedPtr<TObjectPool<ObjectFactory>, ESPMode::ThreadSafe> PinnedStorage = OwningPool.Pin();
// The weak pointer must be lockable since the pool has a circular reference to itself
// during pool destruction.
if (ensure(PinnedStorage.IsValid()))
{
PinnedStorage->Release(ObjectToDelete);
}
else
{
if (ensure(ObjectToDelete->IsReadyForReuse()))
{
delete ObjectToDelete;
}
}
}
private:
/** Weak pointer to the owning object pool. */
TWeakPtr<TObjectPool<ObjectFactory>, ESPMode::ThreadSafe> OwningPool;
};
/**
* This deleter class is invoked when the pool is to be destroyed.
* We flag the actual pool as such and let its handling take care of this.
*/
class FPoolDeleter
{
public:
void operator()(TDecoderOutputObjectPool* InPool)
{
// Only mark the actual pool for destruction.
// It handles itself during `Tick()` and will destroy itself
// once all objects are returned it it.
check(InPool->ObjectPool.IsValid());
InPool->ObjectPool->SetPendingDestruction(MoveTemp(InPool->ObjectPool));
}
};
/** Private constructor. */
TDecoderOutputObjectPool() : ObjectPool(MakeShareable(new TObjectPool<ObjectFactory>()))
{ }
/** The actual pool object. */
TSharedPtr<TObjectPool<ObjectFactory>, ESPMode::ThreadSafe> ObjectPool;
};
class IDecoderOutput;
class IDecoderOutputOwner
{
public:
virtual void SampleReleasedToPool(IDecoderOutput* InDecoderOutput) = 0;
};
class IDecoderOutput : public IDecoderOutputPoolable
{
public:
virtual void SetOwner(const TSharedPtr<IDecoderOutputOwner, ESPMode::ThreadSafe>& Renderer) = 0;
virtual FDecoderTimeStamp GetTime() const = 0;
virtual FTimespan GetDuration() const = 0;
virtual Electra::FParamDict& GetMutablePropertyDictionary()
{ return PropertyDictionary; }
private:
Electra::FParamDict PropertyDictionary;
};
namespace IDecoderOutputOptionNames
{
static const FName PTS(TEXT("pts"));
static const FName Duration(TEXT("duration"));
static const FName Width(TEXT("width"));
static const FName Height(TEXT("height"));
static const FName Pitch(TEXT("pitch"));
static const FName AspectRatio(TEXT("aspect_ratio"));
static const FName CropLeft(TEXT("crop_left"));
static const FName CropTop(TEXT("crop_top"));
static const FName CropRight(TEXT("crop_right"));
static const FName CropBottom(TEXT("crop_bottom"));
static const FName PixelFormat(TEXT("pixelfmt"));
static const FName PixelEncoding(TEXT("pixelenc"));
static const FName Orientation(TEXT("orientation"));
static const FName BitsPerComponent(TEXT("bits_per"));
static const FName HDRInfo(TEXT("hdr_info"));
static const FName Colorimetry(TEXT("colorimetry"));
static const FName AspectW(TEXT("aspect_w"));
static const FName AspectH(TEXT("aspect_h"));
static const FName FPSNumerator(TEXT("fps_num"));
static const FName FPSDenominator(TEXT("fps_denom"));
static const FName PixelDataScale(TEXT("pix_datascale"));
static const FName Timecode(TEXT("timecode"));
static const FName TMCDTimecode(TEXT("tmcd_timecode"));
static const FName TMCDFramerate(TEXT("tmcd_framerate"));
}