mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2026-11-06 05:17:42 +00:00
Frames Before FPS After FPS Before Memory After Memory Before GPU Commit After GPU Commit HEVC 3840x2160 11904 645.51 646.97 1024.63 MiB 764.11 MiB 676.05 MiB 413.59 MiB AV1 3840x2160 2880 383.19 383.40 286.05 MiB 204.57 MiB 209.66 MiB 139.25 MiB VP9 7680x4320 16504 166.14 165.94 2329.95 MiB 1372.44 MiB 2098.50 MiB 1147.08 MiB Signed-off-by: Wu Jianhua <toqsxw@outlook.com>
707 lines
25 KiB
C
707 lines
25 KiB
C
/*
|
|
* Direct3D 12 HW acceleration video decoder
|
|
*
|
|
* copyright (c) 2022-2023 Wu Jianhua <toqsxw@outlook.com>
|
|
*
|
|
* This file is part of FFmpeg.
|
|
*
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <initguid.h>
|
|
|
|
#include "libavutil/common.h"
|
|
#include "libavutil/log.h"
|
|
#include "libavutil/mem.h"
|
|
#include "libavutil/time.h"
|
|
#include "libavutil/hwcontext_d3d12va_internal.h"
|
|
#include "libavutil/hwcontext_d3d12va.h"
|
|
#include "avcodec.h"
|
|
#include "decode.h"
|
|
#include "d3d12va_decode.h"
|
|
#include "dxva2_internal.h"
|
|
|
|
typedef struct HelperObjects {
|
|
ID3D12CommandAllocator *command_allocator;
|
|
ID3D12Resource *buffer;
|
|
uint64_t fence_value;
|
|
} HelperObjects;
|
|
|
|
typedef struct ReferenceFrame {
|
|
ID3D12Resource *resource;
|
|
int used;
|
|
ID3D12Resource *output_resource;
|
|
} ReferenceFrame;
|
|
|
|
static ID3D12Resource *get_reference_only_resource(AVCodecContext *avctx, ID3D12Resource *output_resource)
|
|
{
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
AVD3D12VADeviceContext *device_hwctx = ctx->device_ctx;
|
|
int i = 0;
|
|
ID3D12Resource *resource = NULL;
|
|
D3D12_HEAP_PROPERTIES props = { .Type = D3D12_HEAP_TYPE_DEFAULT };
|
|
D3D12_RESOURCE_DESC desc;
|
|
ReferenceFrame *reference_only_map = ctx->reference_only_map;
|
|
if (reference_only_map == NULL) {
|
|
av_log(avctx, AV_LOG_ERROR, "Reference frames are not allocated!\n");
|
|
return NULL;
|
|
}
|
|
|
|
// Reuse the slot already mapped to this output resource. Output surfaces are
|
|
// recycled by the frame pool, so without this the same output_resource would
|
|
// be assigned a new slot every time it is reused, leaking slots until the
|
|
// pool is exhausted.
|
|
for (i = 0; i < ctx->max_num_ref + 1; i++) {
|
|
if (reference_only_map[i].resource != NULL &&
|
|
reference_only_map[i].output_resource == output_resource) {
|
|
reference_only_map[i].used = 1;
|
|
return reference_only_map[i].resource;
|
|
}
|
|
}
|
|
|
|
// Find an unused resource.
|
|
for (i = 0; i < ctx->max_num_ref + 1; i++) {
|
|
if (!reference_only_map[i].used && reference_only_map[i].resource != NULL) {
|
|
reference_only_map[i].used = 1;
|
|
resource = reference_only_map[i].resource;
|
|
reference_only_map[i].output_resource = output_resource;
|
|
return resource;
|
|
}
|
|
}
|
|
|
|
// Find space to allocate.
|
|
for (i = 0; i < ctx->max_num_ref + 1; i++) {
|
|
if (reference_only_map[i].resource == NULL)
|
|
break;
|
|
}
|
|
|
|
if (i == ctx->max_num_ref + 1) {
|
|
av_log(avctx, AV_LOG_ERROR, "No space for new Reference frame!\n");
|
|
return NULL;
|
|
}
|
|
|
|
// allocate frame
|
|
output_resource->lpVtbl->GetDesc(output_resource, &desc);
|
|
desc.Flags = D3D12_RESOURCE_FLAG_VIDEO_DECODE_REFERENCE_ONLY | D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE;
|
|
|
|
if (FAILED(ID3D12Device_CreateCommittedResource(device_hwctx->device, &props, D3D12_HEAP_FLAG_NONE, &desc,
|
|
D3D12_RESOURCE_STATE_COMMON, NULL, &IID_ID3D12Resource, (void **)&reference_only_map[i].resource))) {
|
|
av_log(ctx, AV_LOG_ERROR, "Failed to create D3D12 Reference Resource!\n");
|
|
return NULL;
|
|
}
|
|
|
|
reference_only_map[i].used = 1;
|
|
resource = reference_only_map[i].resource;
|
|
reference_only_map[i].output_resource = output_resource;
|
|
|
|
return resource;
|
|
}
|
|
|
|
static void free_reference_only_resources(AVCodecContext *avctx)
|
|
{
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
int i;
|
|
ReferenceFrame *reference_only_map = ctx->reference_only_map;
|
|
if (reference_only_map != NULL) {
|
|
for (i = 0; i < ctx->max_num_ref + 1; i++) {
|
|
if (reference_only_map[i].resource != NULL) {
|
|
D3D12_OBJECT_RELEASE(reference_only_map[i].resource);
|
|
}
|
|
}
|
|
av_freep(&ctx->reference_only_map);
|
|
av_freep(&ctx->ref_only_resources);
|
|
}
|
|
}
|
|
|
|
static void prepare_reference_only_resources(AVCodecContext *avctx)
|
|
{
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
int i, j;
|
|
ReferenceFrame *reference_only_map = ctx->reference_only_map;
|
|
if (reference_only_map == NULL)
|
|
return;
|
|
memset(ctx->ref_only_resources, 0, ctx->max_num_ref * sizeof(*(ctx->ref_only_resources)));
|
|
for (j = 0; j < ctx->max_num_ref + 1; j++) {
|
|
for (i = 0; i < ctx->max_num_ref; i++) {
|
|
if (reference_only_map[j].used && reference_only_map[j].output_resource == ctx->ref_resources[i]) {
|
|
ctx->ref_only_resources[i] = reference_only_map[j].resource;
|
|
break;
|
|
}
|
|
}
|
|
if (i == ctx->max_num_ref)
|
|
reference_only_map[j].used = 0;
|
|
}
|
|
}
|
|
|
|
unsigned ff_d3d12va_get_surface_index(const AVCodecContext *avctx,
|
|
D3D12VADecodeContext *ctx, const AVFrame *frame,
|
|
int curr)
|
|
{
|
|
AVD3D12VAFrame *f;
|
|
ID3D12Resource *res;
|
|
unsigned i;
|
|
|
|
f = (AVD3D12VAFrame *)frame->data[0];
|
|
if (!f)
|
|
goto fail;
|
|
|
|
res = f->texture;
|
|
if (!res)
|
|
goto fail;
|
|
|
|
for (i = 0; i < ctx->max_num_ref; i++) {
|
|
if (ctx->ref_resources[i] && res == ctx->ref_resources[i]) {
|
|
ctx->used_mask |= 1 << i;
|
|
return i;
|
|
}
|
|
}
|
|
|
|
if (curr) {
|
|
for (i = 0; i < ctx->max_num_ref; i++) {
|
|
if (!((ctx->used_mask >> i) & 0x1)) {
|
|
ctx->ref_resources[i] = res;
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
fail:
|
|
av_log((AVCodecContext *)avctx, AV_LOG_WARNING, "Could not get surface index. Using 0 instead.\n");
|
|
return 0;
|
|
}
|
|
|
|
static int d3d12va_resize_bitstream_buffer(AVCodecContext *avctx, ID3D12Resource **ppBuffer,
|
|
UINT64 bitstream_size)
|
|
{
|
|
HRESULT hr;
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
D3D12_HEAP_PROPERTIES heap_props = { .Type = D3D12_HEAP_TYPE_UPLOAD };
|
|
ID3D12Resource *buffer = NULL;
|
|
D3D12_RESOURCE_DESC desc;
|
|
|
|
if (!bitstream_size)
|
|
return 0;
|
|
|
|
if (*ppBuffer) {
|
|
(*ppBuffer)->lpVtbl->GetDesc(*ppBuffer, &desc);
|
|
if (desc.Width >= bitstream_size)
|
|
return 0;
|
|
} else {
|
|
desc = (D3D12_RESOURCE_DESC) {
|
|
.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
|
|
.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT,
|
|
.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,
|
|
};
|
|
}
|
|
desc.Width = bitstream_size * 1.5;
|
|
|
|
hr = ID3D12Device_CreateCommittedResource(ctx->device_ctx->device, &heap_props, D3D12_HEAP_FLAG_NONE,
|
|
&desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL,
|
|
&IID_ID3D12Resource, (void **)&buffer);
|
|
if (FAILED(hr)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to create a new D3D12 bitstream buffer for uploading!\n");
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
D3D12_OBJECT_RELEASE(*ppBuffer);
|
|
*ppBuffer = buffer;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int d3d12va_get_valid_helper_objects(AVCodecContext *avctx, ID3D12CommandAllocator **ppAllocator,
|
|
ID3D12Resource **ppBuffer, UINT64 bitstream_size)
|
|
{
|
|
HRESULT hr;
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
HelperObjects obj = { 0 };
|
|
|
|
if (av_fifo_peek(ctx->objects_queue, &obj, 1, 0) >= 0) {
|
|
uint64_t completion = ID3D12Fence_GetCompletedValue(ctx->sync_ctx.fence);
|
|
if (completion >= obj.fence_value) {
|
|
*ppAllocator = obj.command_allocator;
|
|
*ppBuffer = obj.buffer;
|
|
av_fifo_read(ctx->objects_queue, &obj, 1);
|
|
return d3d12va_resize_bitstream_buffer(avctx, ppBuffer, bitstream_size);
|
|
}
|
|
}
|
|
|
|
hr = ID3D12Device_CreateCommandAllocator(ctx->device_ctx->device, D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE,
|
|
&IID_ID3D12CommandAllocator, (void **)ppAllocator);
|
|
if (FAILED(hr)) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to create a new command allocator!\n");
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
return d3d12va_resize_bitstream_buffer(avctx, ppBuffer, bitstream_size);
|
|
}
|
|
|
|
static int d3d12va_discard_helper_objects(AVCodecContext *avctx, ID3D12CommandAllocator *pAllocator,
|
|
ID3D12Resource *pBuffer, uint64_t fence_value)
|
|
{
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
|
|
HelperObjects obj = {
|
|
.command_allocator = pAllocator,
|
|
.buffer = pBuffer,
|
|
.fence_value = fence_value,
|
|
};
|
|
|
|
if (av_fifo_write(ctx->objects_queue, &obj, 1) < 0) {
|
|
D3D12_OBJECT_RELEASE(pAllocator);
|
|
D3D12_OBJECT_RELEASE(pBuffer);
|
|
return AVERROR(ENOMEM);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int d3d12va_fence_completion(AVD3D12VASyncContext *psync_ctx)
|
|
{
|
|
uint64_t completion = ID3D12Fence_GetCompletedValue(psync_ctx->fence);
|
|
if (completion < psync_ctx->fence_value) {
|
|
if (FAILED(ID3D12Fence_SetEventOnCompletion(psync_ctx->fence, psync_ctx->fence_value, psync_ctx->event)))
|
|
return AVERROR(EINVAL);
|
|
|
|
WaitForSingleObjectEx(psync_ctx->event, INFINITE, FALSE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void bufref_free_interface(void *opaque, uint8_t *data)
|
|
{
|
|
D3D12_OBJECT_RELEASE(opaque);
|
|
}
|
|
|
|
static AVBufferRef *bufref_wrap_interface(IUnknown *iface)
|
|
{
|
|
return av_buffer_create((uint8_t*)iface, 1, bufref_free_interface, iface, 0);
|
|
}
|
|
|
|
static int d3d12va_sync_with_gpu(AVCodecContext *avctx)
|
|
{
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
|
|
DX_CHECK(ID3D12CommandQueue_Signal(ctx->command_queue, ctx->sync_ctx.fence, ++ctx->sync_ctx.fence_value));
|
|
return d3d12va_fence_completion(&ctx->sync_ctx);
|
|
|
|
fail:
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
static int d3d12va_create_decoder_heap(AVCodecContext *avctx)
|
|
{
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
AVHWFramesContext *frames_ctx = D3D12VA_FRAMES_CONTEXT(avctx);
|
|
AVD3D12VADeviceContext *device_hwctx = ctx->device_ctx;
|
|
AVD3D12VAFramesContext *frames_hwctx = frames_ctx->hwctx;
|
|
|
|
D3D12_VIDEO_DECODER_HEAP_DESC desc = {
|
|
.NodeMask = 0,
|
|
.Configuration = ctx->cfg,
|
|
.DecodeWidth = frames_ctx->width,
|
|
.DecodeHeight = frames_ctx->height,
|
|
.Format = frames_hwctx->format,
|
|
.FrameRate = { avctx->framerate.num, avctx->framerate.den },
|
|
.BitRate = avctx->bit_rate,
|
|
.MaxDecodePictureBufferCount = ctx->max_num_ref,
|
|
};
|
|
|
|
DX_CHECK(ID3D12VideoDevice_CreateVideoDecoderHeap(device_hwctx->video_device, &desc,
|
|
&IID_ID3D12VideoDecoderHeap, (void **)&ctx->decoder_heap));
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
if (ctx->decoder) {
|
|
av_log(avctx, AV_LOG_ERROR, "D3D12 doesn't support decoding frames with an extent "
|
|
"[width(%d), height(%d)], on your device!\n", frames_ctx->width, frames_ctx->height);
|
|
}
|
|
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
static int d3d12va_create_decoder(AVCodecContext *avctx)
|
|
{
|
|
D3D12_VIDEO_DECODER_DESC desc;
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
AVHWFramesContext *frames_ctx = D3D12VA_FRAMES_CONTEXT(avctx);
|
|
AVD3D12VADeviceContext *device_hwctx = ctx->device_ctx;
|
|
AVD3D12VAFramesContext *frames_hwctx = frames_ctx->hwctx;
|
|
|
|
D3D12_FEATURE_DATA_VIDEO_DECODE_SUPPORT feature = {
|
|
.NodeIndex = 0,
|
|
.Configuration = ctx->cfg,
|
|
.Width = frames_ctx->width,
|
|
.Height = frames_ctx->height,
|
|
.DecodeFormat = frames_hwctx->format,
|
|
.FrameRate = { avctx->framerate.num, avctx->framerate.den },
|
|
.BitRate = avctx->bit_rate,
|
|
};
|
|
|
|
DX_CHECK(ID3D12VideoDevice_CheckFeatureSupport(device_hwctx->video_device, D3D12_FEATURE_VIDEO_DECODE_SUPPORT,
|
|
&feature, sizeof(feature)));
|
|
if (!(feature.SupportFlags & D3D12_VIDEO_DECODE_SUPPORT_FLAG_SUPPORTED)) {
|
|
av_log(avctx, AV_LOG_ERROR, "D3D12 video decode is not supported on this device.\n");
|
|
return AVERROR(ENOSYS);
|
|
}
|
|
if (!(feature.DecodeTier >= D3D12_VIDEO_DECODE_TIER_2)) {
|
|
av_log(avctx, AV_LOG_ERROR, "D3D12 video decode on this device requires tier %d support, "
|
|
"but it is not implemented.\n", feature.DecodeTier);
|
|
return AVERROR_PATCHWELCOME;
|
|
}
|
|
|
|
ctx->reference_only_map = NULL;
|
|
ctx->ref_only_resources = NULL;
|
|
if (feature.ConfigurationFlags & D3D12_VIDEO_DECODE_CONFIGURATION_FLAG_REFERENCE_ONLY_ALLOCATIONS_REQUIRED) {
|
|
av_log(avctx, AV_LOG_VERBOSE, "Reference-Only Allocations are required for this D3D12 decoder configuration.\n");
|
|
ctx->reference_only_map = av_calloc(ctx->max_num_ref + 1, sizeof(ReferenceFrame));
|
|
if (!ctx->reference_only_map)
|
|
return AVERROR(ENOMEM);
|
|
ctx->ref_only_resources = av_calloc(ctx->max_num_ref, sizeof(*ctx->ref_only_resources));
|
|
if (!ctx->ref_only_resources)
|
|
return AVERROR(ENOMEM);
|
|
}
|
|
|
|
desc = (D3D12_VIDEO_DECODER_DESC) {
|
|
.NodeMask = 0,
|
|
.Configuration = ctx->cfg,
|
|
};
|
|
|
|
DX_CHECK(ID3D12VideoDevice_CreateVideoDecoder(device_hwctx->video_device, &desc, &IID_ID3D12VideoDecoder,
|
|
(void **)&ctx->decoder));
|
|
|
|
ctx->decoder_ref = bufref_wrap_interface((IUnknown *)ctx->decoder);
|
|
if (!ctx->decoder_ref)
|
|
return AVERROR(ENOMEM);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
int ff_d3d12va_common_frame_params(AVCodecContext *avctx, AVBufferRef *hw_frames_ctx)
|
|
{
|
|
AVHWFramesContext *frames_ctx = (AVHWFramesContext *)hw_frames_ctx->data;
|
|
|
|
frames_ctx->format = AV_PIX_FMT_D3D12;
|
|
switch (avctx->sw_pix_fmt) {
|
|
case AV_PIX_FMT_YUV420P10: frames_ctx->sw_format = AV_PIX_FMT_P010; break;
|
|
case AV_PIX_FMT_YUV420P12: frames_ctx->sw_format = AV_PIX_FMT_P012; break;
|
|
default: frames_ctx->sw_format = AV_PIX_FMT_NV12; break;
|
|
}
|
|
frames_ctx->width = avctx->coded_width;
|
|
frames_ctx->height = avctx->coded_height;
|
|
|
|
return 0;
|
|
}
|
|
|
|
av_cold int ff_d3d12va_decode_init(AVCodecContext *avctx)
|
|
{
|
|
int ret;
|
|
AVHWFramesContext *frames_ctx;
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
ID3D12Resource *buffer = NULL;
|
|
ID3D12CommandAllocator *command_allocator = NULL;
|
|
D3D12_COMMAND_QUEUE_DESC queue_desc = {
|
|
.Type = D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE,
|
|
.Priority = 0,
|
|
.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE,
|
|
.NodeMask = 0,
|
|
};
|
|
|
|
ctx->pix_fmt = avctx->hwaccel->pix_fmt;
|
|
|
|
ret = ff_decode_get_hw_frames_ctx(avctx, AV_HWDEVICE_TYPE_D3D12VA);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
frames_ctx = D3D12VA_FRAMES_CONTEXT(avctx);
|
|
ctx->device_ctx = (AVD3D12VADeviceContext *)frames_ctx->device_ctx->hwctx;
|
|
|
|
if (frames_ctx->format != ctx->pix_fmt) {
|
|
av_log(avctx, AV_LOG_ERROR, "Invalid pixfmt for hwaccel!\n");
|
|
goto fail;
|
|
}
|
|
|
|
ret = d3d12va_create_decoder(avctx);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
ret = d3d12va_create_decoder_heap(avctx);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
ctx->ref_resources = av_calloc(ctx->max_num_ref, sizeof(*ctx->ref_resources));
|
|
if (!ctx->ref_resources)
|
|
return AVERROR(ENOMEM);
|
|
|
|
ctx->ref_subresources = av_calloc(ctx->max_num_ref, sizeof(*ctx->ref_subresources));
|
|
if (!ctx->ref_subresources)
|
|
return AVERROR(ENOMEM);
|
|
|
|
ctx->objects_queue = av_fifo_alloc2(D3D12VA_VIDEO_DEC_ASYNC_DEPTH,
|
|
sizeof(HelperObjects), AV_FIFO_FLAG_AUTO_GROW);
|
|
if (!ctx->objects_queue)
|
|
return AVERROR(ENOMEM);
|
|
|
|
DX_CHECK(ID3D12Device_CreateFence(ctx->device_ctx->device, 0, D3D12_FENCE_FLAG_NONE,
|
|
&IID_ID3D12Fence, (void **)&ctx->sync_ctx.fence));
|
|
|
|
ctx->sync_ctx.event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
if (!ctx->sync_ctx.event)
|
|
goto fail;
|
|
|
|
ret = d3d12va_get_valid_helper_objects(avctx, &command_allocator, &buffer, 0);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
DX_CHECK(ID3D12Device_CreateCommandQueue(ctx->device_ctx->device, &queue_desc,
|
|
&IID_ID3D12CommandQueue, (void **)&ctx->command_queue));
|
|
|
|
DX_CHECK(ID3D12Device_CreateCommandList(ctx->device_ctx->device, 0, queue_desc.Type,
|
|
command_allocator, NULL, &IID_ID3D12CommandList, (void **)&ctx->command_list));
|
|
|
|
DX_CHECK(ID3D12VideoDecodeCommandList_Close(ctx->command_list));
|
|
|
|
ID3D12CommandQueue_ExecuteCommandLists(ctx->command_queue, 1, (ID3D12CommandList **)&ctx->command_list);
|
|
|
|
ret = d3d12va_sync_with_gpu(avctx);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
d3d12va_discard_helper_objects(avctx, command_allocator, buffer, ctx->sync_ctx.fence_value);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
D3D12_OBJECT_RELEASE(command_allocator);
|
|
D3D12_OBJECT_RELEASE(buffer);
|
|
ff_d3d12va_decode_uninit(avctx);
|
|
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
av_cold int ff_d3d12va_decode_uninit(AVCodecContext *avctx)
|
|
{
|
|
int num_allocator = 0;
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
HelperObjects obj;
|
|
|
|
if (ctx->sync_ctx.fence)
|
|
d3d12va_sync_with_gpu(avctx);
|
|
|
|
av_freep(&ctx->ref_resources);
|
|
av_freep(&ctx->ref_subresources);
|
|
|
|
D3D12_OBJECT_RELEASE(ctx->command_list);
|
|
D3D12_OBJECT_RELEASE(ctx->command_queue);
|
|
|
|
if (ctx->objects_queue) {
|
|
while (av_fifo_read(ctx->objects_queue, &obj, 1) >= 0) {
|
|
num_allocator++;
|
|
D3D12_OBJECT_RELEASE(obj.buffer);
|
|
D3D12_OBJECT_RELEASE(obj.command_allocator);
|
|
}
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, "Total number of command allocators reused: %d\n", num_allocator);
|
|
}
|
|
free_reference_only_resources(avctx);
|
|
|
|
av_fifo_freep2(&ctx->objects_queue);
|
|
|
|
D3D12_OBJECT_RELEASE(ctx->sync_ctx.fence);
|
|
if (ctx->sync_ctx.event)
|
|
CloseHandle(ctx->sync_ctx.event);
|
|
|
|
D3D12_OBJECT_RELEASE(ctx->decoder_heap);
|
|
|
|
av_buffer_unref(&ctx->decoder_ref);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int d3d12va_update_reference_frames_state(AVCodecContext *avctx, D3D12_RESOURCE_BARRIER *barriers,
|
|
ID3D12Resource *current_resource, int state_before, int state_end)
|
|
{
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
ID3D12Resource **ref_resources = ctx->ref_only_resources ? ctx->ref_only_resources : ctx->ref_resources;
|
|
|
|
int num_barrier = 0;
|
|
for (int i = 0; i < ctx->max_num_ref; i++) {
|
|
if (((ctx->used_mask >> i) & 0x1) && ref_resources[i] && ref_resources[i] != current_resource) {
|
|
barriers[num_barrier].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
|
barriers[num_barrier].Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
|
|
barriers[num_barrier].Transition = (D3D12_RESOURCE_TRANSITION_BARRIER) {
|
|
.pResource = ref_resources[i],
|
|
.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
|
|
.StateBefore = state_before,
|
|
.StateAfter = state_end,
|
|
};
|
|
num_barrier++;
|
|
}
|
|
}
|
|
|
|
return num_barrier;
|
|
}
|
|
|
|
int ff_d3d12va_common_end_frame(AVCodecContext *avctx, AVFrame *frame,
|
|
const void *pp, unsigned pp_size,
|
|
const void *qm, unsigned qm_size,
|
|
UINT64 bitstream_size,
|
|
int(*update_input_arguments)(AVCodecContext *, D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS *, ID3D12Resource *))
|
|
{
|
|
int ret;
|
|
D3D12VADecodeContext *ctx = D3D12VA_DECODE_CONTEXT(avctx);
|
|
ID3D12Resource *buffer = NULL;
|
|
ID3D12CommandAllocator *command_allocator = NULL;
|
|
AVD3D12VAFrame *f = (AVD3D12VAFrame*)frame->data[0];
|
|
ID3D12Resource *output_resource = (ID3D12Resource*)f->texture;
|
|
ID3D12Resource *ref_resource = NULL;
|
|
|
|
ID3D12VideoDecodeCommandList *cmd_list = ctx->command_list;
|
|
D3D12_RESOURCE_BARRIER barriers[32] = { 0 };
|
|
|
|
D3D12_VIDEO_DECODE_INPUT_STREAM_ARGUMENTS input_args = {
|
|
.NumFrameArguments = 2,
|
|
.FrameArguments = {
|
|
[0] = {
|
|
.Type = D3D12_VIDEO_DECODE_ARGUMENT_TYPE_PICTURE_PARAMETERS,
|
|
.Size = pp_size,
|
|
.pData = (void *)pp,
|
|
},
|
|
[1] = {
|
|
.Type = D3D12_VIDEO_DECODE_ARGUMENT_TYPE_INVERSE_QUANTIZATION_MATRIX,
|
|
.Size = qm_size,
|
|
.pData = (void *)qm,
|
|
},
|
|
},
|
|
.pHeap = ctx->decoder_heap,
|
|
};
|
|
|
|
D3D12_VIDEO_DECODE_OUTPUT_STREAM_ARGUMENTS output_args = {
|
|
.ConversionArguments = { 0 },
|
|
.OutputSubresource = 0,
|
|
.pOutputTexture2D = output_resource,
|
|
};
|
|
|
|
memset(ctx->ref_subresources, 0, sizeof(UINT) * ctx->max_num_ref);
|
|
input_args.ReferenceFrames.NumTexture2Ds = ctx->max_num_ref;
|
|
input_args.ReferenceFrames.pSubresources = ctx->ref_subresources;
|
|
|
|
if (ctx->reference_only_map) {
|
|
ref_resource = get_reference_only_resource(avctx, output_resource);
|
|
if (ref_resource == NULL) {
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to get reference frame!\n");
|
|
goto fail;
|
|
}
|
|
prepare_reference_only_resources(avctx);
|
|
|
|
output_args.ConversionArguments.Enable = 1;
|
|
input_args.ReferenceFrames.ppTexture2Ds = ctx->ref_only_resources;
|
|
output_args.ConversionArguments.pReferenceTexture2D = ref_resource;
|
|
output_args.ConversionArguments.ReferenceSubresource = 0;
|
|
} else {
|
|
ref_resource = output_resource;
|
|
input_args.ReferenceFrames.ppTexture2Ds = ctx->ref_resources;
|
|
}
|
|
|
|
UINT num_barrier = 1;
|
|
barriers[0] = (D3D12_RESOURCE_BARRIER) {
|
|
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
|
|
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
|
|
.Transition = {
|
|
.pResource = output_resource,
|
|
.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
|
|
.StateBefore = D3D12_RESOURCE_STATE_COMMON,
|
|
.StateAfter = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE,
|
|
},
|
|
};
|
|
|
|
if (ctx->reference_only_map) {
|
|
barriers[1] = (D3D12_RESOURCE_BARRIER) {
|
|
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
|
|
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
|
|
.Transition = {
|
|
.pResource = ref_resource,
|
|
.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
|
|
.StateBefore = D3D12_RESOURCE_STATE_COMMON,
|
|
.StateAfter = D3D12_RESOURCE_STATE_VIDEO_DECODE_WRITE,
|
|
},
|
|
};
|
|
num_barrier++;
|
|
}
|
|
|
|
ret = d3d12va_fence_completion(&f->sync_ctx);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
if (!qm)
|
|
input_args.NumFrameArguments = 1;
|
|
|
|
ret = d3d12va_get_valid_helper_objects(avctx, &command_allocator, &buffer, bitstream_size);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
ret = update_input_arguments(avctx, &input_args, buffer);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
DX_CHECK(ID3D12CommandAllocator_Reset(command_allocator));
|
|
|
|
DX_CHECK(ID3D12VideoDecodeCommandList_Reset(cmd_list, command_allocator));
|
|
|
|
num_barrier += d3d12va_update_reference_frames_state(avctx, &barriers[num_barrier], ref_resource, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VIDEO_DECODE_READ);
|
|
|
|
ID3D12VideoDecodeCommandList_ResourceBarrier(cmd_list, num_barrier, barriers);
|
|
|
|
ID3D12VideoDecodeCommandList_DecodeFrame(cmd_list, ctx->decoder, &output_args, &input_args);
|
|
|
|
for (int i = 0; i < num_barrier; i++)
|
|
FFSWAP(D3D12_RESOURCE_STATES, barriers[i].Transition.StateBefore, barriers[i].Transition.StateAfter);
|
|
|
|
ID3D12VideoDecodeCommandList_ResourceBarrier(cmd_list, num_barrier, barriers);
|
|
|
|
DX_CHECK(ID3D12VideoDecodeCommandList_Close(cmd_list));
|
|
|
|
ID3D12CommandQueue_ExecuteCommandLists(ctx->command_queue, 1, (ID3D12CommandList **)&ctx->command_list);
|
|
|
|
DX_CHECK(ID3D12CommandQueue_Signal(ctx->command_queue, f->sync_ctx.fence, ++f->sync_ctx.fence_value));
|
|
|
|
DX_CHECK(ID3D12CommandQueue_Signal(ctx->command_queue, ctx->sync_ctx.fence, ++ctx->sync_ctx.fence_value));
|
|
|
|
ret = d3d12va_discard_helper_objects(avctx, command_allocator, buffer, ctx->sync_ctx.fence_value);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
if (command_allocator)
|
|
d3d12va_discard_helper_objects(avctx, command_allocator, buffer, ctx->sync_ctx.fence_value);
|
|
return AVERROR(EINVAL);
|
|
}
|