Files
bgfx/src/video_d3d12.cpp
2026-06-15 17:13:38 +00:00

1846 lines
59 KiB
C++

/*
* Copyright 2011-2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
*/
#include "bgfx_p.h"
#if BGFX_CONFIG_RENDERER_DIRECT3D12
# include "renderer_d3d12.h"
# include "video_d3d12.h"
# if BGFX_CONFIG_VIDEO_DIRECT3D12
# include "video.h"
# include <dxva.h>
namespace bgfx { namespace d3d12
{
struct RendererContextD3D12;
void initVideoDecoder(const VideoBindingD3D12& _binding);
ID3D12PipelineState* videoGetPipelineState(RendererContextD3D12* _renderer, ProgramHandle _handle);
ScratchBufferD3D12& videoGetScratchBuffer(RendererContextD3D12* _renderer);
D3D12_GPU_DESCRIPTOR_HANDLE videoGetSamplerHandle(RendererContextD3D12* _renderer, const uint32_t* _samplerFlags);
ID3D12GraphicsCommandList* videoGetCommandList(RendererContextD3D12* _renderer);
void videoReleaseResource(RendererContextD3D12* _renderer, ID3D12Resource* _resource);
static VideoBindingD3D12 s_videoD3D12;
extern const GUID IID_ID3D12CommandAllocator;
extern const GUID IID_ID3D12CommandQueue;
extern const GUID IID_ID3D12Fence;
extern const GUID IID_ID3D12GraphicsCommandList;
extern const GUID IID_ID3D12Resource;
static const GUID IID_ID3D12VideoDecodeCommandList = { 0x3b60536e, 0xad29, 0x4e64, { 0xa2, 0x69, 0xf8, 0x53, 0x83, 0x7e, 0x5e, 0x53 } };
static const GUID IID_ID3D12VideoDecoder = { 0xc59b6bdc, 0x7720, 0x4074, { 0xa1, 0x36, 0x17, 0xa1, 0x56, 0x03, 0x74, 0x70 } };
static const GUID IID_ID3D12VideoDecoderHeap = { 0x0946b7c9, 0xebf6, 0x4047, { 0xbb, 0x73, 0x86, 0x83, 0xe2, 0x7d, 0xbb, 0x1f } };
static const GUID IID_ID3D12VideoDevice = { 0x1f052807, 0x0b46, 0x4acc, { 0x8a, 0x89, 0x36, 0x4f, 0x79, 0x37, 0x18, 0xa4 } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2 = { 0x17127009, 0xa00f, 0x4ce1, { 0x99, 0x4e, 0xbf, 0x40, 0x81, 0xf6, 0xf3, 0xf0 } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2_420 = { 0x2d80bed6, 0x9cac, 0x4835, { 0x9e, 0x91, 0x32, 0x7b, 0xbc, 0x4f, 0x9e, 0xe8 } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE0 = { 0xb8be4ccb, 0xcf53, 0x46ba, { 0x8d, 0x59, 0xd6, 0xb8, 0xa6, 0xda, 0x5d, 0x2a } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE1 = { 0x6936ff0f, 0x45b1, 0x4163, { 0x9c, 0xc1, 0x64, 0x6e, 0xf6, 0x94, 0x61, 0x08 } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE2 = { 0x0c5f2aa1, 0xe541, 0x4089, { 0xbb, 0x7b, 0x98, 0x11, 0x0a, 0x19, 0xd7, 0xc8 } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_H264 = { 0x1b81be68, 0xa0c7, 0x11d3, { 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5 } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN = { 0x5b11d51b, 0x2f4c, 0x4452, { 0xbc, 0xc3, 0x09, 0xf2, 0xa1, 0x16, 0x0c, 0xc0 } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10 = { 0x107af0e0, 0xef1a, 0x4d19, { 0xab, 0xa8, 0x67, 0xa1, 0x63, 0x07, 0x3d, 0x13 } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10_422 = { 0x0bac4fe5, 0x1532, 0x4429, { 0xa8, 0x54, 0xf8, 0x4d, 0xe0, 0x49, 0x53, 0xdb } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10_444 = { 0x0dabeffa, 0x4458, 0x4602, { 0xbc, 0x03, 0x07, 0x95, 0x65, 0x9d, 0x61, 0x7c } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12 = { 0x1a72925f, 0x0c2c, 0x4f15, { 0x96, 0xfb, 0xb1, 0x7d, 0x14, 0x73, 0x60, 0x3f } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12_422 = { 0x55bcac81, 0xf311, 0x4093, { 0xa7, 0xd0, 0x1c, 0xbc, 0x0b, 0x84, 0x9b, 0xee } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12_444 = { 0x9798634d, 0xfe9d, 0x48e5, { 0xb4, 0xda, 0xdb, 0xec, 0x45, 0xb3, 0xdf, 0x01 } };
static const GUID D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN_444 = { 0x4008018f, 0xf537, 0x4b36, { 0x98, 0xcf, 0x61, 0xaf, 0x8a, 0x2c, 0x1a, 0x33 } };
struct VideoDecoderD3D12
{
static constexpr uint32_t kNumDpbSlots = 17;
static constexpr uint32_t kBitstreamRingFrames = 8;
static constexpr uint32_t kCopyRingSize = 16;
struct ReorderedPicture
{
ID3D12Resource* texture;
int32_t displayOrder;
int64_t ptsUs;
};
struct DpbState
{
D3D12_RESOURCE_STATES state;
int32_t poc;
int32_t frameNum;
};
VideoDecoderD3D12()
: m_renderer(NULL)
, m_videoDevice(NULL)
, m_decoder(NULL)
, m_decoderHeap(NULL)
, m_videoQueue(NULL)
, m_videoFence(NULL)
, m_videoFenceValue(0)
, m_videoControl(BX_COUNTOF(m_videoCmdAllocator) )
, m_copyControl(BX_COUNTOF(m_copyCmdAllocator) )
, m_bitstreamBuffer(NULL)
, m_bitstreamMapped(NULL)
, m_bitstreamCapacity(0)
, m_bitstreamCursor(0)
, m_dummyCbv(NULL)
, m_dpb(NULL)
, m_dpbOutput(NULL)
, m_referenceOnly(false)
, m_codedWidth(0)
, m_codedHeight(0)
, m_dstWidth(0)
, m_dstHeight(0)
, m_numDpbSlots(kNumDpbSlots)
, m_nextSlot(0)
, m_currentSlot(0)
, m_numActiveRefs(0)
, m_displayOrderNext(0)
, m_prevPocLsb(0)
, m_prevPocMsb(0)
, m_displayedSlot(-1)
, m_prevDisplayedSlot(-1)
, m_statusReportFeedback(0)
, m_initFlags(0)
, m_cachedAuBytes(0)
{
bx::memSet(m_dpbState, 0, sizeof(m_dpbState) );
bx::memSet(m_referenceUsage, 0, sizeof(m_referenceUsage) );
bx::memSet(&m_spsActive, 0, sizeof(m_spsActive) );
for (uint32_t ii = 0; ii < kBitstreamRingFrames; ++ii)
{
m_videoCmdAllocator[ii] = NULL;
m_videoCmdAllocatorFence[ii] = 0;
}
m_videoCmd = NULL;
for (uint32_t ii = 0; ii < kCopyRingSize; ++ii)
{
m_copyCmdAllocator[ii] = NULL;
m_copyFenceRing[ii] = 0;
}
m_copyCmdList = NULL;
m_copyFence = NULL;
m_copyFenceValue = 0;
bx::memSet(m_slotLastCopyFence, 0, sizeof(m_slotLastCopyFence) );
m_lastDpbOutputCopyFence = 0;
for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii)
{
m_reorderPool[ii].texture = NULL;
m_reorderPool[ii].displayOrder = -1;
}
}
bool create(const VideoDecoderInit& _init, RendererContextD3D12* _renderer, uint16_t _width, uint16_t _height)
{
m_renderer = _renderer;
m_initFlags = _init.flags;
m_cachedAuBytes = (0 != _init.cachedAuBytes) ? _init.cachedAuBytes : (4u << 20);
m_auQueue.configure(m_cachedAuBytes, 0 != (m_initFlags & BGFX_VIDEO_DECODER_INIT_RETAIN) );
if (VideoCodec::H264 != _init.codec)
{
BX_TRACE("VideoDecoderD3D12: codec %d not yet supported on D3D12.", _init.codec);
return false;
}
if (NULL == _init.parameterSets
|| 0 == _init.parameterSetsSize)
{
BX_TRACE("VideoDecoderD3D12: empty parameter sets.");
return false;
}
h264::PPS ppsActive = {};
if (!bgfx::parseParameterSets(m_spsArray, BX_COUNTOF(m_spsArray), m_ppsArray, BX_COUNTOF(m_ppsArray), m_spsActive, ppsActive, _init.parameterSets, _init.parameterSetsSize) )
{
BX_TRACE("VideoDecoderD3D12: failed to parse SPS/PPS from parameter sets.");
return false;
}
if (0 != m_spsActive.bit_depth_luma_minus8
|| 0 != m_spsActive.bit_depth_chroma_minus8
|| 1 != m_spsActive.chroma_format_idc)
{
BX_TRACE("VideoDecoderD3D12: only 8-bit 4:2:0 (NV12) is supported.");
return false;
}
HRESULT hr = s_videoD3D12.device->QueryInterface(IID_ID3D12VideoDevice, (void**)&m_videoDevice);
if (FAILED(hr)
|| NULL == m_videoDevice)
{
BX_TRACE("VideoDecoderD3D12: ID3D12VideoDevice not available.");
return false;
}
m_dstWidth = _width;
m_dstHeight = _height;
m_codedWidth = (m_spsActive.pic_width_in_mbs_minus1 + 1) * 16;
m_codedHeight = (m_spsActive.pic_height_in_map_units_minus1 + 1) * 16 * (m_spsActive.frame_mbs_only_flag ? 1 : 2);
m_numDpbSlots = bx::clamp<uint32_t>(uint32_t(m_spsActive.num_ref_frames) + 1, 2, kNumDpbSlots);
const DXGI_FORMAT decodeFormat = DXGI_FORMAT_NV12;
D3D12_VIDEO_DECODER_DESC decoderDesc
{
.NodeMask = 0,
.Configuration =
{
.DecodeProfile = D3D12_VIDEO_DECODE_PROFILE_H264,
.BitstreamEncryption = {},
.InterlaceType = D3D12_VIDEO_FRAME_CODED_INTERLACE_TYPE_NONE,
},
};
D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT decodeSupport
{
.NodeIndex = 0,
.Configuration = decoderDesc.Configuration,
.Width = m_codedWidth,
.Height = m_codedHeight,
.DecodeFormat = decodeFormat,
.FrameRate = { 0, 1 },
.BitRate = 0,
.SupportFlags = D3D12_VIDEO_DECODE_SUPPORT_FLAG_NONE,
.ConfigurationFlags = D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_NONE,
.DecodeTier = D3D12_VIDEO_DECODE_TIER_NOT_SUPPORTED,
};
hr = m_videoDevice->CheckFeatureSupport(D3D12_FEATURE_VIDEO_DECODE_SUPPORT, &decodeSupport, sizeof(decodeSupport) );
if (FAILED(hr)
|| 0 == (decodeSupport.SupportFlags & D3D12_VIDEO_DECODE_SUPPORT_FLAG_SUPPORTED)
|| decodeSupport.DecodeTier < D3D12_VIDEO_DECODE_TIER_1)
{
BX_TRACE("VideoDecoderD3D12: H.264 NV12 not supported by hardware (tier=%d, hr=0x%08x).", decodeSupport.DecodeTier, hr);
DX_RELEASE_I(m_videoDevice);
return false;
}
m_referenceOnly = 0 != (decodeSupport.ConfigurationFlags & D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_REFERENCE_ONLY_ALLOCATIONS_REQUIRED);
const uint16_t vendorId = s_videoD3D12.vendorId;
if (BGFX_PCI_ID_INTEL == vendorId)
{
if (!m_referenceOnly)
{
BX_TRACE("VideoDecoderD3D12: Intel detected (vendorId=0x%04x): forcing reference-only DPB.", vendorId);
}
m_referenceOnly = true;
}
uint32_t heapWidth = m_codedWidth;
uint32_t heapHeight = m_codedHeight;
if (0 != (decodeSupport.ConfigurationFlags & D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_HEIGHT_ALIGNMENT_MULTIPLE_32_REQUIRED) )
{
heapWidth = bx::strideAlign(heapWidth, 32);
heapHeight = bx::strideAlign(heapHeight, 32);
}
D3D12_VIDEO_DECODER_HEAP_DESC heapDesc
{
.NodeMask = 0,
.Configuration = decoderDesc.Configuration,
.DecodeWidth = heapWidth,
.DecodeHeight = heapHeight,
.Format = decodeFormat,
.FrameRate = { 0, 1 },
.BitRate = 0,
.MaxDecodePictureBufferCount = m_numDpbSlots,
};
hr = m_videoDevice->CreateVideoDecoderHeap(&heapDesc, IID_ID3D12VideoDecoderHeap, (void**)&m_decoderHeap);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: CreateVideoDecoderHeap failed (0x%08x).", hr);
destroy();
return false;
}
hr = m_videoDevice->CreateVideoDecoder(&decoderDesc, IID_ID3D12VideoDecoder, (void**)&m_decoder);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: CreateVideoDecoder failed (0x%08x).", hr);
destroy();
return false;
}
D3D12_COMMAND_QUEUE_DESC queueDesc
{
.Type = D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE,
.Priority = 0,
.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE,
.NodeMask = 0,
};
hr = s_videoD3D12.device->CreateCommandQueue(&queueDesc, IID_ID3D12CommandQueue, (void**)&m_videoQueue);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: video CreateCommandQueue failed (0x%08x).", hr);
destroy();
return false;
}
for (uint32_t ii = 0; ii < kBitstreamRingFrames; ++ii)
{
hr = s_videoD3D12.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE, IID_ID3D12CommandAllocator, (void**)&m_videoCmdAllocator[ii]);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: video CreateCommandAllocator failed (0x%08x).", hr);
destroy();
return false;
}
}
hr = s_videoD3D12.device->CreateCommandList(0
, D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE
, m_videoCmdAllocator[0]
, NULL
, IID_ID3D12VideoDecodeCommandList
, (void**)&m_videoCmd
);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: CreateCommandList(VIDEO_DECODE) failed (0x%08x).", hr);
destroy();
return false;
}
m_videoCmd->Close();
hr = s_videoD3D12.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_ID3D12Fence, (void**)&m_videoFence);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: CreateFence failed (0x%08x).", hr);
destroy();
return false;
}
for (uint32_t ii = 0; ii < kCopyRingSize; ++ii)
{
hr = s_videoD3D12.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_ID3D12CommandAllocator, (void**)&m_copyCmdAllocator[ii]);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: copy CreateCommandAllocator failed (0x%08x).", hr);
destroy();
return false;
}
}
hr = s_videoD3D12.device->CreateCommandList(0
, D3D12_COMMAND_LIST_TYPE_DIRECT
, m_copyCmdAllocator[0]
, NULL
, IID_ID3D12GraphicsCommandList
, (void**)&m_copyCmdList
);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: CreateCommandList(DIRECT) failed (0x%08x).", hr);
destroy();
return false;
}
m_copyCmdList->Close();
hr = s_videoD3D12.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_ID3D12Fence, (void**)&m_copyFence);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: copy CreateFence failed (0x%08x).", hr);
destroy();
return false;
}
for (uint32_t ii = 0; ii < kCopyRingSize; ++ii)
{
m_copyFenceRing[ii] = 0;
}
m_copyFenceValue = 0;
m_videoControl.reset();
m_copyControl.reset();
bx::memSet(m_slotLastCopyFence, 0, sizeof(m_slotLastCopyFence) );
const uint32_t bitstreamBudget = bx::max<uint32_t>(2u*1024u*1024u, m_codedWidth * m_codedHeight);
m_bitstreamCapacity = bx::alignUp<uint64_t>(uint64_t(bitstreamBudget) * kBitstreamRingFrames, D3D12_VIDEO_DECODE_MIN_BITSTREAM_OFFSET_ALIGNMENT);
{
D3D12_RESOURCE_DESC desc
{
.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
.Alignment = 0,
.Width = m_bitstreamCapacity,
.Height = 1,
.DepthOrArraySize = 1,
.MipLevels = 1,
.Format = DXGI_FORMAT_UNKNOWN,
.SampleDesc = { .Count = 1, .Quality = 0 },
.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
.Flags = D3D12_RESOURCE_FLAG_NONE,
};
D3D12_HEAP_PROPERTIES heap
{
.Type = D3D12_HEAP_TYPE_UPLOAD,
.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
.CreationNodeMask = 0,
.VisibleNodeMask = 0,
};
hr = s_videoD3D12.device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_ID3D12Resource, (void**)&m_bitstreamBuffer);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: bitstream buffer create failed (0x%08x).", hr);
destroy();
return false;
}
D3D12_RANGE noRead = { 0, 0 };
hr = m_bitstreamBuffer->Map(0, &noRead, (void**)&m_bitstreamMapped);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: bitstream Map failed (0x%08x).", hr);
destroy();
return false;
}
}
{
D3D12_RESOURCE_DESC desc
{
.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
.Alignment = 0,
.Width = 256,
.Height = 1,
.DepthOrArraySize = 1,
.MipLevels = 1,
.Format = DXGI_FORMAT_UNKNOWN,
.SampleDesc = { .Count = 1, .Quality = 0 },
.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
.Flags = D3D12_RESOURCE_FLAG_NONE,
};
D3D12_HEAP_PROPERTIES heap
{
.Type = D3D12_HEAP_TYPE_UPLOAD,
.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
.CreationNodeMask = 0,
.VisibleNodeMask = 0,
};
hr = s_videoD3D12.device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_ID3D12Resource, (void**)&m_dummyCbv);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: dummy CBV create failed (0x%08x).", hr);
destroy();
return false;
}
}
{
D3D12_RESOURCE_DESC desc
{
.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
.Alignment = 0,
.Width = m_codedWidth,
.Height = m_codedHeight,
.DepthOrArraySize = uint16_t(m_numDpbSlots),
.MipLevels = 1,
.Format = decodeFormat,
.SampleDesc = { .Count = 1, .Quality = 0 },
.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
.Flags = m_referenceOnly
? (D3D12_RESOURCE_FLAG_VIDEO_DECODE_REFERENCE_ONLY | D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE)
: D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS
,
};
D3D12_HEAP_PROPERTIES heap
{
.Type = D3D12_HEAP_TYPE_DEFAULT,
.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
.CreationNodeMask = 0,
.VisibleNodeMask = 0,
};
hr = s_videoD3D12.device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, NULL, IID_ID3D12Resource, (void**)&m_dpb);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: DPB texture create failed (0x%08x).", hr);
destroy();
return false;
}
if (m_referenceOnly)
{
// AMD path: separate output texture (sampleable NV12).
desc.DepthOrArraySize = 1;
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
hr = s_videoD3D12.device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, NULL, IID_ID3D12Resource, (void**)&m_dpbOutput);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: DPB output texture create failed (0x%08x).", hr);
destroy();
return false;
}
}
}
for (uint32_t ii = 0; ii < m_numDpbSlots; ++ii)
{
m_dpbState[ii].state = D3D12_RESOURCE_STATE_COMMON;
}
BX_TRACE("VideoDecoderD3D12: H.264 decoder ready, coded %dx%d, dst %dx%d, %s DPB."
, m_codedWidth, m_codedHeight
, m_dstWidth, m_dstHeight
, m_referenceOnly ? "reference-only" : "shared"
);
return true;
}
void destroy()
{
if (NULL != m_videoFence
&& NULL != m_videoQueue)
{
const uint64_t fence = ++m_videoFenceValue;
m_videoQueue->Signal(m_videoFence, fence);
if (m_videoFence->GetCompletedValue() < fence)
{
HANDLE evt = CreateEventA(NULL, FALSE, FALSE, NULL);
m_videoFence->SetEventOnCompletion(fence, evt);
WaitForSingleObject(evt, INFINITE);
CloseHandle(evt);
}
}
if (NULL != m_copyFence
&& NULL != s_videoD3D12.commandQueue)
{
const uint64_t fence = ++m_copyFenceValue;
s_videoD3D12.commandQueue->Signal(m_copyFence, fence);
if (m_copyFence->GetCompletedValue() < fence)
{
HANDLE evt = CreateEventA(NULL, FALSE, FALSE, NULL);
m_copyFence->SetEventOnCompletion(fence, evt);
WaitForSingleObject(evt, INFINITE);
CloseHandle(evt);
}
}
DX_RELEASE(m_videoCmd, 0);
DX_RELEASE(m_copyCmdList, 0);
for (uint32_t ii = 0; ii < kBitstreamRingFrames; ++ii)
{
if (NULL != m_videoCmdAllocator[ii])
{
m_videoCmdAllocator[ii]->Reset();
}
}
for (uint32_t ii = 0; ii < kCopyRingSize; ++ii)
{
if (NULL != m_copyCmdAllocator[ii])
{
m_copyCmdAllocator[ii]->Reset();
}
}
for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii)
{
if (NULL != m_reorderPool[ii].texture)
{
videoReleaseResource(m_renderer, m_reorderPool[ii].texture);
m_reorderPool[ii].texture = NULL;
}
m_reorderPool[ii].displayOrder = -1;
}
if (NULL != m_bitstreamBuffer
&& NULL != m_bitstreamMapped)
{
D3D12_RANGE noWrite = { 0, 0 };
m_bitstreamBuffer->Unmap(0, &noWrite);
m_bitstreamMapped = NULL;
}
DX_RELEASE(m_bitstreamBuffer, 0);
DX_RELEASE(m_dummyCbv, 0);
DX_RELEASE(m_dpbOutput, 0);
DX_RELEASE(m_dpb, 0);
for (uint32_t ii = 0; ii < kBitstreamRingFrames; ++ii)
{
DX_RELEASE(m_videoCmdAllocator[ii], 0);
}
for (uint32_t ii = 0; ii < kCopyRingSize; ++ii)
{
DX_RELEASE(m_copyCmdAllocator[ii], 0);
}
DX_RELEASE(m_copyFence, 0);
DX_RELEASE(m_videoFence, 0);
DX_RELEASE(m_videoQueue, 0);
DX_RELEASE(m_decoder, 0);
DX_RELEASE(m_decoderHeap, 0);
DX_RELEASE_I(m_videoDevice);
}
bool decode(const VideoDecoderFrame& _frame, TextureD3D12& _dst)
{
if (NULL == m_decoder)
{
return false;
}
if (0 != (_frame.flags & BGFX_VIDEO_DECODE_FRAME_SET) )
{
resetForSet();
}
if (NULL != _frame.bitstream && 0 != _frame.numAus)
{
const uint8_t* bs = _frame.bitstream;
for (uint32_t ii = 0; ii < _frame.numAus; ++ii)
{
const uint32_t auSize = _frame.aus[ii].size;
const bool isIdr = bgfx::peekAuIsIdr(bs, auSize);
m_auQueue.enqueue(bs, auSize, _frame.aus[ii].ptsUs, isIdr);
bs += auSize;
}
}
while (!m_auQueue.empty()
&& bgfx::hasReorderSpace(m_reorderPool, kVideoMaxReorderInFlight) )
{
const bgfx::PendingAU& au = m_auQueue.front();
decodeOneAU(au.data.data(), uint32_t(au.data.size() ), au.ptsUs);
m_auQueue.pop();
}
m_auQueue.compact();
if (0 == _frame.numAus
&& 0 == (_frame.flags & BGFX_VIDEO_DECODE_FRAME_NO_BLIT) )
{
int64_t adjustedClock = _frame.presentationTimeUs;
if (0 != (_frame.flags & BGFX_VIDEO_DECODE_FRAME_LOOP)
&& m_auQueue.isRetaining()
&& m_auQueue.minPts() < m_auQueue.maxPts() )
{
adjustedClock = bgfx::applyLoopWrap(adjustedClock, m_auQueue.minPts(), m_auQueue.maxPts() );
const bool poolCovers = bgfx::reorderPoolCovers(m_reorderPool, kVideoMaxReorderInFlight, adjustedClock, 500000);
const bool forwardCanCatchUp = true
&& !m_auQueue.empty()
&& m_auQueue.front().ptsUs <= adjustedClock + 500000
;
if (!poolCovers
&& !forwardCanCatchUp)
{
bgfx::faultInFromCache(
m_auQueue
, adjustedClock
, [this]() { resetForSet(); }
, [this](const uint8_t* _bs, uint32_t _bsSize, int64_t _pts)
{
decodeOneAU(_bs, _bsSize, _pts);
}
, [this]()
{
return bgfx::hasReorderSpace(m_reorderPool, kVideoMaxReorderInFlight);
}
);
}
}
pickDisplaySlotForTime(adjustedClock, _dst);
}
return true;
}
void resetForSet()
{
if (m_auQueue.isRetaining() )
{
m_auQueue.rewindTo(m_auQueue.size() );
}
else
{
m_auQueue.clear();
}
for (uint32_t ii = 0; ii < kVideoMaxReorderInFlight; ++ii)
{
m_reorderPool[ii].displayOrder = -1;
m_reorderPool[ii].ptsUs = 0;
}
m_displayedSlot = -1;
m_prevDisplayedSlot = -1;
m_displayOrderNext = 0;
m_numActiveRefs = 0;
m_nextSlot = 0;
m_currentSlot = 0;
bx::memSet(m_referenceUsage, 0, sizeof(m_referenceUsage) );
for (uint32_t ii = 0; ii < kNumDpbSlots; ++ii)
{
m_dpbState[ii].poc = 0;
m_dpbState[ii].frameNum = 0;
}
m_prevPocLsb = 0;
m_prevPocMsb = 0;
}
bool decodeOneAU(const uint8_t* _bitstream, uint32_t _bitstreamSize, int64_t _ptsUs)
{
h264::NALHeader nal = {};
h264::SliceHeader sh = {};
bool isIdr = false;
if (!bgfx::parseSliceFromAccessUnit(m_spsArray, m_ppsArray, _bitstream, _bitstreamSize, nal, sh, isIdr) )
{
BX_TRACE("VideoDecoderD3D12: no slice NAL found in access unit.");
return false;
}
const h264::PPS& pps = m_ppsArray[sh.pic_parameter_set_id];
const h264::SPS& sps = m_spsArray[pps.seq_parameter_set_id];
if (isIdr)
{
m_numActiveRefs = 0;
m_nextSlot = 0;
}
const int32_t poc = bgfx::computePoc(m_prevPocMsb, m_prevPocLsb, sps, sh, nal, isIdr);
m_currentSlot = bgfx::pickFreeDpbSlot(
m_nextSlot
, m_numDpbSlots
, m_referenceUsage
, m_numActiveRefs
, [](uint32_t){ return false; }
);
BX_ASSERT(m_currentSlot < m_numDpbSlots
, "DPB exhausted: m_numDpbSlots=%u m_numActiveRefs=%u. Cap maxRefs to m_numDpbSlots-1."
, m_numDpbSlots
, m_numActiveRefs
);
m_nextSlot = (m_currentSlot + 1) % m_numDpbSlots;
const uint32_t reorderSlot = bgfx::pickReorderSlotOrEvictOldest(m_reorderPool, kVideoMaxReorderInFlight);
const int32_t displayOrder = m_displayOrderNext++;
m_reorderPool[reorderSlot].displayOrder = displayOrder;
m_reorderPool[reorderSlot].ptsUs = _ptsUs;
m_dpbState[m_currentSlot].poc = poc;
m_dpbState[m_currentSlot].frameNum = sh.frame_num;
while (0 == m_videoControl.reserve(1) )
{
const uint32_t readIdx = m_videoControl.m_read;
const uint64_t waitFence = m_videoCmdAllocatorFence[readIdx];
if (waitFence > m_videoFence->GetCompletedValue() )
{
HANDLE evt = CreateEventA(NULL, FALSE, FALSE, NULL);
m_videoFence->SetEventOnCompletion(waitFence, evt);
WaitForSingleObject(evt, INFINITE);
CloseHandle(evt);
}
m_videoControl.consume(1);
}
const uint32_t allocIdx = m_videoControl.m_current;
const uint32_t sliceOffsetInAu = bgfx::findFirstSliceNalOffset(_bitstream, _bitstreamSize);
const uint8_t* sliceBitstream = _bitstream + sliceOffsetInAu;
const uint32_t sliceBitstreamSize = _bitstreamSize - sliceOffsetInAu;
const uint32_t alignedSize = bx::alignUp<uint32_t>(sliceBitstreamSize, D3D12_VIDEO_DECODE_MIN_BITSTREAM_OFFSET_ALIGNMENT);
if (m_bitstreamCursor + alignedSize > m_bitstreamCapacity)
{
m_bitstreamCursor = 0;
}
const uint64_t bitstreamOffset = m_bitstreamCursor;
bx::memCopy(m_bitstreamMapped + bitstreamOffset, sliceBitstream, sliceBitstreamSize);
m_bitstreamCursor += alignedSize;
HRESULT hr = m_videoCmdAllocator[allocIdx]->Reset();
BX_WARN(SUCCEEDED(hr), "VideoDecoderD3D12: video allocator Reset failed (0x%08x).", hr);
BX_UNUSED(hr);
hr = m_videoCmd->Reset(m_videoCmdAllocator[allocIdx]);
BX_WARN(SUCCEEDED(hr), "VideoDecoderD3D12: video cmdlist Reset failed (0x%08x).", hr);
if (m_dpbState[m_currentSlot].state != D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE)
{
D3D12_RESOURCE_BARRIER barriers[2]
{
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = m_dpb,
.Subresource = m_currentSlot,
.StateBefore = m_dpbState[m_currentSlot].state,
.StateAfter = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE,
},
},
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = m_dpb,
.Subresource = m_numDpbSlots + m_currentSlot,
.StateBefore = m_dpbState[m_currentSlot].state,
.StateAfter = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE,
},
},
};
m_videoCmd->ResourceBarrier(BX_COUNTOF(barriers), barriers);
m_dpbState[m_currentSlot].state = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE;
}
if (m_referenceOnly)
{
D3D12_RESOURCE_BARRIER barrier
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = m_dpbOutput,
.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
.StateBefore = D3D12_RESOURCE_STATE_COMMON,
.StateAfter = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE,
},
};
m_videoCmd->ResourceBarrier(1, &barrier);
}
DXVA_PicParams_H264 picParams = {};
DXVA_Qmatrix_H264 qmatrix = {};
DXVA_Slice_H264_Short sliceCtl = {};
picParams.wFrameWidthInMbsMinus1 = uint16_t(sps.pic_width_in_mbs_minus1);
picParams.wFrameHeightInMbsMinus1 = uint16_t(sps.pic_height_in_map_units_minus1);
picParams.IntraPicFlag = isIdr ? 1 : 0;
picParams.MbaffFrameFlag = 0;
picParams.field_pic_flag = 0;
picParams.chroma_format_idc = 1;
picParams.bit_depth_chroma_minus8 = uint8_t(sps.bit_depth_chroma_minus8);
picParams.bit_depth_luma_minus8 = uint8_t(sps.bit_depth_luma_minus8);
picParams.residual_colour_transform_flag = uint8_t(sps.separate_colour_plane_flag);
picParams.CurrPic.AssociatedFlag = 0;
picParams.CurrPic.Index7Bits = uint8_t(m_currentSlot);
picParams.CurrFieldOrderCnt[0] = poc;
picParams.CurrFieldOrderCnt[1] = poc;
for (uint32_t ii = 0; ii < 16; ++ii)
{
picParams.RefFrameList[ii].bPicEntry = 0xFF;
picParams.FieldOrderCntList[ii][0] = 0;
picParams.FieldOrderCntList[ii][1] = 0;
picParams.FrameNumList[ii] = 0;
}
picParams.UsedForReferenceFlags = 0;
for (uint32_t ii = 0; ii < m_numActiveRefs && ii < 16; ++ii)
{
const uint32_t refSlot = m_referenceUsage[ii];
picParams.RefFrameList[ii].AssociatedFlag = 0;
picParams.RefFrameList[ii].Index7Bits = uint8_t(refSlot);
picParams.FieldOrderCntList[ii][0] = m_dpbState[refSlot].poc;
picParams.FieldOrderCntList[ii][1] = m_dpbState[refSlot].poc;
picParams.FrameNumList[ii] = uint16_t(m_dpbState[refSlot].frameNum);
picParams.UsedForReferenceFlags |= 1u << (ii * 2 + 0);
picParams.UsedForReferenceFlags |= 1u << (ii * 2 + 1);
}
picParams.weighted_pred_flag = uint8_t(pps.weighted_pred_flag);
picParams.weighted_bipred_idc = uint8_t(pps.weighted_bipred_idc);
picParams.sp_for_switch_flag = 0;
picParams.transform_8x8_mode_flag = uint8_t(pps.transform_8x8_mode_flag);
picParams.constrained_intra_pred_flag = uint8_t(pps.constrained_intra_pred_flag);
picParams.num_ref_frames = uint8_t(sps.num_ref_frames);
picParams.MbsConsecutiveFlag = 1;
picParams.frame_mbs_only_flag = uint8_t(sps.frame_mbs_only_flag);
picParams.MinLumaBipredSize8x8Flag = sps.level_idc >= 31 ? 1 : 0;
picParams.RefPicFlag = (0 != nal.idc) ? 1 : 0;
picParams.frame_num = uint16_t(sh.frame_num);
picParams.pic_init_qp_minus26 = int8_t(pps.pic_init_qp_minus26);
picParams.pic_init_qs_minus26 = int8_t(pps.pic_init_qs_minus26);
picParams.chroma_qp_index_offset = int8_t(pps.chroma_qp_index_offset);
picParams.second_chroma_qp_index_offset = int8_t(pps.second_chroma_qp_index_offset);
picParams.log2_max_frame_num_minus4 = uint8_t(sps.log2_max_frame_num_minus4);
picParams.pic_order_cnt_type = uint8_t(sps.pic_order_cnt_type);
picParams.log2_max_pic_order_cnt_lsb_minus4 = uint8_t(sps.log2_max_pic_order_cnt_lsb_minus4);
picParams.delta_pic_order_always_zero_flag = uint8_t(sps.delta_pic_order_always_zero_flag);
picParams.direct_8x8_inference_flag = uint8_t(sps.direct_8x8_inference_flag);
picParams.entropy_coding_mode_flag = uint8_t(pps.entropy_coding_mode_flag);
picParams.pic_order_present_flag = uint8_t(pps.pic_order_present_flag);
picParams.num_slice_groups_minus1 = uint8_t(pps.num_slice_groups_minus1);
picParams.slice_group_map_type = uint8_t(pps.slice_group_map_type);
picParams.deblocking_filter_control_present_flag = uint8_t(pps.deblocking_filter_control_present_flag);
picParams.redundant_pic_cnt_present_flag = uint8_t(pps.redundant_pic_cnt_present_flag);
picParams.slice_group_change_rate_minus1 = uint16_t(pps.slice_group_change_rate_minus1);
picParams.Reserved16Bits = 3;
picParams.StatusReportFeedbackNumber = ++m_statusReportFeedback;
picParams.ContinuationFlag = 1;
picParams.num_ref_idx_l0_active_minus1 = uint8_t(pps.num_ref_idx_l0_active_minus1);
picParams.num_ref_idx_l1_active_minus1 = uint8_t(pps.num_ref_idx_l1_active_minus1);
if (sps.seq_scaling_matrix_present_flag || pps.pic_scaling_matrix_present_flag)
{
uint8_t lists4x4[6][16];
uint8_t lists8x8[2][64];
buildEffectiveScalingLists(sps, pps, lists4x4, lists8x8);
for (int i = 0; i < 6; ++i)
{
for (int j = 0; j < 16; ++j)
{
qmatrix.bScalingLists4x4[i][j] = lists4x4[i][j];
}
}
for (int i = 0; i < 64; ++i)
{
qmatrix.bScalingLists8x8[0][i] = lists8x8[0][i];
qmatrix.bScalingLists8x8[1][i] = lists8x8[1][i];
}
}
else
{
bx::memSet(&qmatrix, 16, sizeof(qmatrix) );
}
sliceCtl.BSNALunitDataLocation = 0;
sliceCtl.SliceBytesInBuffer = sliceBitstreamSize;
sliceCtl.wBadSliceChopping = 0;
ID3D12Resource* refFrames[kNumDpbSlots] = {};
UINT refSubresources[kNumDpbSlots] = {};
for (uint32_t ii = 0; ii < m_numDpbSlots; ++ii)
{
refFrames[ii] = m_dpb;
refSubresources[ii] = UINT(ii);
}
D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS input
{
.NumFrameArguments = 3,
.FrameArguments =
{
{ .Type = D3D12_VIDEO_DECODE_ARGUMENT_TYPE_PICTURE_PARAMETERS, .Size = sizeof(picParams), .pData = &picParams },
{ .Type = D3D12_VIDEO_DECODE_ARGUMENT_TYPE_INVERSE_QUANTIZATION_MATRIX,.Size = sizeof(qmatrix), .pData = &qmatrix },
{ .Type = D3D12_VIDEO_DECODE_ARGUMENT_TYPE_SLICE_CONTROL, .Size = sizeof(sliceCtl), .pData = &sliceCtl },
},
.ReferenceFrames =
{
.NumTexture2Ds = kNumDpbSlots,
.ppTexture2Ds = refFrames,
.pSubresources = refSubresources,
.ppHeaps = NULL,
},
.CompressedBitstream =
{
.pBuffer = m_bitstreamBuffer,
.Offset = bitstreamOffset,
.Size = sliceBitstreamSize,
},
.pHeap = m_decoderHeap,
};
D3D12_VIDEO_DECODE_OUTPUT_STREAM_ARGUMENTS output = m_referenceOnly
? D3D12_VIDEO_DECODE_OUTPUT_STREAM_ARGUMENTS
{
.pOutputTexture2D = m_dpbOutput,
.OutputSubresource = 0,
.ConversionArguments =
{
.Enable = TRUE,
.pReferenceTexture2D = m_dpb,
.ReferenceSubresource = m_currentSlot,
.OutputColorSpace = DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709,
.DecodeColorSpace = DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709,
},
}
: D3D12_VIDEO_DECODE_OUTPUT_STREAM_ARGUMENTS
{
.pOutputTexture2D = m_dpb,
.OutputSubresource = m_currentSlot,
.ConversionArguments = {},
}
;
m_videoCmd->DecodeFrame(m_decoder, &output, &input);
if (m_referenceOnly)
{
D3D12_RESOURCE_BARRIER barrier
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = m_dpbOutput,
.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
.StateBefore = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE,
.StateAfter = D3D12_RESOURCE_STATE_COMMON,
},
};
m_videoCmd->ResourceBarrier(1, &barrier);
D3D12_RESOURCE_BARRIER dpbBarriers[2]
{
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = m_dpb,
.Subresource = m_currentSlot,
.StateBefore = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE,
.StateAfter = D3D12_RESOURCE_STATE_COMMON,
},
},
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = m_dpb,
.Subresource = m_numDpbSlots + m_currentSlot,
.StateBefore = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE,
.StateAfter = D3D12_RESOURCE_STATE_COMMON,
},
},
};
m_videoCmd->ResourceBarrier(BX_COUNTOF(dpbBarriers), dpbBarriers);
m_dpbState[m_currentSlot].state = D3D12_RESOURCE_STATE_COMMON;
}
else
{
D3D12_RESOURCE_BARRIER barriers[2]
{
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = m_dpb,
.Subresource = m_currentSlot,
.StateBefore = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE,
.StateAfter = D3D12_RESOURCE_STATE_COMMON,
},
},
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = m_dpb,
.Subresource = m_numDpbSlots + m_currentSlot,
.StateBefore = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE,
.StateAfter = D3D12_RESOURCE_STATE_COMMON,
},
},
};
m_videoCmd->ResourceBarrier(BX_COUNTOF(barriers), barriers);
m_dpbState[m_currentSlot].state = D3D12_RESOURCE_STATE_COMMON;
}
hr = m_videoCmd->Close();
BX_WARN(SUCCEEDED(hr), "VideoDecoderD3D12: video Close failed (0x%08x).", hr);
ID3D12CommandList* lists[] = { m_videoCmd };
if (m_slotLastCopyFence[m_currentSlot] > 0)
{
m_videoQueue->Wait(m_copyFence, m_slotLastCopyFence[m_currentSlot]);
}
if (m_referenceOnly && m_lastDpbOutputCopyFence > 0)
{
m_videoQueue->Wait(m_copyFence, m_lastDpbOutputCopyFence);
}
m_videoQueue->ExecuteCommandLists(BX_COUNTOF(lists), lists);
const uint64_t signalValue = ++m_videoFenceValue;
m_videoQueue->Signal(m_videoFence, signalValue);
m_videoCmdAllocatorFence[allocIdx] = signalValue;
m_videoControl.commit(1);
if (0 != nal.idc)
{
if (0 != sh.drpm.adaptive_ref_pic_marking_mode_flag
&& m_numActiveRefs > 0)
{
uint16_t refFrameNums[16];
const uint32_t n = bx::min<uint32_t>(m_numActiveRefs, 16);
for (uint32_t ii = 0; ii < n; ++ii)
{
refFrameNums[ii] = uint16_t(m_dpbState[m_referenceUsage[ii]].frameNum);
}
const int32_t maxFrameNum = 1 << (sps.log2_max_frame_num_minus4 + 4);
m_numActiveRefs = applyMmcoUnmarkShortTerm(
m_referenceUsage
, refFrameNums
, n
, sh.frame_num
, maxFrameNum
, sh
);
}
const uint32_t maxRefs = bx::min<uint32_t>(uint32_t(sps.num_ref_frames), m_numDpbSlots - 1);
m_numActiveRefs = bgfx::appendShortTermRef(
m_referenceUsage
, m_numActiveRefs
, m_currentSlot
, maxRefs
);
}
while (0 == m_copyControl.reserve(1) )
{
const uint32_t readIdx = m_copyControl.m_read;
const uint64_t waitFence = m_copyFenceRing[readIdx];
if (m_copyFence->GetCompletedValue() < waitFence)
{
HANDLE evt = CreateEventA(NULL, FALSE, FALSE, NULL);
m_copyFence->SetEventOnCompletion(waitFence, evt);
WaitForSingleObject(evt, INFINITE);
CloseHandle(evt);
}
m_copyControl.consume(1);
}
const uint32_t copyRingIdx = m_copyControl.m_current;
hr = m_copyCmdAllocator[copyRingIdx]->Reset();
BX_WARN(SUCCEEDED(hr), "VideoDecoderD3D12: copy allocator Reset failed (0x%08x).", hr);
hr = m_copyCmdList->Reset(m_copyCmdAllocator[copyRingIdx], NULL);
BX_WARN(SUCCEEDED(hr), "VideoDecoderD3D12: copy cmdlist Reset failed (0x%08x).", hr);
ID3D12GraphicsCommandList* commandList = m_copyCmdList;
ID3D12Resource* nv12Source = m_referenceOnly
? m_dpbOutput
: m_dpb;
const uint32_t nv12LumaSubres = m_referenceOnly ? 0u : m_currentSlot;
const uint32_t nv12ChromaSubres = m_referenceOnly ? 1u : (m_numDpbSlots + m_currentSlot);
ID3D12Resource* reorderTex = acquireReorderTexture(uint32_t(reorderSlot) );
if (NULL == reorderTex)
{
m_copyCmdList->Close();
return false;
}
{
D3D12_RESOURCE_BARRIER barriers[2]
{
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = reorderTex,
.Subresource = 0,
.StateBefore = D3D12_RESOURCE_STATE_COMMON,
.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST,
},
},
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = reorderTex,
.Subresource = 1,
.StateBefore = D3D12_RESOURCE_STATE_COMMON,
.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST,
},
},
};
commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers);
}
{
D3D12_RESOURCE_BARRIER barriers[2]
{
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = nv12Source,
.Subresource = nv12LumaSubres,
.StateBefore = D3D12_RESOURCE_STATE_COMMON,
.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE,
},
},
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = nv12Source,
.Subresource = nv12ChromaSubres,
.StateBefore = D3D12_RESOURCE_STATE_COMMON,
.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE,
},
},
};
commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers);
}
{
const uint32_t reorderW = (m_dstWidth + 1) & ~1u;
const uint32_t reorderH = (m_dstHeight + 1) & ~1u;
const D3D12_BOX lumaBox = { 0, 0, 0, reorderW, reorderH, 1 };
const D3D12_BOX chromaBox = { 0, 0, 0, reorderW / 2, reorderH / 2, 1 };
{
D3D12_TEXTURE_COPY_LOCATION dst
{
.pResource = reorderTex,
.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
.SubresourceIndex = 0,
};
D3D12_TEXTURE_COPY_LOCATION src
{
.pResource = nv12Source,
.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
.SubresourceIndex = nv12LumaSubres,
};
commandList->CopyTextureRegion(&dst, 0, 0, 0, &src, &lumaBox);
}
{
D3D12_TEXTURE_COPY_LOCATION dst
{
.pResource = reorderTex,
.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
.SubresourceIndex = 1,
};
D3D12_TEXTURE_COPY_LOCATION src
{
.pResource = nv12Source,
.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
.SubresourceIndex = nv12ChromaSubres,
};
commandList->CopyTextureRegion(&dst, 0, 0, 0, &src, &chromaBox);
}
}
{
D3D12_RESOURCE_BARRIER barriers[2]
{
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = nv12Source,
.Subresource = nv12LumaSubres,
.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE,
.StateAfter = D3D12_RESOURCE_STATE_COMMON,
},
},
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = nv12Source,
.Subresource = nv12ChromaSubres,
.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE,
.StateAfter = D3D12_RESOURCE_STATE_COMMON,
},
},
};
commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers);
}
{
D3D12_RESOURCE_BARRIER barriers[2]
{
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = reorderTex,
.Subresource = 0,
.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST,
.StateAfter = D3D12_RESOURCE_STATE_COMMON,
},
},
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = reorderTex,
.Subresource = 1,
.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST,
.StateAfter = D3D12_RESOURCE_STATE_COMMON,
},
},
};
commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers);
}
hr = m_copyCmdList->Close();
BX_WARN(SUCCEEDED(hr), "VideoDecoderD3D12: copy Close failed (0x%08x).", hr);
ID3D12CommandQueue* gfxQueue = s_videoD3D12.commandQueue;
gfxQueue->Wait(m_videoFence, signalValue);
ID3D12CommandList* copyLists[] = { m_copyCmdList };
gfxQueue->ExecuteCommandLists(BX_COUNTOF(copyLists), copyLists);
const uint64_t copySignal = ++m_copyFenceValue;
gfxQueue->Signal(m_copyFence, copySignal);
m_copyFenceRing[copyRingIdx] = copySignal;
m_slotLastCopyFence[m_currentSlot] = copySignal;
m_lastDpbOutputCopyFence = copySignal;
m_copyControl.commit(1);
return true;
}
void pickDisplaySlotForTime(int64_t _presentationTimeUs, TextureD3D12& _dst)
{
bgfx::pickDisplaySlotForTime(
_presentationTimeUs
, m_reorderPool
, kVideoMaxReorderInFlight
, m_displayedSlot
, m_prevDisplayedSlot
, m_auQueue.empty()
, [this, &_dst](uint32_t _slot)
{
ID3D12GraphicsCommandList* gfx = videoGetCommandList(m_renderer);
dispatchYuvToRgb(gfx, m_reorderPool[_slot].texture, _dst);
}
);
}
RendererContextD3D12* m_renderer;
ID3D12Resource* acquireReorderTexture(uint32_t _slot)
{
BX_ASSERT(_slot < kVideoMaxReorderInFlight, "VideoDecoderD3D12: reorder slot %u out of range.", _slot);
if (NULL != m_reorderPool[_slot].texture)
{
return m_reorderPool[_slot].texture;
}
ID3D12Device* device = s_videoD3D12.device;
D3D12_RESOURCE_DESC desc
{
.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
.Alignment = 0,
.Width = (m_dstWidth + 1) & ~1u,
.Height = (m_dstHeight + 1) & ~1u,
.DepthOrArraySize = 1,
.MipLevels = 1,
.Format = DXGI_FORMAT_NV12,
.SampleDesc = { .Count = 1, .Quality = 0 },
.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
.Flags = D3D12_RESOURCE_FLAG_NONE,
};
D3D12_HEAP_PROPERTIES heap
{
.Type = D3D12_HEAP_TYPE_DEFAULT,
.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
.CreationNodeMask = 0,
.VisibleNodeMask = 0,
};
HRESULT hr = device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, NULL, IID_ID3D12Resource, (void**)&m_reorderPool[_slot].texture);
if (FAILED(hr) )
{
BX_TRACE("VideoDecoderD3D12: reorder texture create failed (0x%08x).", hr);
return NULL;
}
return m_reorderPool[_slot].texture;
}
void dispatchYuvToRgb(ID3D12GraphicsCommandList* _commandList, ID3D12Resource* _nv12, TextureD3D12& _dst)
{
BX_ASSERT(NULL != g_videoDecode && isValid(g_videoDecode->m_program), "Video decode program not initialized.");
ID3D12PipelineState* pso = videoGetPipelineState(m_renderer, g_videoDecode->m_program);
if (NULL == pso)
{
return;
}
{
D3D12_RESOURCE_BARRIER barriers[2]
{
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = _nv12,
.Subresource = 0,
.StateBefore = D3D12_RESOURCE_STATE_COMMON,
.StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
},
},
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = _nv12,
.Subresource = 1,
.StateBefore = D3D12_RESOURCE_STATE_COMMON,
.StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
},
},
};
_commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers);
}
_dst.setState(_commandList, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
ScratchBufferD3D12& scratch = videoGetScratchBuffer(m_renderer);
_commandList->SetComputeRootSignature(s_videoD3D12.computeRootSignature);
ID3D12DescriptorHeap* heaps[] =
{
s_videoD3D12.samplerHeap,
scratch.getHeap(),
};
_commandList->SetDescriptorHeaps(BX_COUNTOF(heaps), heaps);
_commandList->SetPipelineState(pso);
uint32_t samplerFlags[BGFX_MAX_COMPUTE_BINDINGS] = {};
samplerFlags[1] = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP | BGFX_SAMPLER_W_CLAMP;
samplerFlags[2] = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP | BGFX_SAMPLER_W_CLAMP;
_commandList->SetComputeRootDescriptorTable(ComputeRp::Sampler, videoGetSamplerHandle(m_renderer, samplerFlags) );
D3D12_GPU_VIRTUAL_ADDRESS cbvAddress = m_dummyCbv->GetGPUVirtualAddress();
_commandList->SetComputeRootConstantBufferView(ComputeRp::CBV, cbvAddress);
D3D12_GPU_DESCRIPTOR_HANDLE srvHandle[BGFX_MAX_COMPUTE_BINDINGS] = {};
{
scratch.allocEmpty(srvHandle[0]);
D3D12_SHADER_RESOURCE_VIEW_DESC srvDescY
{
.Format = DXGI_FORMAT_R8_UNORM,
.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D,
.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
.Texture2D =
{
.MostDetailedMip = 0,
.MipLevels = 1,
.PlaneSlice = 0,
.ResourceMinLODClamp = 0.0f,
},
};
scratch.allocSrv(srvHandle[1], _nv12, srvDescY);
D3D12_SHADER_RESOURCE_VIEW_DESC srvDescCbCr
{
.Format = DXGI_FORMAT_R8G8_UNORM,
.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D,
.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
.Texture2D =
{
.MostDetailedMip = 0,
.MipLevels = 1,
.PlaneSlice = 1,
.ResourceMinLODClamp = 0.0f,
},
};
scratch.allocSrv(srvHandle[2], _nv12, srvDescCbCr);
for (uint32_t ii = 3; ii < BGFX_MAX_COMPUTE_BINDINGS; ++ii)
{
scratch.allocEmpty(srvHandle[ii]);
}
}
_commandList->SetComputeRootDescriptorTable(ComputeRp::SRV, srvHandle[0]);
D3D12_GPU_DESCRIPTOR_HANDLE uavHandle[BGFX_MAX_COMPUTE_BINDINGS] = {};
{
scratch.allocUav(uavHandle[0], _dst, 0);
for (uint32_t ii = 1; ii < BGFX_MAX_COMPUTE_BINDINGS; ++ii)
{
scratch.allocEmpty(uavHandle[ii]);
}
}
_commandList->SetComputeRootDescriptorTable(ComputeRp::UAV, uavHandle[0]);
_commandList->Dispatch(
bx::max<uint32_t>( (m_dstWidth + 7) / 8, 1)
, bx::max<uint32_t>( (m_dstHeight + 7) / 8, 1)
, 1
);
{
D3D12_RESOURCE_BARRIER barriers[2]
{
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = _nv12,
.Subresource = 0,
.StateBefore = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
.StateAfter = D3D12_RESOURCE_STATE_COMMON,
},
},
{
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition =
{
.pResource = _nv12,
.Subresource = 1,
.StateBefore = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
.StateAfter = D3D12_RESOURCE_STATE_COMMON,
},
},
};
_commandList->ResourceBarrier(BX_COUNTOF(barriers), barriers);
}
}
ID3D12VideoDevice* m_videoDevice;
ID3D12VideoDecoder* m_decoder;
ID3D12VideoDecoderHeap* m_decoderHeap;
ID3D12CommandQueue* m_videoQueue;
ID3D12CommandAllocator* m_videoCmdAllocator[kBitstreamRingFrames];
uint64_t m_videoCmdAllocatorFence[kBitstreamRingFrames];
ID3D12VideoDecodeCommandList* m_videoCmd;
ID3D12Fence* m_videoFence;
uint64_t m_videoFenceValue;
bx::RingBufferControl m_videoControl;
ID3D12CommandAllocator* m_copyCmdAllocator[kCopyRingSize];
ID3D12GraphicsCommandList* m_copyCmdList;
ID3D12Fence* m_copyFence;
uint64_t m_copyFenceValue;
uint64_t m_copyFenceRing[kCopyRingSize];
uint64_t m_slotLastCopyFence[kNumDpbSlots];
uint64_t m_lastDpbOutputCopyFence;
bx::RingBufferControl m_copyControl;
ID3D12Resource* m_bitstreamBuffer;
uint8_t* m_bitstreamMapped;
uint64_t m_bitstreamCapacity;
uint64_t m_bitstreamCursor;
ID3D12Resource* m_dummyCbv;
ID3D12Resource* m_dpb;
ID3D12Resource* m_dpbOutput;
bool m_referenceOnly;
DpbState m_dpbState[kNumDpbSlots];
uint8_t m_referenceUsage[kNumDpbSlots];
uint32_t m_codedWidth;
uint32_t m_codedHeight;
uint32_t m_dstWidth;
uint32_t m_dstHeight;
uint32_t m_numDpbSlots;
uint32_t m_nextSlot;
uint32_t m_currentSlot;
uint32_t m_numActiveRefs;
int32_t m_displayOrderNext;
int32_t m_prevPocLsb;
int32_t m_prevPocMsb;
ReorderedPicture m_reorderPool[kVideoMaxReorderInFlight];
int32_t m_displayedSlot;
int32_t m_prevDisplayedSlot;
uint32_t m_statusReportFeedback;
bgfx::AuQueue m_auQueue;
uint32_t m_initFlags;
uint32_t m_cachedAuBytes;
h264::SPS m_spsActive;
h264::SPS m_spsArray[kVideoMaxH264SpsCount];
h264::PPS m_ppsArray[kVideoMaxH264PpsCount];
};
VideoDecoderD3D12* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextD3D12* _renderer, uint16_t _width, uint16_t _height)
{
VideoDecoderD3D12* decoder = BX_NEW(g_allocator, VideoDecoderD3D12);
if (!decoder->create(_init, _renderer, _width, _height) )
{
decoder->destroy();
bx::deleteObject(g_allocator, decoder);
return NULL;
}
return decoder;
}
void videoDecoderDestroy(VideoDecoderD3D12* _decoder)
{
if (NULL != _decoder)
{
_decoder->destroy();
bx::deleteObject(g_allocator, _decoder);
}
}
bool videoDecoderDecode(VideoDecoderD3D12* _decoder, const VideoDecoderFrame& _frame, TextureD3D12& _dst)
{
return _decoder->decode(_frame, _dst);
}
void initVideoDecoder(const VideoBindingD3D12& _binding)
{
s_videoD3D12 = _binding;
ID3D12VideoDevice* videoDevice = NULL;
HRESULT hr = _binding.device->QueryInterface(IID_ID3D12VideoDevice, (void**)&videoDevice);
if (FAILED(hr) || NULL == videoDevice)
{
return;
}
D3D12_FEATURE_DATA_VIDEO_DECODE_PROFILE_COUNT profileCount = {};
hr = videoDevice->CheckFeatureSupport(
D3D12_FEATURE_VIDEO_DECODE_PROFILE_COUNT
, &profileCount
, sizeof(profileCount)
);
if (FAILED(hr) || 0 == profileCount.ProfileCount)
{
DX_RELEASE(videoDevice, 0);
return;
}
GUID* profiles = (GUID*)BX_STACK_ALLOC(profileCount.ProfileCount * sizeof(GUID) );
D3D12_FEATURE_DATA_VIDEO_DECODE_PROFILES profileList
{
.NodeIndex = 0,
.ProfileCount = profileCount.ProfileCount,
.pProfiles = profiles,
};
hr = videoDevice->CheckFeatureSupport(
D3D12_FEATURE_VIDEO_DECODE_PROFILES
, &profileList
, sizeof(profileList)
);
if (FAILED(hr) )
{
DX_RELEASE(videoDevice, 0);
return;
}
struct ProfileEntry
{
const GUID* guid;
VideoCodec::Enum codec;
uint32_t caps;
};
static const ProfileEntry s_profiles[] =
{
{ &D3D12_VIDEO_DECODE_PROFILE_H264, VideoCodec::H264, BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 },
{ &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 },
{ &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 },
{ &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_12 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 },
{ &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10_422, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_422 },
{ &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12_422, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_12 | BGFX_CAPS_VIDEO_CODEC_CHROMA_422 },
{ &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN_444, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_8 | BGFX_CAPS_VIDEO_CODEC_CHROMA_444 },
{ &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN10_444, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_444 },
{ &D3D12_VIDEO_DECODE_PROFILE_HEVC_MAIN12_444, VideoCodec::H265, BGFX_CAPS_VIDEO_CODEC_BIT_12 | BGFX_CAPS_VIDEO_CODEC_CHROMA_444 },
{ &D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE0, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_8
| BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 },
{ &D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE1, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_8
| BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_444 },
{ &D3D12_VIDEO_DECODE_PROFILE_AV1_PROFILE2, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_10 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420
| BGFX_CAPS_VIDEO_CODEC_CHROMA_422 },
{ &D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_12 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420
| BGFX_CAPS_VIDEO_CODEC_CHROMA_422 },
{ &D3D12_VIDEO_DECODE_PROFILE_AV1_12BIT_PROFILE2_420, VideoCodec::AV1, BGFX_CAPS_VIDEO_CODEC_BIT_12 | BGFX_CAPS_VIDEO_CODEC_CHROMA_420 },
};
bool anySupported = false;
for (UINT ii = 0; ii < profileList.ProfileCount; ++ii)
{
for (uint32_t jj = 0; jj < BX_COUNTOF(s_profiles); ++jj)
{
if (0 != bx::memCmp(&profiles[ii], s_profiles[jj].guid, sizeof(GUID) ) )
{
continue;
}
D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT support
{
.NodeIndex = 0,
.Configuration =
{
.DecodeProfile = profiles[ii],
.BitstreamEncryption = D3D12_BITSTREAM_ENCRYPTION_TYPE_NONE,
.InterlaceType = D3D12_VIDEO_FRAME_CODED_INTERLACE_TYPE_NONE,
},
.Width = 1920,
.Height = 1080,
.DecodeFormat = (s_profiles[jj].caps & BGFX_CAPS_VIDEO_CODEC_BIT_10)
? DXGI_FORMAT_P010 : DXGI_FORMAT_NV12,
.FrameRate = { .Numerator = 30, .Denominator = 1 },
.BitRate = 0,
.SupportFlags = D3D12_VIDEO_DECODE_SUPPORT_FLAG_NONE,
.ConfigurationFlags = D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_NONE,
.DecodeTier = D3D12_VIDEO_DECODE_TIER_NOT_SUPPORTED,
};
hr = videoDevice->CheckFeatureSupport(
D3D12_FEATURE_VIDEO_DECODE_SUPPORT
, &support
, sizeof(support)
);
if (SUCCEEDED(hr)
&& 0 != (support.SupportFlags & D3D12_VIDEO_DECODE_SUPPORT_FLAG_SUPPORTED) )
{
g_caps.codecs[s_profiles[jj].codec] |= s_profiles[jj].caps;
anySupported = true;
}
break;
}
}
if (anySupported)
{
g_caps.supported |= BGFX_CAPS_VIDEO_DECODE;
const TextureFormat::Enum dstFormats[] =
{
TextureFormat::BGRA8,
TextureFormat::RGBA8,
TextureFormat::RGB10A2,
TextureFormat::RGBA16F,
};
for (uint32_t ii = 0; ii < BX_COUNTOF(dstFormats); ++ii)
{
const TextureFormat::Enum fmt = dstFormats[ii];
if (0 != (g_caps.formats[fmt] & BGFX_CAPS_FORMAT_TEXTURE_2D) )
{
g_caps.formats[fmt] |= BGFX_CAPS_FORMAT_TEXTURE_VIDEO_DECODE_DST;
}
}
}
DX_RELEASE_I(videoDevice);
}
} } // namespace bgfx::d3d12
# else
namespace bgfx { namespace d3d12
{
void initVideoDecoder(const VideoBindingD3D12& _binding)
{
BX_UNUSED(_binding);
}
VideoDecoderD3D12* videoDecoderCreate(const VideoDecoderInit& _init, RendererContextD3D12* _renderer, uint16_t _width, uint16_t _height)
{
BX_UNUSED(_init, _renderer, _width, _height);
return NULL;
}
void videoDecoderDestroy(VideoDecoderD3D12* _decoder)
{
BX_UNUSED(_decoder);
}
bool videoDecoderDecode(VideoDecoderD3D12* _decoder, const VideoDecoderFrame& _frame, TextureD3D12& _dst)
{
BX_UNUSED(_decoder, _frame, _dst);
return false;
}
} } // namespace bgfx::d3d12
# endif // BGFX_CONFIG_VIDEO_DIRECT3D12
#endif // BGFX_CONFIG_RENDERER_DIRECT3D12