Files
UnrealEngine/Engine/Plugins/Media/D3D12VideoDecodersElectra/Source/Private/VideoDecoder_D3D12_Common.h
2025-05-18 13:04:45 +08:00

506 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Misc/ScopeLock.h"
#include "Templates/SharedPointer.h"
#include "Misc/TVariant.h"
#include "Containers/Queue.h"
#include "IElectraDecoder.h"
#include "IElectraDecoderResourceDelegate.h"
#include "IElectraDecoderFeaturesAndOptions.h"
#include "IElectraDecoderOutputVideo.h"
#include "ElectraDecodersUtils.h"
/*********************************************************************************************************************/
#include COMPILED_PLATFORM_HEADER(PlatformHeaders_Video_D3D.h)
#include "DecoderErrors_D3D12.h"
/*********************************************************************************************************************/
namespace ElectraVideoDecodersD3D12Video
{
class FCodecFormatHelper
{
public:
enum class ECodecType
{
H264,
H265,
VP9
};
struct FCodecInfo
{
ECodecType CodecType;
bool b10Bit = false;
GUID ProfileGUID;
TArray<DXGI_FORMAT> PixelFormats;
bool operator == (const FCodecInfo& rhs) const
{
return CodecType == rhs.CodecType && b10Bit == rhs.b10Bit && ProfileGUID == rhs.ProfileGUID && PixelFormats == rhs.PixelFormats;
}
};
FCodecFormatHelper() = default;
~FCodecFormatHelper() = default;
int32 FindSupportedFormats(ID3D12Device* InD3D12Device);
const FCodecInfo* HaveFormat(ECodecType InType, int32 InNumBits);
TRefCountPtr<ID3D12VideoDevice> GetVideoDevice()
{
return DxVideoDevice;
}
uint32 GetVideoDeviceNodeIndex()
{
return DxDeviceNodeIndex;
}
private:
// The profile GUIDs we support.
const GUID D3D12_VIDEO_DECODE_PROFILE_H264 = { 0x1b81be68, 0xa0c7, 0x11d3, { 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5 } };
const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN = { 0x5b11d51b, 0x2f4c, 0x4452, { 0xbc, 0xc3, 0x09, 0xf2, 0xa1, 0x16, 0x0c, 0xc0 } };
const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10 = { 0x107af0e0, 0xef1a, 0x4d19, { 0xab, 0xa8, 0x67, 0xa1, 0x63, 0x07, 0x3d, 0x13 } };
const GUID D3D12_VIDEO_DECODE_PROFILE_VP9 = { 0x463707f8, 0xa1d0, 0x4585, { 0x87, 0x6d, 0x83, 0xaa, 0x6d, 0x60, 0xb8, 0x9e } };
const GUID D3D12_VIDEO_DECODE_PROFILE_VP9_10BIT_PROFILE2 = { 0xa4c749ef, 0x6ecf, 0x48aa, { 0x84, 0x48, 0x50, 0xa7, 0xa1, 0x16, 0x5f, 0xf7 } };
/*
const GUID D3D12_VIDEO_DECODE_PROFILE_VP8 = { 0x90b899ea, 0x3a62, 0x4705, { 0x88, 0xb3, 0x8d, 0xf0, 0x4b, 0x27, 0x44, 0xe7 } };
const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE0 = { 0xb8be4ccb, 0xcf53, 0x46ba, { 0x8d, 0x59, 0xd6, 0xb8, 0xa6, 0xda, 0x5d, 0x2a } };
const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE1 = { 0x6936ff0f, 0x45b1, 0x4163, { 0x9c, 0xc1, 0x64, 0x6e, 0xf6, 0x94, 0x61, 0x08 } };
const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE2 = { 0x0c5f2aa1, 0xe541, 0x4089, { 0xbb, 0x7b, 0x98, 0x11, 0x0a, 0x19, 0xd7, 0xc8 } };
const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2 = { 0x17127009, 0xa00f, 0x4ce1, { 0x99, 0x4e, 0xbf, 0x40, 0x81, 0xf6, 0xf3, 0xf0 } };
const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2_420 = { 0x2d80bed6, 0x9cac, 0x4835, { 0x9e, 0x91, 0x32, 0x7b, 0xbc, 0x4f, 0x9e, 0xe8 } };
*/
TArray<FCodecInfo> CodecInfos;
TRefCountPtr<ID3D12VideoDevice> DxVideoDevice;
uint32 DxDeviceNodeIndex = 0;
};
class FSyncObject
{
public:
virtual ~FSyncObject()
{
AwaitCompletion(0);
if (EventHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(EventHandle);
}
Fence.SafeRelease();
}
HRESULT Create(const TRefCountPtr<ID3D12Device>& InDevice, uint64 InInitialValue)
{
Value = InInitialValue;
HRESULT Result = InDevice->CreateFence(Value, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), (void**)Fence.GetInitReference());
if (Result == S_OK)
{
EventHandle = CreateEvent(nullptr, false, false, nullptr);
check(EventHandle != INVALID_HANDLE_VALUE);
Result = EventHandle != INVALID_HANDLE_VALUE ? Result : ERROR_INVALID_HANDLE;
}
return Result;
}
TRefCountPtr<ID3D12Fence> GetFence()
{
return Fence;
}
void* GetID3D12Fence()
{
return Fence.GetReference();
}
uint64 IncrementAndGetNewFenceValue()
{
++Value;
return Value;
}
uint64& GetFenceValue()
{
return Value;
}
bool AwaitCompletion(uint32 InTimeoutMillisec)
{
if (Fence.IsValid())
{
uint64 CompletedValue = Fence->GetCompletedValue();
if (CompletedValue < Value)
{
HRESULT Result = Fence->SetEventOnCompletion(Value, EventHandle);
if (!ensure(Result == S_OK))
{
return false;
}
return WaitForSingleObjectEx(EventHandle, InTimeoutMillisec, false) == WAIT_OBJECT_0;
}
return true;
}
return false;
}
private:
TRefCountPtr<ID3D12Fence> Fence;
HANDLE EventHandle = INVALID_HANDLE_VALUE;
uint64 Value = 0;
};
struct FDecodedFrame
{
TRefCountPtr<ID3D12Resource> Texture;
FSyncObject Sync;
int32 IndexInPictureBuffer = 0;
};
class FDecodedPictureBuffer
{
public:
~FDecodedPictureBuffer();
void ReleaseAllFrames(int32 InWaitForEachFrameMillis);
TSharedPtr<FDecodedFrame, ESPMode::ThreadSafe> GetFrameAtIndex(int32 InIndex);
TSharedPtr<FDecodedFrame, ESPMode::ThreadSafe> GetFrameForResource(const ID3D12Resource* InResource);
TSharedPtr<FDecodedFrame, ESPMode::ThreadSafe> GetNextUnusedFrame();
void ReturnUnusedFrameToAvailableQueue(TSharedPtr<FDecodedFrame, ESPMode::ThreadSafe>&& InFrame);
void ReturnFrameToAvailableQueue(TSharedPtr<FDecodedFrame, ESPMode::ThreadSafe>&& InFrame);
public:
TArray<TSharedPtr<FDecodedFrame, ESPMode::ThreadSafe>> Frames;
TArray<TSharedPtr<FDecodedFrame, ESPMode::ThreadSafe>> AvailableQueue;
};
class FVideoDecoderOutputD3D12Electra : public IElectraDecoderVideoOutput, public IElectraDecoderVideoOutputImageBuffers
{
public:
virtual ~FVideoDecoderOutputD3D12Electra()
{
}
FTimespan GetPTS() const override
{
return PTS;
}
uint64 GetUserValue() const override
{
return UserValue;
}
EOutputType GetOutputType() const
{
return OutputType;
}
int32 GetWidth() const override
{
return ImageWidth;
}
int32 GetHeight() const override
{
return ImageHeight;
}
int32 GetDecodedWidth() const override
{
return Width;
}
int32 GetDecodedHeight() const override
{
return Height;
}
FElectraVideoDecoderOutputCropValues GetCropValues() const override
{
return Crop;
}
int32 GetAspectRatioW() const override
{
return AspectW;
}
int32 GetAspectRatioH() const override
{
return AspectH;
}
int32 GetFrameRateNumerator() const override
{
return FrameRateN;
}
int32 GetFrameRateDenominator() const override
{
return FrameRateD;
}
int32 GetNumberOfBits() const override
{
return NumBits;
}
void GetExtraValues(TMap<FString, FVariant>& OutExtraValues) const override
{
OutExtraValues = ExtraValues;
}
void* GetPlatformOutputHandle(EElectraDecoderPlatformOutputHandleType InTypeOfHandle) const override
{
if (InTypeOfHandle == EElectraDecoderPlatformOutputHandleType::ImageBuffers)
{
return static_cast<IElectraDecoderVideoOutputImageBuffers*>(const_cast<FVideoDecoderOutputD3D12Electra*>(this));
}
return nullptr;
}
IElectraDecoderVideoOutputTransferHandle* GetTransferHandle() const override
{
return nullptr;
}
IElectraDecoderVideoOutput::EImageCopyResult CopyPlatformImage(IElectraDecoderVideoOutputCopyResources* InCopyResources) const override
{
return IElectraDecoderVideoOutput::EImageCopyResult::NotSupported;
}
// Methods from IElectraDecoderVideoOutputImageBuffers
uint32 GetCodec4CC() const override
{
return Codec4CC;
}
int32 GetNumberOfBuffers() const override
{
return 1;
}
TSharedPtr<TArray<uint8>, ESPMode::ThreadSafe> GetBufferDataByIndex(int32 InBufferIndex) const override
{
check(InBufferIndex == 0);
// No CPU data here.
return nullptr;
}
void* GetBufferTextureByIndex(int32 InBufferIndex) const override
{
check(InBufferIndex == 0);
if (DecodedFrame.IsValid())
{
return DecodedFrame->Texture;
}
return nullptr;
}
EElectraDecoderPlatformPixelFormat GetBufferFormatByIndex(int32 InBufferIndex) const override
{
return BufferFormat;
}
EElectraDecoderPlatformPixelEncoding GetBufferEncodingByIndex(int32 InBufferIndex) const override
{
return BufferEncoding;
}
int32 GetBufferPitchByIndex(int32 InBufferIndex) const override
{
return Pitch;
}
bool GetBufferTextureSyncByIndex(int32 InBufferIndex, FElectraDecoderOutputSync& SyncObject) const override
{
if (InBufferIndex == 0 && DecodedFrame.IsValid())
{
// Provide the caller with the decode fence and associated value.
SyncObject.Sync = DecodedFrame->Sync.GetFence();
SyncObject.SyncValue = DecodedFrame->Sync.GetFenceValue();
// Now, since we are asked to provide the sync object we *CONTRACTUALLY* assume that the caller will *DO*
// something with the output.
// As such, we also return the fence as the copy-complete fence with an increased fence value.
SyncObject.CopyDoneSync = DecodedFrame->Sync.GetFence();
SyncObject.CopyDoneSyncValue = DecodedFrame->Sync.IncrementAndGetNewFenceValue();
return true;
}
return false;
}
public:
FTimespan PTS;
uint64 UserValue = 0;
FElectraVideoDecoderOutputCropValues Crop;
int32 ImageWidth = 0;
int32 ImageHeight = 0;
int32 Width = 0;
int32 Height = 0;
int32 Pitch = 0;
int32 NumBits = 0;
int32 AspectW = 1;
int32 AspectH = 1;
int32 FrameRateN = 0;
int32 FrameRateD = 0;
int32 PixelFormat = 0;
TMap<FString, FVariant> ExtraValues;
EOutputType OutputType = EOutputType::Output;
uint64 UserValue0 = 0;
bool bDoNotOutput = false;
uint32 Codec4CC = 0;
EElectraDecoderPlatformPixelFormat BufferFormat = EElectraDecoderPlatformPixelFormat::INVALID;
EElectraDecoderPlatformPixelEncoding BufferEncoding = EElectraDecoderPlatformPixelEncoding::Native;
TSharedPtr<FDecodedPictureBuffer, ESPMode::ThreadSafe> OwningDPB;
TSharedPtr<FDecodedFrame, ESPMode::ThreadSafe> DecodedFrame;
};
class FD3D12VideoDecoder : public IElectraDecoder
{
public:
static bool D3D12VIDEODECODERSELECTRA_API CheckPlatformDecodeCapabilities(D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT& InOutDecodeSupport, const ElectraDecodersUtil::FMimeTypeVideoCodecInfo& InCodecInfo, const TMap<FString, FVariant>& InOptions);
FD3D12VideoDecoder(const FCodecFormatHelper::FCodecInfo& InCodecInfo, const D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT& InDecodeSupport, const TMap<FString, FVariant>& InOptions, TSharedPtr<IElectraDecoderResourceDelegate, ESPMode::ThreadSafe> InResourceDelegate, const TRefCountPtr<ID3D12Device>& InD3D12Device, const TRefCountPtr<ID3D12VideoDevice>& InVideoDevice, uint32 InVideoDeviceNodeIndex);
virtual ~FD3D12VideoDecoder();
protected:
EType GetType() const override
{ return IElectraDecoder::EType::Video; }
void GetFeatures(TMap<FString, FVariant>& OutFeatures) const override;
FError GetError() const override
{ return LastError; }
void Close() override;
ECSDCompatibility IsCompatibleWith(const TMap<FString, FVariant>& CSDAndAdditionalOptions) = 0;
bool ResetToCleanStart() override;
TSharedPtr<IElectraDecoderDefaultOutputFormat, ESPMode::ThreadSafe> GetDefaultOutputFormatFromCSD(const TMap<FString, FVariant>& CSDAndAdditionalOptions) = 0;
EDecoderError DecodeAccessUnit(const FInputAccessUnit& InInputAccessUnit, const TMap<FString, FVariant>& InAdditionalOptions) = 0;
EDecoderError SendEndOfData() = 0;
EDecoderError Flush() = 0;
EOutputStatus HaveOutput() override;
TSharedPtr<IElectraDecoderOutput, ESPMode::ThreadSafe> GetOutput() override;
TSharedPtr<IElectraDecoderBitstreamProcessor, ESPMode::ThreadSafe> CreateBitstreamProcessor() = 0;
void Suspend() override
{ }
void Resume() override
{ }
protected:
bool PostError(HRESULT ApiReturnValue, FString Message, int32 Code)
{
LastError.Code = Code;
LastError.SdkCode = ApiReturnValue;
LastError.Message = MoveTemp(Message);
return false;
}
class FAutoReturnUnusedFrame
{
public:
FAutoReturnUnusedFrame(const TSharedPtr<FDecodedPictureBuffer, ESPMode::ThreadSafe>& InDPB, const TSharedPtr<FDecodedFrame, ESPMode::ThreadSafe>& InFrame)
: OwningDPB(InDPB), ThisFrame(InFrame)
{}
~FAutoReturnUnusedFrame()
{
if (ThisFrame.IsValid())
{
OwningDPB->ReturnUnusedFrameToAvailableQueue(MoveTemp(ThisFrame));
}
}
void ReleaseOwnership()
{
ThisFrame.Reset();
}
private:
TSharedPtr<FDecodedPictureBuffer, ESPMode::ThreadSafe> OwningDPB;
TSharedPtr<FDecodedFrame, ESPMode::ThreadSafe> ThisFrame;
};
// A structure holding every resource needed to decode one frame.
struct FFrameDecodeResource
{
// Decoder keep-alive resources.
TRefCountPtr<ID3D12VideoDecoder> D3DDecoder;
TRefCountPtr<ID3D12VideoDecoderHeap> D3DDecoderHeap;
// Active decoding resource.
enum
{
kMaxRefFrames = 32
};
ID3D12Resource* ReferenceFrameList[kMaxRefFrames] {};
UINT ReferenceFrameListSubRes[kMaxRefFrames] { 0 };
TRefCountPtr<ID3D12Resource> D3DBitstreamBuffer;
uint32 D3DBitstreamBufferAllocatedSize = 0;
uint32 D3DBitstreamBufferPayloadSize = 0;
struct FInputEmpty
{
};
struct FInputH264
{
DXVA_PicParams_H264 PicParams;
DXVA_Qmatrix_H264 QuantMtx;
TArray<DXVA_Slice_H264_Short> SliceHeaders;
};
struct FInputH265
{
DXVA_PicParams_HEVC PicParams;
DXVA_Qmatrix_HEVC QuantMtx;
TArray<DXVA_Slice_HEVC_Short> SliceHeaders;
};
TVariant<FInputEmpty, FInputH264, FInputH265> PicInput;
};
struct FDecoderConfiguration
{
void Reset()
{
MaxDecodedWidth = 0;
MaxDecodedHeight = 0;
MaxNumInDPB = 0;
VideoDecoderHeap.SafeRelease();
VideoDecoderDPBWidth = 0;
VideoDecoderDPBHeight = 0;
}
int32 MaxDecodedWidth = 0;
int32 MaxDecodedHeight = 0;
int32 MaxNumInDPB = 0;
TRefCountPtr<ID3D12VideoDecoderHeap> VideoDecoderHeap;
int32 VideoDecoderDPBWidth = 0;
int32 VideoDecoderDPBHeight = 0;
};
bool InternalDecoderCreate();
void ReturnAllFrames();
bool CreateDecoderHeap(int32 InDPBSize, int32 InMaxWidth, int32 InMaxHeight, int32 InImageSizeAlignment);
bool CreateDPB(TSharedPtr<FDecodedPictureBuffer, ESPMode::ThreadSafe>& OutDPB, int32 InMaxWidth, int32 InMaxHeight, int32 InImageSizeAlignment, int32 InNumFrames);
constexpr uint32 GetNodeMask() const
{ return VideoDeviceNodeIndex; }
bool PrepareBitstreamBuffer(const TSharedPtr<FFrameDecodeResource, ESPMode::ThreadSafe>& InFrameDecodeResourceToPrepare, uint32 InMaxInputBufferSize);
EDecoderError ExecuteCommonDecode(const D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS& InInputArgs, const D3D12_VIDEO_DECODE_OUTPUT_STREAM_ARGUMENTS& InOutputArgs);
virtual bool InternalResetToCleanStart() = 0;
FCodecFormatHelper::FCodecInfo CodecInfo;
D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT DecodeSupport;
TMap<FString, FVariant> InitialCreationOptions;
TWeakPtr<IElectraDecoderResourceDelegate, ESPMode::ThreadSafe> ResourceDelegate;
TRefCountPtr<ID3D12Device> D3D12Device;
TRefCountPtr<ID3D12VideoDevice> VideoDevice;
uint32 VideoDeviceNodeIndex = 0;
IElectraDecoder::FError LastError;
TUniquePtr<FSyncObject> VideoDecoderSync;
TRefCountPtr<ID3D12CommandQueue> VideoDecoderCommandQueue;
TRefCountPtr<ID3D12CommandAllocator> VideoDecoderCommandAllocator;
TRefCountPtr<ID3D12VideoDecodeCommandList> VideoDecoderCommandList;
TRefCountPtr<ID3D12VideoDecoder> VideoDecoder;
uint32 StatusReportFeedbackNumber = 0;
FDecoderConfiguration CurrentConfig;
// Queue of frame decode resources that are available again for re-use.
TQueue<TSharedPtr<FFrameDecodeResource, ESPMode::ThreadSafe>> AvailableFrameDecodeResourceQueue;
// Currently active frame decode resources that await completion.
//TArray<TSharedPtr<FFrameDecodeResource, ESPMode::ThreadSafe>> ActiveFrameDecodeResources;
TSharedPtr<FDecodedPictureBuffer, ESPMode::ThreadSafe> DPB;
TSharedPtr<FDecodedFrame, ESPMode::ThreadSafe> MissingReferenceFrame;
uint32 RunningFrameNumLo = 0;
uint32 RunningFrameNumHi = 0;
bool bIsDraining = false;
TArray<TSharedPtr<FVideoDecoderOutputD3D12Electra, ESPMode::ThreadSafe>> FramesInDecoder;
TArray<TSharedPtr<FVideoDecoderOutputD3D12Electra, ESPMode::ThreadSafe>> FramesReadyForOutput;
TArray<TSharedPtr<FVideoDecoderOutputD3D12Electra, ESPMode::ThreadSafe>> FramesGivenOutForOutput;
};
}