| /* |
| * 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 }; |
| int barriers_ref_index = 0; |
| D3D12_RESOURCE_BARRIER *barriers_ref = NULL; |
| |
| 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->subresource_index = ctx->is_texture_array ? pic->recon_surface->subresource_index : 0; |
| |
| 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; |
| } |
| |
| if (ctx->is_texture_array) { |
| d3d12_refs.pSubresources = av_calloc(d3d12_refs.NumTexture2Ds, |
| sizeof(*d3d12_refs.pSubresources)); |
| if (!d3d12_refs.pSubresources) { |
| 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; |
| if (ctx->is_texture_array) |
| d3d12_refs.pSubresources[i] = ((D3D12VAEncodePicture *)base_pic->refs[0][j]->priv)->subresource_index; |
| i++; |
| } |
| for (j = 0; j < base_pic->nb_refs[1]; j++) { |
| d3d12_refs.ppTexture2Ds[i] = ((D3D12VAEncodePicture *)base_pic->refs[1][j]->priv)->recon_surface->texture; |
| if (ctx->is_texture_array) |
| d3d12_refs.pSubresources[i] = ((D3D12VAEncodePicture *)base_pic->refs[1][j]->priv)->subresource_index; |
| i++; |
| } |
| } |
| |
| 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 = ctx->is_texture_array ? pic->subresource_index : 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, subres, before, after) \ |
| (D3D12_RESOURCE_BARRIER) { \ |
| .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, \ |
| .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, \ |
| .Transition = { \ |
| .pResource = res, \ |
| .Subresource = subres, \ |
| .StateBefore = before, \ |
| .StateAfter = after, \ |
| }, \ |
| } |
| |
| barriers[0] = TRANSITION_BARRIER(pic->input_surface->texture, |
| D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, |
| D3D12_RESOURCE_STATE_COMMON, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ); |
| barriers[1] = TRANSITION_BARRIER(pic->output_buffer, |
| D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, |
| D3D12_RESOURCE_STATE_COMMON, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE); |
| barriers[2] = TRANSITION_BARRIER(pic->encoded_metadata, |
| D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, |
| D3D12_RESOURCE_STATE_COMMON, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE); |
| barriers[3] = TRANSITION_BARRIER(pic->resolved_metadata, |
| D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, |
| D3D12_RESOURCE_STATE_COMMON, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE); |
| |
| ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, 4, barriers); |
| |
| if (ctx->is_texture_array) |
| barriers_ref = av_calloc(base_ctx->recon_frames->initial_pool_size * ctx->plane_count, |
| sizeof(D3D12_RESOURCE_BARRIER)); |
| else |
| barriers_ref = av_calloc(MAX_DPB_SIZE, sizeof(D3D12_RESOURCE_BARRIER)); |
| |
| if (ctx->is_texture_array) { |
| D3D12_RESOURCE_DESC references_tex_array_desc = { 0 }; |
| pic->recon_surface->texture->lpVtbl->GetDesc(pic->recon_surface->texture, &references_tex_array_desc); |
| |
| for (uint32_t reference_subresource = 0; reference_subresource < references_tex_array_desc.DepthOrArraySize; |
| reference_subresource++) { |
| |
| uint32_t array_size = references_tex_array_desc.DepthOrArraySize; |
| uint32_t mip_slice = reference_subresource % references_tex_array_desc.MipLevels; |
| uint32_t array_slice = (reference_subresource / references_tex_array_desc.MipLevels) % array_size; |
| |
| for (uint32_t plane_slice = 0; plane_slice < ctx->plane_count; plane_slice++) { |
| uint32_t outputSubresource = mip_slice + array_slice * references_tex_array_desc.MipLevels + |
| plane_slice * references_tex_array_desc.MipLevels * array_size; |
| if (reference_subresource == pic->subresource_index) { |
| barriers_ref[barriers_ref_index++] = TRANSITION_BARRIER(pic->recon_surface->texture, outputSubresource, |
| D3D12_RESOURCE_STATE_COMMON, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE); |
| } else { |
| barriers_ref[barriers_ref_index++] = TRANSITION_BARRIER(pic->recon_surface->texture, outputSubresource, |
| D3D12_RESOURCE_STATE_COMMON, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ); |
| } |
| } |
| } |
| } else { |
| barriers_ref[barriers_ref_index++] = TRANSITION_BARRIER(pic->recon_surface->texture, |
| D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, |
| D3D12_RESOURCE_STATE_COMMON, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE); |
| |
| if (d3d12_refs.NumTexture2Ds) { |
| for (i = 0; i < d3d12_refs.NumTexture2Ds; i++) |
| barriers_ref[barriers_ref_index++] = TRANSITION_BARRIER(d3d12_refs.ppTexture2Ds[i], |
| D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, |
| D3D12_RESOURCE_STATE_COMMON, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ); |
| } |
| } |
| ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, barriers_ref_index, barriers_ref); |
| |
| ID3D12VideoEncodeCommandList2_EncodeFrame(cmd_list, ctx->encoder, ctx->encoder_heap, |
| &input_args, &output_args); |
| |
| barriers[3] = TRANSITION_BARRIER(pic->encoded_metadata, |
| D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, |
| 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 (barriers_ref_index > 0) { |
| for (i = 0; i < barriers_ref_index; i++) |
| FFSWAP(D3D12_RESOURCE_STATES, barriers_ref[i].Transition.StateBefore, barriers_ref[i].Transition.StateAfter); |
| |
| ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, barriers_ref_index, |
| barriers_ref); |
| } |
| |
| barriers[0] = TRANSITION_BARRIER(pic->input_surface->texture, |
| D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ, |
| D3D12_RESOURCE_STATE_COMMON); |
| barriers[1] = TRANSITION_BARRIER(pic->output_buffer, |
| D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE, |
| D3D12_RESOURCE_STATE_COMMON); |
| barriers[2] = TRANSITION_BARRIER(pic->encoded_metadata, |
| D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ, |
| D3D12_RESOURCE_STATE_COMMON); |
| barriers[3] = TRANSITION_BARRIER(pic->resolved_metadata, |
| D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, |
| D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE, |
| D3D12_RESOURCE_STATE_COMMON); |
| |
| ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, 4, 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); |
| |
| if (ctx->is_texture_array && d3d12_refs.pSubresources) |
| av_freep(&d3d12_refs.pSubresources); |
| |
| if (barriers_ref) |
| av_freep(&barriers_ref); |
| |
| 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); |
| |
| if (ctx->is_texture_array && d3d12_refs.pSubresources) |
| av_freep(&d3d12_refs.pSubresources); |
| |
| if (barriers_ref) |
| av_freep(&barriers_ref); |
| |
| if (ctx->codec->free_picture_params) |
| ctx->codec->free_picture_params(pic); |
| |
| av_buffer_unref(&pic->output_buffer_ref); |
| pic->output_buffer = NULL; |
| D3D12_OBJECT_RELEASE(pic->encoded_metadata); |
| D3D12_OBJECT_RELEASE(pic->resolved_metadata); |
| return err; |
| } |
| |
| static int d3d12va_encode_discard(AVCodecContext *avctx, |
| FFHWBaseEncodePicture *base_pic) |
| { |
| D3D12VAEncodePicture *pic = base_pic->priv; |
| |
| d3d12va_encode_wait(avctx, base_pic); |
| |
| if (pic->output_buffer_ref) { |
| av_log(avctx, AV_LOG_DEBUG, "Discard output for pic " |
| "%"PRId64"/%"PRId64".\n", |
| base_pic->display_order, base_pic->encode_order); |
| |
| av_buffer_unref(&pic->output_buffer_ref); |
| pic->output_buffer = NULL; |
| } |
| |
| D3D12_OBJECT_RELEASE(pic->encoded_metadata); |
| D3D12_OBJECT_RELEASE(pic->resolved_metadata); |
| |
| return 0; |
| } |
| |
| static int d3d12va_encode_free_rc_params(AVCodecContext *avctx) |
| { |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| |
| switch (ctx->rc.Mode) |
| { |
| case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP: |
| av_freep(&ctx->rc.ConfigParams.pConfiguration_CQP); |
| break; |
| case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: |
| av_freep(&ctx->rc.ConfigParams.pConfiguration_CBR); |
| break; |
| case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: |
| av_freep(&ctx->rc.ConfigParams.pConfiguration_VBR); |
| break; |
| case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: |
| av_freep(&ctx->rc.ConfigParams.pConfiguration_QVBR); |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int d3d12va_encode_init(AVCodecContext *avctx, FFHWBaseEncodePicture *pic) |
| { |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| D3D12VAEncodePicture *priv = pic->priv; |
| AVFrame *frame = pic->input_image; |
| |
| if (ctx->codec->picture_priv_data_size > 0) { |
| pic->codec_priv = av_mallocz(ctx->codec->picture_priv_data_size); |
| if (!pic->codec_priv) |
| return AVERROR(ENOMEM); |
| } |
| |
| priv->input_surface = (AVD3D12VAFrame *)frame->data[0]; |
| |
| return 0; |
| } |
| |
| static int d3d12va_encode_free(AVCodecContext *avctx, FFHWBaseEncodePicture *pic) |
| { |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| D3D12VAEncodePicture *priv = pic->priv; |
| |
| if (pic->encode_issued) |
| d3d12va_encode_discard(avctx, pic); |
| |
| if (ctx->codec->free_picture_params) |
| ctx->codec->free_picture_params(priv); |
| |
| return 0; |
| } |
| |
| static int d3d12va_encode_get_buffer_size(AVCodecContext *avctx, |
| D3D12VAEncodePicture *pic, size_t *size) |
| { |
| D3D12_VIDEO_ENCODER_OUTPUT_METADATA *meta = NULL; |
| uint8_t *data; |
| HRESULT hr; |
| int err; |
| |
| hr = ID3D12Resource_Map(pic->resolved_metadata, 0, NULL, (void **)&data); |
| if (FAILED(hr)) { |
| err = AVERROR_UNKNOWN; |
| return err; |
| } |
| |
| meta = (D3D12_VIDEO_ENCODER_OUTPUT_METADATA *)data; |
| |
| if (meta->EncodeErrorFlags != D3D12_VIDEO_ENCODER_ENCODE_ERROR_FLAG_NO_ERROR) { |
| av_log(avctx, AV_LOG_ERROR, "Encode failed %"PRIu64"\n", meta->EncodeErrorFlags); |
| err = AVERROR(EINVAL); |
| return err; |
| } |
| |
| if (meta->EncodedBitstreamWrittenBytesCount == 0) { |
| av_log(avctx, AV_LOG_ERROR, "No bytes were written to encoded bitstream\n"); |
| err = AVERROR(EINVAL); |
| return err; |
| } |
| |
| *size = meta->EncodedBitstreamWrittenBytesCount; |
| |
| ID3D12Resource_Unmap(pic->resolved_metadata, 0, NULL); |
| |
| return 0; |
| } |
| |
| static int d3d12va_encode_get_coded_data(AVCodecContext *avctx, |
| D3D12VAEncodePicture *pic, AVPacket *pkt) |
| { |
| int err; |
| uint8_t *ptr, *mapped_data; |
| size_t total_size = 0; |
| HRESULT hr; |
| |
| err = d3d12va_encode_get_buffer_size(avctx, pic, &total_size); |
| if (err < 0) |
| goto end; |
| |
| total_size += pic->header_size; |
| av_log(avctx, AV_LOG_DEBUG, "Output buffer size %"SIZE_SPECIFIER"\n", total_size); |
| |
| hr = ID3D12Resource_Map(pic->output_buffer, 0, NULL, (void **)&mapped_data); |
| if (FAILED(hr)) { |
| err = AVERROR_UNKNOWN; |
| goto end; |
| } |
| |
| err = ff_get_encode_buffer(avctx, pkt, total_size, 0); |
| if (err < 0) |
| goto end; |
| ptr = pkt->data; |
| |
| memcpy(ptr, mapped_data, pic->header_size); |
| |
| ptr += pic->header_size; |
| mapped_data += pic->aligned_header_size; |
| total_size -= pic->header_size; |
| |
| memcpy(ptr, mapped_data, total_size); |
| |
| ID3D12Resource_Unmap(pic->output_buffer, 0, NULL); |
| |
| end: |
| av_buffer_unref(&pic->output_buffer_ref); |
| pic->output_buffer = NULL; |
| return err; |
| } |
| |
| static int d3d12va_encode_output(AVCodecContext *avctx, |
| FFHWBaseEncodePicture *base_pic, AVPacket *pkt) |
| { |
| FFHWBaseEncodeContext *base_ctx = avctx->priv_data; |
| D3D12VAEncodePicture *pic = base_pic->priv; |
| AVPacket *pkt_ptr = pkt; |
| int err; |
| |
| err = d3d12va_encode_wait(avctx, base_pic); |
| if (err < 0) |
| return err; |
| |
| err = d3d12va_encode_get_coded_data(avctx, pic, pkt); |
| if (err < 0) |
| return err; |
| |
| av_log(avctx, AV_LOG_DEBUG, "Output read for pic %"PRId64"/%"PRId64".\n", |
| base_pic->display_order, base_pic->encode_order); |
| |
| ff_hw_base_encode_set_output_property(base_ctx, avctx, (FFHWBaseEncodePicture *)base_pic, |
| pkt_ptr, 0); |
| |
| return 0; |
| } |
| |
| static int d3d12va_encode_set_profile(AVCodecContext *avctx) |
| { |
| FFHWBaseEncodeContext *base_ctx = avctx->priv_data; |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| const D3D12VAEncodeProfile *profile; |
| const AVPixFmtDescriptor *desc; |
| int i, depth; |
| |
| desc = av_pix_fmt_desc_get(base_ctx->input_frames->sw_format); |
| if (!desc) { |
| av_log(avctx, AV_LOG_ERROR, "Invalid input pixfmt (%d).\n", |
| base_ctx->input_frames->sw_format); |
| return AVERROR(EINVAL); |
| } |
| |
| depth = desc->comp[0].depth; |
| for (i = 1; i < desc->nb_components; i++) { |
| if (desc->comp[i].depth != depth) { |
| av_log(avctx, AV_LOG_ERROR, "Invalid input pixfmt (%s).\n", |
| desc->name); |
| return AVERROR(EINVAL); |
| } |
| } |
| av_log(avctx, AV_LOG_VERBOSE, "Input surface format is %s.\n", |
| desc->name); |
| |
| av_assert0(ctx->codec->profiles); |
| for (i = 0; (ctx->codec->profiles[i].av_profile != |
| AV_PROFILE_UNKNOWN); i++) { |
| profile = &ctx->codec->profiles[i]; |
| if (depth != profile->depth || |
| desc->nb_components != profile->nb_components) |
| continue; |
| if (desc->nb_components > 1 && |
| (desc->log2_chroma_w != profile->log2_chroma_w || |
| desc->log2_chroma_h != profile->log2_chroma_h)) |
| continue; |
| if (avctx->profile != profile->av_profile && |
| avctx->profile != AV_PROFILE_UNKNOWN) |
| continue; |
| |
| ctx->profile = profile; |
| break; |
| } |
| if (!ctx->profile) { |
| av_log(avctx, AV_LOG_ERROR, "No usable encoding profile found.\n"); |
| return AVERROR(ENOSYS); |
| } |
| |
| avctx->profile = profile->av_profile; |
| return 0; |
| } |
| |
| static const D3D12VAEncodeRCMode d3d12va_encode_rc_modes[] = { |
| // Bitrate Quality |
| // | Maxrate | HRD/VBV |
| { 0 }, // | | | | |
| { RC_MODE_CQP, "CQP", 0, 0, 1, 0, D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP }, |
| { RC_MODE_CBR, "CBR", 1, 0, 0, 1, D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR }, |
| { RC_MODE_VBR, "VBR", 1, 1, 0, 1, D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR }, |
| { RC_MODE_QVBR, "QVBR", 1, 1, 1, 1, D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR }, |
| }; |
| |
| static int check_rate_control_support(AVCodecContext *avctx, const D3D12VAEncodeRCMode *rc_mode) |
| { |
| HRESULT hr; |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| D3D12_FEATURE_DATA_VIDEO_ENCODER_RATE_CONTROL_MODE d3d12_rc_mode = { |
| .Codec = ctx->codec->d3d12_codec, |
| }; |
| |
| if (!rc_mode->d3d12_mode) |
| return 0; |
| |
| d3d12_rc_mode.IsSupported = 0; |
| d3d12_rc_mode.RateControlMode = rc_mode->d3d12_mode; |
| |
| hr = ID3D12VideoDevice3_CheckFeatureSupport(ctx->video_device3, |
| D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE, |
| &d3d12_rc_mode, sizeof(d3d12_rc_mode)); |
| if (FAILED(hr)) { |
| av_log(avctx, AV_LOG_ERROR, "Failed to check rate control support.\n"); |
| return 0; |
| } |
| |
| return d3d12_rc_mode.IsSupported; |
| } |
| |
| static int d3d12va_encode_init_rate_control(AVCodecContext *avctx) |
| { |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| int64_t rc_target_bitrate; |
| int64_t rc_peak_bitrate; |
| int rc_quality; |
| int64_t hrd_buffer_size; |
| int64_t hrd_initial_buffer_fullness; |
| int fr_num, fr_den; |
| const D3D12VAEncodeRCMode *rc_mode; |
| |
| // Rate control mode selection: |
| // * If the user has set a mode explicitly with the rc_mode option, |
| // use it and fail if it is not available. |
| // * If an explicit QP option has been set, use CQP. |
| // * If the codec is CQ-only, use CQP. |
| // * If the QSCALE avcodec option is set, use CQP. |
| // * If bitrate and quality are both set, try QVBR. |
| // * If quality is set, try CQP. |
| // * If bitrate and maxrate are set and have the same value, try CBR. |
| // * If a bitrate is set, try VBR, then CBR. |
| // * If no bitrate is set, try CQP. |
| |
| #define TRY_RC_MODE(mode, fail) do { \ |
| rc_mode = &d3d12va_encode_rc_modes[mode]; \ |
| if (!(rc_mode->d3d12_mode && check_rate_control_support(avctx, rc_mode))) { \ |
| if (fail) { \ |
| av_log(avctx, AV_LOG_ERROR, "Driver does not support %s " \ |
| "RC mode.\n", rc_mode->name); \ |
| return AVERROR(EINVAL); \ |
| } \ |
| av_log(avctx, AV_LOG_DEBUG, "Driver does not support %s " \ |
| "RC mode.\n", rc_mode->name); \ |
| rc_mode = NULL; \ |
| } else { \ |
| goto rc_mode_found; \ |
| } \ |
| } while (0) |
| |
| if (ctx->explicit_rc_mode) |
| TRY_RC_MODE(ctx->explicit_rc_mode, 1); |
| |
| if (ctx->explicit_qp) |
| TRY_RC_MODE(RC_MODE_CQP, 1); |
| |
| if (ctx->codec->flags & FF_HW_FLAG_CONSTANT_QUALITY_ONLY) |
| TRY_RC_MODE(RC_MODE_CQP, 1); |
| |
| if (avctx->flags & AV_CODEC_FLAG_QSCALE) |
| TRY_RC_MODE(RC_MODE_CQP, 1); |
| |
| if (avctx->bit_rate > 0 && avctx->global_quality > 0) |
| TRY_RC_MODE(RC_MODE_QVBR, 0); |
| |
| if (avctx->global_quality > 0) { |
| TRY_RC_MODE(RC_MODE_CQP, 0); |
| } |
| |
| if (avctx->bit_rate > 0 && avctx->rc_max_rate == avctx->bit_rate) |
| TRY_RC_MODE(RC_MODE_CBR, 0); |
| |
| if (avctx->bit_rate > 0) { |
| TRY_RC_MODE(RC_MODE_VBR, 0); |
| TRY_RC_MODE(RC_MODE_CBR, 0); |
| } else { |
| TRY_RC_MODE(RC_MODE_CQP, 0); |
| } |
| |
| av_log(avctx, AV_LOG_ERROR, "Driver does not support any " |
| "RC mode compatible with selected options.\n"); |
| return AVERROR(EINVAL); |
| |
| rc_mode_found: |
| if (rc_mode->bitrate) { |
| if (avctx->bit_rate <= 0) { |
| av_log(avctx, AV_LOG_ERROR, "Bitrate must be set for %s " |
| "RC mode.\n", rc_mode->name); |
| return AVERROR(EINVAL); |
| } |
| |
| if (rc_mode->maxrate) { |
| if (avctx->rc_max_rate > 0) { |
| if (avctx->rc_max_rate < avctx->bit_rate) { |
| av_log(avctx, AV_LOG_ERROR, "Invalid bitrate settings: " |
| "bitrate (%"PRId64") must not be greater than " |
| "maxrate (%"PRId64").\n", avctx->bit_rate, |
| avctx->rc_max_rate); |
| return AVERROR(EINVAL); |
| } |
| rc_target_bitrate = avctx->bit_rate; |
| rc_peak_bitrate = avctx->rc_max_rate; |
| } else { |
| // We only have a target bitrate, but this mode requires |
| // that a maximum rate be supplied as well. Since the |
| // user does not want this to be a constraint, arbitrarily |
| // pick a maximum rate of double the target rate. |
| rc_target_bitrate = avctx->bit_rate; |
| rc_peak_bitrate = 2 * avctx->bit_rate; |
| } |
| } else { |
| if (avctx->rc_max_rate > avctx->bit_rate) { |
| av_log(avctx, AV_LOG_WARNING, "Max bitrate is ignored " |
| "in %s RC mode.\n", rc_mode->name); |
| } |
| rc_target_bitrate = avctx->bit_rate; |
| rc_peak_bitrate = 0; |
| } |
| } else { |
| rc_target_bitrate = 0; |
| rc_peak_bitrate = 0; |
| } |
| |
| if (rc_mode->quality) { |
| if (ctx->explicit_qp) { |
| rc_quality = ctx->explicit_qp; |
| } else if (avctx->global_quality > 0) { |
| if (avctx->flags & AV_CODEC_FLAG_QSCALE) |
| rc_quality = avctx->global_quality / FF_QP2LAMBDA; |
| else |
| rc_quality = avctx->global_quality; |
| } else { |
| rc_quality = ctx->codec->default_quality; |
| av_log(avctx, AV_LOG_WARNING, "No quality level set; " |
| "using default (%d).\n", rc_quality); |
| } |
| } else { |
| rc_quality = 0; |
| } |
| |
| if (rc_mode->hrd) { |
| if (avctx->rc_buffer_size) |
| hrd_buffer_size = avctx->rc_buffer_size; |
| else if (avctx->rc_max_rate > 0) |
| hrd_buffer_size = avctx->rc_max_rate; |
| else |
| hrd_buffer_size = avctx->bit_rate; |
| if (avctx->rc_initial_buffer_occupancy) { |
| if (avctx->rc_initial_buffer_occupancy > hrd_buffer_size) { |
| av_log(avctx, AV_LOG_ERROR, "Invalid RC buffer settings: " |
| "must have initial buffer size (%d) <= " |
| "buffer size (%"PRId64").\n", |
| avctx->rc_initial_buffer_occupancy, hrd_buffer_size); |
| return AVERROR(EINVAL); |
| } |
| hrd_initial_buffer_fullness = avctx->rc_initial_buffer_occupancy; |
| } else { |
| hrd_initial_buffer_fullness = hrd_buffer_size * 3 / 4; |
| } |
| } else { |
| if (avctx->rc_buffer_size || avctx->rc_initial_buffer_occupancy) { |
| av_log(avctx, AV_LOG_WARNING, "Buffering settings are ignored " |
| "in %s RC mode.\n", rc_mode->name); |
| } |
| |
| hrd_buffer_size = 0; |
| hrd_initial_buffer_fullness = 0; |
| } |
| |
| if (rc_target_bitrate > UINT32_MAX || |
| hrd_buffer_size > UINT32_MAX || |
| hrd_initial_buffer_fullness > UINT32_MAX) { |
| av_log(avctx, AV_LOG_ERROR, "RC parameters of 2^32 or " |
| "greater are not supported by D3D12.\n"); |
| return AVERROR(EINVAL); |
| } |
| |
| ctx->rc_quality = rc_quality; |
| |
| av_log(avctx, AV_LOG_VERBOSE, "RC mode: %s.\n", rc_mode->name); |
| |
| if (rc_mode->quality) |
| av_log(avctx, AV_LOG_VERBOSE, "RC quality: %d.\n", rc_quality); |
| |
| if (rc_mode->hrd) { |
| av_log(avctx, AV_LOG_VERBOSE, "RC buffer: %"PRId64" bits, " |
| "initial fullness %"PRId64" bits.\n", |
| hrd_buffer_size, hrd_initial_buffer_fullness); |
| } |
| |
| if (avctx->framerate.num > 0 && avctx->framerate.den > 0) |
| av_reduce(&fr_num, &fr_den, |
| avctx->framerate.num, avctx->framerate.den, 65535); |
| else |
| av_reduce(&fr_num, &fr_den, |
| avctx->time_base.den, avctx->time_base.num, 65535); |
| |
| av_log(avctx, AV_LOG_VERBOSE, "RC framerate: %d/%d (%.2f fps).\n", |
| fr_num, fr_den, (double)fr_num / fr_den); |
| |
| ctx->rc.Flags = D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_NONE; |
| ctx->rc.TargetFrameRate.Numerator = fr_num; |
| ctx->rc.TargetFrameRate.Denominator = fr_den; |
| ctx->rc.Mode = rc_mode->d3d12_mode; |
| |
| switch (rc_mode->mode) { |
| case RC_MODE_CQP: |
| // cqp ConfigParams will be updated in ctx->codec->configure. |
| break; |
| case RC_MODE_CBR: { |
| D3D12_VIDEO_ENCODER_RATE_CONTROL_CBR *cbr_ctl; |
| |
| ctx->rc.ConfigParams.DataSize = sizeof(D3D12_VIDEO_ENCODER_RATE_CONTROL_CBR); |
| cbr_ctl = av_mallocz(ctx->rc.ConfigParams.DataSize); |
| if (!cbr_ctl) |
| return AVERROR(ENOMEM); |
| |
| cbr_ctl->TargetBitRate = rc_target_bitrate; |
| cbr_ctl->VBVCapacity = hrd_buffer_size; |
| cbr_ctl->InitialVBVFullness = hrd_initial_buffer_fullness; |
| ctx->rc.Flags |= D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_VBV_SIZES; |
| |
| if (avctx->qmin > 0 || avctx->qmax > 0) { |
| cbr_ctl->MinQP = avctx->qmin; |
| cbr_ctl->MaxQP = avctx->qmax; |
| ctx->rc.Flags |= D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_QP_RANGE; |
| } |
| |
| if (ctx->max_frame_size > 0) { |
| cbr_ctl->MaxFrameBitSize = ctx->max_frame_size * 8; |
| ctx->rc.Flags |= D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_MAX_FRAME_SIZE; |
| } |
| |
| ctx->rc.ConfigParams.pConfiguration_CBR = cbr_ctl; |
| break; |
| } |
| case RC_MODE_VBR: { |
| D3D12_VIDEO_ENCODER_RATE_CONTROL_VBR *vbr_ctl; |
| |
| ctx->rc.ConfigParams.DataSize = sizeof(D3D12_VIDEO_ENCODER_RATE_CONTROL_VBR); |
| vbr_ctl = av_mallocz(ctx->rc.ConfigParams.DataSize); |
| if (!vbr_ctl) |
| return AVERROR(ENOMEM); |
| |
| vbr_ctl->TargetAvgBitRate = rc_target_bitrate; |
| vbr_ctl->PeakBitRate = rc_peak_bitrate; |
| vbr_ctl->VBVCapacity = hrd_buffer_size; |
| vbr_ctl->InitialVBVFullness = hrd_initial_buffer_fullness; |
| ctx->rc.Flags |= D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_VBV_SIZES; |
| |
| if (avctx->qmin > 0 || avctx->qmax > 0) { |
| vbr_ctl->MinQP = avctx->qmin; |
| vbr_ctl->MaxQP = avctx->qmax; |
| ctx->rc.Flags |= D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_QP_RANGE; |
| } |
| |
| if (ctx->max_frame_size > 0) { |
| vbr_ctl->MaxFrameBitSize = ctx->max_frame_size * 8; |
| ctx->rc.Flags |= D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_MAX_FRAME_SIZE; |
| } |
| |
| ctx->rc.ConfigParams.pConfiguration_VBR = vbr_ctl; |
| break; |
| } |
| case RC_MODE_QVBR: { |
| D3D12_VIDEO_ENCODER_RATE_CONTROL_QVBR *qvbr_ctl; |
| |
| ctx->rc.ConfigParams.DataSize = sizeof(D3D12_VIDEO_ENCODER_RATE_CONTROL_QVBR); |
| qvbr_ctl = av_mallocz(ctx->rc.ConfigParams.DataSize); |
| if (!qvbr_ctl) |
| return AVERROR(ENOMEM); |
| |
| qvbr_ctl->TargetAvgBitRate = rc_target_bitrate; |
| qvbr_ctl->PeakBitRate = rc_peak_bitrate; |
| qvbr_ctl->ConstantQualityTarget = rc_quality; |
| |
| if (avctx->qmin > 0 || avctx->qmax > 0) { |
| qvbr_ctl->MinQP = avctx->qmin; |
| qvbr_ctl->MaxQP = avctx->qmax; |
| ctx->rc.Flags |= D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_QP_RANGE; |
| } |
| |
| if (ctx->max_frame_size > 0) { |
| qvbr_ctl->MaxFrameBitSize = ctx->max_frame_size * 8; |
| ctx->rc.Flags |= D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_MAX_FRAME_SIZE; |
| } |
| |
| ctx->rc.ConfigParams.pConfiguration_QVBR = qvbr_ctl; |
| break; |
| } |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static int d3d12va_encode_init_gop_structure(AVCodecContext *avctx) |
| { |
| FFHWBaseEncodeContext *base_ctx = avctx->priv_data; |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| uint32_t ref_l0, ref_l1; |
| int err; |
| HRESULT hr; |
| D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT support; |
| union { |
| D3D12_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT_H264 h264; |
| D3D12_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT_HEVC hevc; |
| } codec_support; |
| |
| support.NodeIndex = 0; |
| support.Codec = ctx->codec->d3d12_codec; |
| support.Profile = ctx->profile->d3d12_profile; |
| |
| switch (ctx->codec->d3d12_codec) { |
| case D3D12_VIDEO_ENCODER_CODEC_H264: |
| support.PictureSupport.DataSize = sizeof(codec_support.h264); |
| support.PictureSupport.pH264Support = &codec_support.h264; |
| break; |
| |
| case D3D12_VIDEO_ENCODER_CODEC_HEVC: |
| support.PictureSupport.DataSize = sizeof(codec_support.hevc); |
| support.PictureSupport.pHEVCSupport = &codec_support.hevc; |
| break; |
| |
| default: |
| av_assert0(0); |
| } |
| |
| hr = ID3D12VideoDevice3_CheckFeatureSupport(ctx->video_device3, D3D12_FEATURE_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT, |
| &support, sizeof(support)); |
| if (FAILED(hr)) |
| return AVERROR(EINVAL); |
| |
| if (support.IsSupported) { |
| switch (ctx->codec->d3d12_codec) { |
| case D3D12_VIDEO_ENCODER_CODEC_H264: |
| ref_l0 = FFMIN(support.PictureSupport.pH264Support->MaxL0ReferencesForP, |
| support.PictureSupport.pH264Support->MaxL1ReferencesForB ? |
| support.PictureSupport.pH264Support->MaxL1ReferencesForB : UINT_MAX); |
| ref_l1 = support.PictureSupport.pH264Support->MaxL1ReferencesForB; |
| break; |
| |
| case D3D12_VIDEO_ENCODER_CODEC_HEVC: |
| ref_l0 = FFMIN(support.PictureSupport.pHEVCSupport->MaxL0ReferencesForP, |
| support.PictureSupport.pHEVCSupport->MaxL1ReferencesForB ? |
| support.PictureSupport.pHEVCSupport->MaxL1ReferencesForB : UINT_MAX); |
| ref_l1 = support.PictureSupport.pHEVCSupport->MaxL1ReferencesForB; |
| break; |
| |
| default: |
| av_assert0(0); |
| } |
| } else { |
| ref_l0 = ref_l1 = 0; |
| } |
| |
| if (ref_l0 > 0 && ref_l1 > 0 && ctx->bi_not_empty) { |
| base_ctx->p_to_gpb = 1; |
| av_log(avctx, AV_LOG_VERBOSE, "Driver does not support P-frames, " |
| "replacing them with B-frames.\n"); |
| } |
| |
| err = ff_hw_base_init_gop_structure(base_ctx, avctx, ref_l0, ref_l1, ctx->codec->flags, 0); |
| if (err < 0) |
| return err; |
| |
| return 0; |
| } |
| |
| static int d3d12va_create_encoder(AVCodecContext *avctx) |
| { |
| FFHWBaseEncodeContext *base_ctx = avctx->priv_data; |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| AVD3D12VAFramesContext *frames_hwctx = base_ctx->input_frames->hwctx; |
| HRESULT hr; |
| |
| D3D12_VIDEO_ENCODER_DESC desc = { |
| .NodeMask = 0, |
| .Flags = D3D12_VIDEO_ENCODER_FLAG_NONE, |
| .EncodeCodec = ctx->codec->d3d12_codec, |
| .EncodeProfile = ctx->profile->d3d12_profile, |
| .InputFormat = frames_hwctx->format, |
| .CodecConfiguration = ctx->codec_conf, |
| .MaxMotionEstimationPrecision = D3D12_VIDEO_ENCODER_MOTION_ESTIMATION_PRECISION_MODE_MAXIMUM, |
| }; |
| |
| hr = ID3D12VideoDevice3_CreateVideoEncoder(ctx->video_device3, &desc, &IID_ID3D12VideoEncoder, |
| (void **)&ctx->encoder); |
| if (FAILED(hr)) { |
| av_log(avctx, AV_LOG_ERROR, "Failed to create encoder.\n"); |
| return AVERROR(EINVAL); |
| } |
| |
| return 0; |
| } |
| |
| static int d3d12va_create_encoder_heap(AVCodecContext *avctx) |
| { |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| HRESULT hr; |
| |
| D3D12_VIDEO_ENCODER_HEAP_DESC desc = { |
| .NodeMask = 0, |
| .Flags = D3D12_VIDEO_ENCODER_HEAP_FLAG_NONE, |
| .EncodeCodec = ctx->codec->d3d12_codec, |
| .EncodeProfile = ctx->profile->d3d12_profile, |
| .EncodeLevel = ctx->level, |
| .ResolutionsListCount = 1, |
| .pResolutionList = &ctx->resolution, |
| }; |
| |
| hr = ID3D12VideoDevice3_CreateVideoEncoderHeap(ctx->video_device3, &desc, |
| &IID_ID3D12VideoEncoderHeap, (void **)&ctx->encoder_heap); |
| if (FAILED(hr)) { |
| av_log(avctx, AV_LOG_ERROR, "Failed to create encoder heap.\n"); |
| return AVERROR(EINVAL); |
| } |
| |
| return 0; |
| } |
| |
| static void d3d12va_encode_free_buffer(void *opaque, uint8_t *data) |
| { |
| ID3D12Resource *pResource; |
| |
| pResource = (ID3D12Resource *)data; |
| D3D12_OBJECT_RELEASE(pResource); |
| } |
| |
| static AVBufferRef *d3d12va_encode_alloc_output_buffer(void *opaque, size_t size) |
| { |
| AVCodecContext *avctx = opaque; |
| FFHWBaseEncodeContext *base_ctx = avctx->priv_data; |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| ID3D12Resource *pResource = NULL; |
| HRESULT hr; |
| AVBufferRef *ref; |
| D3D12_HEAP_PROPERTIES heap_props; |
| D3D12_HEAP_TYPE heap_type = D3D12_HEAP_TYPE_READBACK; |
| |
| D3D12_RESOURCE_DESC desc = { |
| .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, |
| .Alignment = 0, |
| .Width = FFALIGN(3 * base_ctx->surface_width * base_ctx->surface_height + (1 << 16), |
| D3D12_TEXTURE_DATA_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, |
| }; |
| |
| ctx->hwctx->device->lpVtbl->GetCustomHeapProperties(ctx->hwctx->device, &heap_props, 0, heap_type); |
| |
| hr = ID3D12Device_CreateCommittedResource(ctx->hwctx->device, &heap_props, D3D12_HEAP_FLAG_NONE, |
| &desc, D3D12_RESOURCE_STATE_COMMON, NULL, &IID_ID3D12Resource, |
| (void **)&pResource); |
| |
| if (FAILED(hr)) { |
| av_log(avctx, AV_LOG_ERROR, "Failed to create d3d12 buffer.\n"); |
| return NULL; |
| } |
| |
| ref = av_buffer_create((uint8_t *)(uintptr_t)pResource, |
| sizeof(pResource), |
| &d3d12va_encode_free_buffer, |
| avctx, AV_BUFFER_FLAG_READONLY); |
| if (!ref) { |
| D3D12_OBJECT_RELEASE(pResource); |
| return NULL; |
| } |
| |
| return ref; |
| } |
| |
| static int d3d12va_encode_prepare_output_buffers(AVCodecContext *avctx) |
| { |
| FFHWBaseEncodeContext *base_ctx = avctx->priv_data; |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| AVD3D12VAFramesContext *frames_ctx = base_ctx->input_frames->hwctx; |
| HRESULT hr; |
| |
| ctx->req.NodeIndex = 0; |
| ctx->req.Codec = ctx->codec->d3d12_codec; |
| ctx->req.Profile = ctx->profile->d3d12_profile; |
| ctx->req.InputFormat = frames_ctx->format; |
| ctx->req.PictureTargetResolution = ctx->resolution; |
| |
| hr = ID3D12VideoDevice3_CheckFeatureSupport(ctx->video_device3, |
| D3D12_FEATURE_VIDEO_ENCODER_RESOURCE_REQUIREMENTS, |
| &ctx->req, sizeof(ctx->req)); |
| if (FAILED(hr)) { |
| av_log(avctx, AV_LOG_ERROR, "Failed to check encoder resource requirements support.\n"); |
| return AVERROR(EINVAL); |
| } |
| |
| if (!ctx->req.IsSupported) { |
| av_log(avctx, AV_LOG_ERROR, "Encoder resource requirements unsupported.\n"); |
| return AVERROR(EINVAL); |
| } |
| |
| ctx->output_buffer_pool = av_buffer_pool_init2(sizeof(ID3D12Resource *), avctx, |
| &d3d12va_encode_alloc_output_buffer, NULL); |
| if (!ctx->output_buffer_pool) |
| return AVERROR(ENOMEM); |
| |
| return 0; |
| } |
| |
| static int d3d12va_encode_create_command_objects(AVCodecContext *avctx) |
| { |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| ID3D12CommandAllocator *command_allocator = NULL; |
| int err = AVERROR_UNKNOWN; |
| HRESULT hr; |
| |
| D3D12_COMMAND_QUEUE_DESC queue_desc = { |
| .Type = D3D12_COMMAND_LIST_TYPE_VIDEO_ENCODE, |
| .Priority = 0, |
| .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, |
| .NodeMask = 0, |
| }; |
| |
| ctx->allocator_queue = av_fifo_alloc2(D3D12VA_VIDEO_ENC_ASYNC_DEPTH, |
| sizeof(CommandAllocator), AV_FIFO_FLAG_AUTO_GROW); |
| if (!ctx->allocator_queue) |
| return AVERROR(ENOMEM); |
| |
| hr = ID3D12Device_CreateFence(ctx->hwctx->device, 0, D3D12_FENCE_FLAG_NONE, |
| &IID_ID3D12Fence, (void **)&ctx->sync_ctx.fence); |
| if (FAILED(hr)) { |
| av_log(avctx, AV_LOG_ERROR, "Failed to create fence(%lx)\n", (long)hr); |
| err = AVERROR_UNKNOWN; |
| goto fail; |
| } |
| |
| ctx->sync_ctx.event = CreateEvent(NULL, FALSE, FALSE, NULL); |
| if (!ctx->sync_ctx.event) |
| goto fail; |
| |
| err = d3d12va_get_valid_command_allocator(avctx, &command_allocator); |
| if (err < 0) |
| goto fail; |
| |
| hr = ID3D12Device_CreateCommandQueue(ctx->hwctx->device, &queue_desc, |
| &IID_ID3D12CommandQueue, (void **)&ctx->command_queue); |
| if (FAILED(hr)) { |
| av_log(avctx, AV_LOG_ERROR, "Failed to create command queue(%lx)\n", (long)hr); |
| err = AVERROR_UNKNOWN; |
| goto fail; |
| } |
| |
| hr = ID3D12Device_CreateCommandList(ctx->hwctx->device, 0, queue_desc.Type, |
| command_allocator, NULL, &IID_ID3D12CommandList, |
| (void **)&ctx->command_list); |
| if (FAILED(hr)) { |
| av_log(avctx, AV_LOG_ERROR, "Failed to create command list(%lx)\n", (long)hr); |
| err = AVERROR_UNKNOWN; |
| goto fail; |
| } |
| |
| hr = ID3D12VideoEncodeCommandList2_Close(ctx->command_list); |
| if (FAILED(hr)) { |
| av_log(avctx, AV_LOG_ERROR, "Failed to close the command list(%lx)\n", (long)hr); |
| err = AVERROR_UNKNOWN; |
| goto fail; |
| } |
| |
| ID3D12CommandQueue_ExecuteCommandLists(ctx->command_queue, 1, (ID3D12CommandList **)&ctx->command_list); |
| |
| err = d3d12va_sync_with_gpu(avctx); |
| if (err < 0) |
| goto fail; |
| |
| err = d3d12va_discard_command_allocator(avctx, command_allocator, ctx->sync_ctx.fence_value); |
| if (err < 0) |
| goto fail; |
| |
| return 0; |
| |
| fail: |
| D3D12_OBJECT_RELEASE(command_allocator); |
| return err; |
| } |
| |
| static int d3d12va_encode_create_recon_frames(AVCodecContext *avctx) |
| { |
| FFHWBaseEncodeContext *base_ctx = avctx->priv_data; |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| AVD3D12VAFramesContext *hwctx; |
| enum AVPixelFormat recon_format; |
| int err; |
| |
| err = ff_hw_base_get_recon_format(base_ctx, NULL, &recon_format); |
| if (err < 0) |
| return err; |
| |
| base_ctx->recon_frames_ref = av_hwframe_ctx_alloc(base_ctx->device_ref); |
| if (!base_ctx->recon_frames_ref) |
| return AVERROR(ENOMEM); |
| |
| base_ctx->recon_frames = (AVHWFramesContext *)base_ctx->recon_frames_ref->data; |
| hwctx = (AVD3D12VAFramesContext *)base_ctx->recon_frames->hwctx; |
| |
| base_ctx->recon_frames->format = AV_PIX_FMT_D3D12; |
| base_ctx->recon_frames->sw_format = recon_format; |
| base_ctx->recon_frames->width = base_ctx->surface_width; |
| base_ctx->recon_frames->height = base_ctx->surface_height; |
| |
| hwctx->resource_flags = D3D12_RESOURCE_FLAG_VIDEO_ENCODE_REFERENCE_ONLY | |
| D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE; |
| if (ctx->is_texture_array) { |
| base_ctx->recon_frames->initial_pool_size = MAX_DPB_SIZE + 1; |
| hwctx->flags |= AV_D3D12VA_FRAME_FLAG_TEXTURE_ARRAY; |
| } |
| |
| err = av_hwframe_ctx_init(base_ctx->recon_frames_ref); |
| if (err < 0) { |
| av_log(avctx, AV_LOG_ERROR, "Failed to initialise reconstructed " |
| "frame context: %d.\n", err); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static const FFHWEncodePictureOperation d3d12va_type = { |
| .priv_size = sizeof(D3D12VAEncodePicture), |
| |
| .init = &d3d12va_encode_init, |
| |
| .issue = &d3d12va_encode_issue, |
| |
| .output = &d3d12va_encode_output, |
| |
| .free = &d3d12va_encode_free, |
| }; |
| |
| int ff_d3d12va_encode_receive_packet(AVCodecContext *avctx, AVPacket *pkt) |
| { |
| return ff_hw_base_encode_receive_packet(avctx->priv_data, avctx, pkt); |
| } |
| |
| int ff_d3d12va_encode_init(AVCodecContext *avctx) |
| { |
| FFHWBaseEncodeContext *base_ctx = avctx->priv_data; |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| D3D12_FEATURE_DATA_VIDEO_FEATURE_AREA_SUPPORT support = { 0 }; |
| D3D12_FEATURE_DATA_FORMAT_INFO format_info = { 0 }; |
| int err; |
| HRESULT hr; |
| |
| err = ff_hw_base_encode_init(avctx, base_ctx); |
| if (err < 0) |
| goto fail; |
| |
| base_ctx->op = &d3d12va_type; |
| |
| ctx->hwctx = base_ctx->device->hwctx; |
| |
| ctx->resolution.Width = base_ctx->input_frames->width; |
| ctx->resolution.Height = base_ctx->input_frames->height; |
| |
| hr = ID3D12Device_QueryInterface(ctx->hwctx->device, &IID_ID3D12Device3, (void **)&ctx->device3); |
| if (FAILED(hr)) { |
| av_log(avctx, AV_LOG_ERROR, "ID3D12Device3 interface is not supported.\n"); |
| err = AVERROR_UNKNOWN; |
| goto fail; |
| } |
| |
| hr = ID3D12Device3_QueryInterface(ctx->device3, &IID_ID3D12VideoDevice3, (void **)&ctx->video_device3); |
| if (FAILED(hr)) { |
| av_log(avctx, AV_LOG_ERROR, "ID3D12VideoDevice3 interface is not supported.\n"); |
| err = AVERROR_UNKNOWN; |
| goto fail; |
| } |
| |
| if (FAILED(ID3D12VideoDevice3_CheckFeatureSupport(ctx->video_device3, D3D12_FEATURE_VIDEO_FEATURE_AREA_SUPPORT, |
| &support, sizeof(support))) && !support.VideoEncodeSupport) { |
| av_log(avctx, AV_LOG_ERROR, "D3D12 video device has no video encoder support.\n"); |
| err = AVERROR(EINVAL); |
| goto fail; |
| } |
| |
| format_info.Format = ((AVD3D12VAFramesContext *)base_ctx->input_frames->hwctx)->format; |
| if (FAILED(ID3D12VideoDevice_CheckFeatureSupport(ctx->hwctx->device, D3D12_FEATURE_FORMAT_INFO, |
| &format_info, sizeof(format_info)))) { |
| av_log(avctx, AV_LOG_ERROR, "Failed to query format plane count: 0x%x\n", hr); |
| err = AVERROR_EXTERNAL; |
| goto fail; |
| } |
| ctx->plane_count = format_info.PlaneCount; |
| |
| err = d3d12va_encode_set_profile(avctx); |
| if (err < 0) |
| goto fail; |
| |
| err = d3d12va_encode_init_rate_control(avctx); |
| if (err < 0) |
| goto fail; |
| |
| if (ctx->codec->get_encoder_caps) { |
| err = ctx->codec->get_encoder_caps(avctx); |
| if (err < 0) |
| goto fail; |
| } |
| |
| err = d3d12va_encode_init_gop_structure(avctx); |
| if (err < 0) |
| goto fail; |
| |
| if (!(ctx->codec->flags & FF_HW_FLAG_SLICE_CONTROL) && avctx->slices > 0) { |
| av_log(avctx, AV_LOG_WARNING, "Multiple slices were requested " |
| "but this codec does not support controlling slices.\n"); |
| } |
| |
| err = d3d12va_encode_create_command_objects(avctx); |
| if (err < 0) |
| goto fail; |
| |
| err = d3d12va_encode_prepare_output_buffers(avctx); |
| if (err < 0) |
| goto fail; |
| |
| if (ctx->codec->configure) { |
| err = ctx->codec->configure(avctx); |
| if (err < 0) |
| goto fail; |
| } |
| |
| if (ctx->codec->init_sequence_params) { |
| err = ctx->codec->init_sequence_params(avctx); |
| if (err < 0) { |
| av_log(avctx, AV_LOG_ERROR, "Codec sequence initialisation " |
| "failed: %d.\n", err); |
| goto fail; |
| } |
| } |
| |
| if (ctx->codec->set_level) { |
| err = ctx->codec->set_level(avctx); |
| if (err < 0) |
| goto fail; |
| } |
| |
| err = d3d12va_encode_create_recon_frames(avctx); |
| if (err < 0) |
| goto fail; |
| |
| base_ctx->output_delay = base_ctx->b_per_p; |
| base_ctx->decode_delay = base_ctx->max_b_depth; |
| |
| err = d3d12va_create_encoder(avctx); |
| if (err < 0) |
| goto fail; |
| |
| err = d3d12va_create_encoder_heap(avctx); |
| if (err < 0) |
| goto fail; |
| |
| base_ctx->async_encode = 1; |
| base_ctx->encode_fifo = av_fifo_alloc2(base_ctx->async_depth, |
| sizeof(D3D12VAEncodePicture *), 0); |
| if (!base_ctx->encode_fifo) |
| return AVERROR(ENOMEM); |
| |
| return 0; |
| |
| fail: |
| return err; |
| } |
| |
| int ff_d3d12va_encode_close(AVCodecContext *avctx) |
| { |
| int num_allocator = 0; |
| FFHWBaseEncodeContext *base_ctx = avctx->priv_data; |
| D3D12VAEncodeContext *ctx = avctx->priv_data; |
| FFHWBaseEncodePicture *pic, *next; |
| CommandAllocator allocator; |
| |
| if (!base_ctx->frame) |
| return 0; |
| |
| for (pic = base_ctx->pic_start; pic; pic = next) { |
| next = pic->next; |
| d3d12va_encode_free(avctx, pic); |
| } |
| |
| d3d12va_encode_free_rc_params(avctx); |
| |
| av_buffer_pool_uninit(&ctx->output_buffer_pool); |
| |
| D3D12_OBJECT_RELEASE(ctx->command_list); |
| D3D12_OBJECT_RELEASE(ctx->command_queue); |
| |
| if (ctx->allocator_queue) { |
| while (av_fifo_read(ctx->allocator_queue, &allocator, 1) >= 0) { |
| num_allocator++; |
| D3D12_OBJECT_RELEASE(allocator.command_allocator); |
| } |
| |
| av_log(avctx, AV_LOG_VERBOSE, "Total number of command allocators reused: %d\n", num_allocator); |
| } |
| |
| av_fifo_freep2(&ctx->allocator_queue); |
| |
| D3D12_OBJECT_RELEASE(ctx->sync_ctx.fence); |
| if (ctx->sync_ctx.event) |
| CloseHandle(ctx->sync_ctx.event); |
| |
| D3D12_OBJECT_RELEASE(ctx->encoder_heap); |
| D3D12_OBJECT_RELEASE(ctx->encoder); |
| D3D12_OBJECT_RELEASE(ctx->video_device3); |
| D3D12_OBJECT_RELEASE(ctx->device3); |
| |
| ff_hw_base_encode_close(base_ctx); |
| |
| return 0; |
| } |