/* * 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. Load more
/* * 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);