1557 lines · 54.8 KB · C
RawDownload
/* * Direct3D 12 HW acceleration video encoder * * Copyright (c) 2024 Intel Corporation * * 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 "libavutil/avassert.h"#include "libavutil/common.h"#include "libavutil/internal.h"#include "libavutil/log.h"#include "libavutil/mem.h"#include "libavutil/pixdesc.h"#include "libavutil/hwcontext_d3d12va_internal.h"#include "libavutil/hwcontext_d3d12va.h" #include "avcodec.h"#include "d3d12va_encode.h"#include "encode.h" const AVCodecHWConfigInternal *const ff_d3d12va_encode_hw_configs[] = {    HW_CONFIG_ENCODER_FRAMES(D3D12, D3D12VA),    NULL,}; 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 int d3d12va_sync_with_gpu(AVCodecContext *avctx){    D3D12VAEncodeContext *ctx = avctx->priv_data;     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);} typedef struct CommandAllocator {    ID3D12CommandAllocator *command_allocator;    uint64_t fence_value;} CommandAllocator; static int d3d12va_get_valid_command_allocator(AVCodecContext *avctx, ID3D12CommandAllocator **ppAllocator){    HRESULT hr;    D3D12VAEncodeContext *ctx = avctx->priv_data;    CommandAllocator allocator;     if (av_fifo_peek(ctx->allocator_queue, &allocator, 1, 0) >= 0) {        uint64_t completion = ID3D12Fence_GetCompletedValue(ctx->sync_ctx.fence);        if (completion >= allocator.fence_value) {            *ppAllocator = allocator.command_allocator;            av_fifo_read(ctx->allocator_queue, &allocator, 1);            return 0;        }    }     hr = ID3D12Device_CreateCommandAllocator(ctx->hwctx->device, D3D12_COMMAND_LIST_TYPE_VIDEO_ENCODE,                                             &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 0;} static int d3d12va_discard_command_allocator(AVCodecContext *avctx, ID3D12CommandAllocator *pAllocator, uint64_t fence_value){    D3D12VAEncodeContext *ctx = avctx->priv_data;     CommandAllocator allocator = {        .command_allocator = pAllocator,        .fence_value = fence_value,    };     av_fifo_write(ctx->allocator_queue, &allocator, 1);     return 0;} static int d3d12va_encode_wait(AVCodecContext *avctx,                               FFHWBaseEncodePicture *base_pic){    D3D12VAEncodeContext *ctx = avctx->priv_data;    D3D12VAEncodePicture *pic = base_pic->priv;    uint64_t completion;     av_assert0(base_pic->encode_issued);     if (base_pic->encode_complete) {        // Already waited for this picture.        return 0;    }     completion = ID3D12Fence_GetCompletedValue(ctx->sync_ctx.fence);    if (completion < pic->fence_value) {        if (FAILED(ID3D12Fence_SetEventOnCompletion(ctx->sync_ctx.fence, pic->fence_value,                                                    ctx->sync_ctx.event)))            return AVERROR(EINVAL);         WaitForSingleObjectEx(ctx->sync_ctx.event, INFINITE, FALSE);    }     av_log(avctx, AV_LOG_DEBUG, "Sync to pic %"PRId64"/%"PRId64" "           "(input surface %p).\n", base_pic->display_order,           base_pic->encode_order, pic->input_surface->texture);     av_frame_free(&base_pic->input_image);     base_pic->encode_complete = 1;    return 0;} static int d3d12va_encode_create_metadata_buffers(AVCodecContext *avctx,                                                  D3D12VAEncodePicture *pic){    D3D12VAEncodeContext *ctx = avctx->priv_data;    int width = sizeof(D3D12_VIDEO_ENCODER_OUTPUT_METADATA) + sizeof(D3D12_VIDEO_ENCODER_FRAME_SUBREGION_METADATA);    D3D12_HEAP_PROPERTIES encoded_meta_props = { .Type = D3D12_HEAP_TYPE_DEFAULT }, resolved_meta_props;    D3D12_HEAP_TYPE resolved_heap_type = D3D12_HEAP_TYPE_READBACK;    HRESULT hr;     D3D12_RESOURCE_DESC meta_desc = {        .Dimension        = D3D12_RESOURCE_DIMENSION_BUFFER,        .Alignment        = 0,        .Width            = ctx->req.MaxEncoderOutputMetadataBufferSize,        .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,    };     hr = ID3D12Device_CreateCommittedResource(ctx->hwctx->device, &encoded_meta_props, D3D12_HEAP_FLAG_NONE,                                              &meta_desc, D3D12_RESOURCE_STATE_COMMON, NULL,                                              &IID_ID3D12Resource, (void **)&pic->encoded_metadata);    if (FAILED(hr)) {        av_log(avctx, AV_LOG_ERROR, "Failed to create metadata buffer.\n");        return AVERROR_UNKNOWN;    }     ctx->hwctx->device->lpVtbl->GetCustomHeapProperties(ctx->hwctx->device, &resolved_meta_props, 0, resolved_heap_type);     meta_desc.Width = width;     hr = ID3D12Device_CreateCommittedResource(ctx->hwctx->device, &resolved_meta_props, D3D12_HEAP_FLAG_NONE,                                              &meta_desc, D3D12_RESOURCE_STATE_COMMON, NULL,                                              &IID_ID3D12Resource, (void **)&pic->resolved_metadata);     if (FAILED(hr)) {        av_log(avctx, AV_LOG_ERROR, "Failed to create output metadata buffer.\n");        return AVERROR_UNKNOWN;    }     return 0;} static int d3d12va_encode_issue(AVCodecContext *avctx,                                FFHWBaseEncodePicture *base_pic){    FFHWBaseEncodeContext *base_ctx = avctx->priv_data;    D3D12VAEncodeContext       *ctx = avctx->priv_data;    D3D12VAEncodePicture       *pic = base_pic->priv;    AVD3D12VAFramesContext *frames_hwctx = base_ctx->input_frames->hwctx;    int err, i, j;    HRESULT hr;    char data[MAX_PARAM_BUFFER_SIZE];    void *ptr;    size_t bit_len;    ID3D12CommandAllocator *command_allocator = NULL;    ID3D12VideoEncodeCommandList2 *cmd_list = ctx->command_list;    D3D12_RESOURCE_BARRIER barriers[32] = { 0 };    D3D12_VIDEO_ENCODE_REFERENCE_FRAMES d3d12_refs = { 0 };     D3D12_VIDEO_ENCODER_ENCODEFRAME_INPUT_ARGUMENTS input_args = {        .SequenceControlDesc = {            .Flags = D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_NONE,            .IntraRefreshConfig = { 0 },            .RateControl = ctx->rc,            .PictureTargetResolution = ctx->resolution,            .SelectedLayoutMode = D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME,            .FrameSubregionsLayoutData = { 0 },            .CodecGopSequence = ctx->gop,        },        .pInputFrame = pic->input_surface->texture,        .InputFrameSubresource = 0,    };     D3D12_VIDEO_ENCODER_ENCODEFRAME_OUTPUT_ARGUMENTS output_args = { 0 };     D3D12_VIDEO_ENCODER_RESOLVE_METADATA_INPUT_ARGUMENTS input_metadata = {        .EncoderCodec = ctx->codec->d3d12_codec,        .EncoderProfile = ctx->profile->d3d12_profile,        .EncoderInputFormat = frames_hwctx->format,        .EncodedPictureEffectiveResolution = ctx->resolution,    };     D3D12_VIDEO_ENCODER_RESOLVE_METADATA_OUTPUT_ARGUMENTS output_metadata = { 0 };     memset(data, 0, sizeof(data));     av_log(avctx, AV_LOG_DEBUG, "Issuing encode for pic %"PRId64"/%"PRId64" "           "as type %s.\n", base_pic->display_order, base_pic->encode_order,           ff_hw_base_encode_get_pictype_name(base_pic->type));    if (base_pic->nb_refs[0] == 0 && base_pic->nb_refs[1] == 0) {        av_log(avctx, AV_LOG_DEBUG, "No reference pictures.\n");    } else {        av_log(avctx, AV_LOG_DEBUG, "L0 refers to");        for (i = 0; i < base_pic->nb_refs[0]; i++) {            av_log(avctx, AV_LOG_DEBUG, " %"PRId64"/%"PRId64,                   base_pic->refs[0][i]->display_order, base_pic->refs[0][i]->encode_order);        }        av_log(avctx, AV_LOG_DEBUG, ".\n");         if (base_pic->nb_refs[1]) {            av_log(avctx, AV_LOG_DEBUG, "L1 refers to");            for (i = 0; i < base_pic->nb_refs[1]; i++) {                av_log(avctx, AV_LOG_DEBUG, " %"PRId64"/%"PRId64,                       base_pic->refs[1][i]->display_order, base_pic->refs[1][i]->encode_order);            }            av_log(avctx, AV_LOG_DEBUG, ".\n");        }    }     av_assert0(!base_pic->encode_issued);    for (i = 0; i < base_pic->nb_refs[0]; i++) {        av_assert0(base_pic->refs[0][i]);        av_assert0(base_pic->refs[0][i]->encode_issued);    }    for (i = 0; i < base_pic->nb_refs[1]; i++) {        av_assert0(base_pic->refs[1][i]);        av_assert0(base_pic->refs[1][i]->encode_issued);    }     av_log(avctx, AV_LOG_DEBUG, "Input surface is %p.\n", pic->input_surface->texture);     pic->recon_surface = (AVD3D12VAFrame *)base_pic->recon_image->data[0];    av_log(avctx, AV_LOG_DEBUG, "Recon surface is %p.\n",           pic->recon_surface->texture);     pic->output_buffer_ref = av_buffer_pool_get(ctx->output_buffer_pool);    if (!pic->output_buffer_ref) {        err = AVERROR(ENOMEM);        goto fail;    }    pic->output_buffer = (ID3D12Resource *)pic->output_buffer_ref->data;    av_log(avctx, AV_LOG_DEBUG, "Output buffer is %p.\n",           pic->output_buffer);     err = d3d12va_encode_create_metadata_buffers(avctx, pic);    if (err < 0)        goto fail;     if (ctx->codec->init_picture_params) {        err = ctx->codec->init_picture_params(avctx, base_pic);        if (err < 0) {            av_log(avctx, AV_LOG_ERROR, "Failed to initialise picture "                   "parameters: %d.\n", err);            goto fail;        }    }     if (base_pic->type == FF_HW_PICTURE_TYPE_IDR) {        if (ctx->codec->write_sequence_header) {            bit_len = 8 * sizeof(data);            err = ctx->codec->write_sequence_header(avctx, data, &bit_len);            if (err < 0) {                av_log(avctx, AV_LOG_ERROR, "Failed to write per-sequence "                       "header: %d.\n", err);                goto fail;            }        }         pic->header_size = (int)bit_len / 8;        pic->aligned_header_size = pic->header_size % ctx->req.CompressedBitstreamBufferAccessAlignment ?                                   FFALIGN(pic->header_size, ctx->req.CompressedBitstreamBufferAccessAlignment) :                                   pic->header_size;         hr = ID3D12Resource_Map(pic->output_buffer, 0, NULL, (void **)&ptr);        if (FAILED(hr)) {            err = AVERROR_UNKNOWN;            goto fail;        }         memcpy(ptr, data, pic->aligned_header_size);        ID3D12Resource_Unmap(pic->output_buffer, 0, NULL);    }     d3d12_refs.NumTexture2Ds = base_pic->nb_refs[0] + base_pic->nb_refs[1];    if (d3d12_refs.NumTexture2Ds) {        d3d12_refs.ppTexture2Ds = av_calloc(d3d12_refs.NumTexture2Ds,                                            sizeof(*d3d12_refs.ppTexture2Ds));        if (!d3d12_refs.ppTexture2Ds) {            err = AVERROR(ENOMEM);            goto fail;        }         i = 0;        for (j = 0; j < base_pic->nb_refs[0]; j++)            d3d12_refs.ppTexture2Ds[i++] = ((D3D12VAEncodePicture *)base_pic->refs[0][j]->priv)->recon_surface->texture;        for (j = 0; j < base_pic->nb_refs[1]; j++)            d3d12_refs.ppTexture2Ds[i++] = ((D3D12VAEncodePicture *)base_pic->refs[1][j]->priv)->recon_surface->texture;    }     input_args.PictureControlDesc.IntraRefreshFrameIndex  = 0;    if (base_pic->is_reference)        input_args.PictureControlDesc.Flags |= D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE;     input_args.PictureControlDesc.PictureControlCodecData = pic->pic_ctl;    input_args.PictureControlDesc.ReferenceFrames         = d3d12_refs;    input_args.CurrentFrameBitstreamMetadataSize          = pic->aligned_header_size;     output_args.Bitstream.pBuffer                                    = pic->output_buffer;    output_args.Bitstream.FrameStartOffset                           = pic->aligned_header_size;    output_args.ReconstructedPicture.pReconstructedPicture           = pic->recon_surface->texture;    output_args.ReconstructedPicture.ReconstructedPictureSubresource = 0;    output_args.EncoderOutputMetadata.pBuffer                        = pic->encoded_metadata;    output_args.EncoderOutputMetadata.Offset                         = 0;     input_metadata.HWLayoutMetadata.pBuffer = pic->encoded_metadata;    input_metadata.HWLayoutMetadata.Offset  = 0;     output_metadata.ResolvedLayoutMetadata.pBuffer = pic->resolved_metadata;    output_metadata.ResolvedLayoutMetadata.Offset  = 0;     err = d3d12va_get_valid_command_allocator(avctx, &command_allocator);    if (err < 0)        goto fail;     hr = ID3D12CommandAllocator_Reset(command_allocator);    if (FAILED(hr)) {        err = AVERROR_UNKNOWN;        goto fail;    }     hr = ID3D12VideoEncodeCommandList2_Reset(cmd_list, command_allocator);    if (FAILED(hr)) {        err = AVERROR_UNKNOWN;        goto fail;    } #define TRANSITION_BARRIER(res, before, after)                      \    (D3D12_RESOURCE_BARRIER) {                                      \        .Type  = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,            \        .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,                  \        .Transition = {                                             \            .pResource   = res,                                     \            .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, \            .StateBefore = before,                                  \            .StateAfter  = after,                                   \        },                                                          \    }     barriers[0] = TRANSITION_BARRIER(pic->input_surface->texture,                                     D3D12_RESOURCE_STATE_COMMON,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ);    barriers[1] = TRANSITION_BARRIER(pic->output_buffer,                                     D3D12_RESOURCE_STATE_COMMON,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE);    barriers[2] = TRANSITION_BARRIER(pic->recon_surface->texture,                                     D3D12_RESOURCE_STATE_COMMON,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE);    barriers[3] = TRANSITION_BARRIER(pic->encoded_metadata,                                     D3D12_RESOURCE_STATE_COMMON,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE);    barriers[4] = TRANSITION_BARRIER(pic->resolved_metadata,                                     D3D12_RESOURCE_STATE_COMMON,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE);     ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, 5, barriers);     if (d3d12_refs.NumTexture2Ds) {        D3D12_RESOURCE_BARRIER refs_barriers[3];         for (i = 0; i < d3d12_refs.NumTexture2Ds; i++)            refs_barriers[i] = TRANSITION_BARRIER(d3d12_refs.ppTexture2Ds[i],                                                  D3D12_RESOURCE_STATE_COMMON,                                                  D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ);         ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, d3d12_refs.NumTexture2Ds,                                                      refs_barriers);    }     ID3D12VideoEncodeCommandList2_EncodeFrame(cmd_list, ctx->encoder, ctx->encoder_heap,                                              &input_args, &output_args);     barriers[3] = TRANSITION_BARRIER(pic->encoded_metadata,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ);     ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, 1, &barriers[3]);     ID3D12VideoEncodeCommandList2_ResolveEncoderOutputMetadata(cmd_list, &input_metadata, &output_metadata);     if (d3d12_refs.NumTexture2Ds) {        D3D12_RESOURCE_BARRIER refs_barriers[3];         for (i = 0; i < d3d12_refs.NumTexture2Ds; i++)            refs_barriers[i] = TRANSITION_BARRIER(d3d12_refs.ppTexture2Ds[i],                                                  D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ,                                                  D3D12_RESOURCE_STATE_COMMON);         ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, d3d12_refs.NumTexture2Ds,                                                      refs_barriers);    }     barriers[0] = TRANSITION_BARRIER(pic->input_surface->texture,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ,                                     D3D12_RESOURCE_STATE_COMMON);    barriers[1] = TRANSITION_BARRIER(pic->output_buffer,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE,                                     D3D12_RESOURCE_STATE_COMMON);    barriers[2] = TRANSITION_BARRIER(pic->recon_surface->texture,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE,                                     D3D12_RESOURCE_STATE_COMMON);    barriers[3] = TRANSITION_BARRIER(pic->encoded_metadata,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ,                                     D3D12_RESOURCE_STATE_COMMON);    barriers[4] = TRANSITION_BARRIER(pic->resolved_metadata,                                     D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE,                                     D3D12_RESOURCE_STATE_COMMON);     ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, 5, barriers);     hr = ID3D12VideoEncodeCommandList2_Close(cmd_list);    if (FAILED(hr)) {        err = AVERROR_UNKNOWN;        goto fail;    }     hr = ID3D12CommandQueue_Wait(ctx->command_queue, pic->input_surface->sync_ctx.fence,                                 pic->input_surface->sync_ctx.fence_value);    if (FAILED(hr)) {        err = AVERROR_UNKNOWN;        goto fail;    }     ID3D12CommandQueue_ExecuteCommandLists(ctx->command_queue, 1, (ID3D12CommandList **)&ctx->command_list);     hr = ID3D12CommandQueue_Signal(ctx->command_queue, pic->input_surface->sync_ctx.fence,                                   ++pic->input_surface->sync_ctx.fence_value);    if (FAILED(hr)) {        err = AVERROR_UNKNOWN;        goto fail;    }     hr = ID3D12CommandQueue_Signal(ctx->command_queue, ctx->sync_ctx.fence, ++ctx->sync_ctx.fence_value);    if (FAILED(hr)) {        err = AVERROR_UNKNOWN;        goto fail;    }     err = d3d12va_discard_command_allocator(avctx, command_allocator, ctx->sync_ctx.fence_value);    if (err < 0)        goto fail;     pic->fence_value = ctx->sync_ctx.fence_value;     if (d3d12_refs.ppTexture2Ds)        av_freep(&d3d12_refs.ppTexture2Ds);     return 0; fail:    if (command_allocator)        d3d12va_discard_command_allocator(avctx, command_allocator, ctx->sync_ctx.fence_value);     if (d3d12_refs.ppTexture2Ds)        av_freep(&d3d12_refs.ppTexture2Ds); 

Lines 1–500 of 1557.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t