This patch adds support for the texture array feature used by AMD boards in the D3D12 HEVC encoder. In texture array mode, a single texture array is shared for all reference and reconstructed pictures using different subresources. The implementation ensures compatibility and has been successfully tested on AMD, Intel, and NVIDIA GPUs.
--- libavcodec/d3d12va_encode.c | 241 +++++++++++++++++++++++++------ libavcodec/d3d12va_encode.h | 29 ++++ libavcodec/d3d12va_encode_hevc.c | 5 +- 3 files changed, 231 insertions(+), 44 deletions(-) diff --git a/libavcodec/d3d12va_encode.c b/libavcodec/d3d12va_encode.c index 4d738200fe..580d2ea383 100644 --- a/libavcodec/d3d12va_encode.c +++ b/libavcodec/d3d12va_encode.c @@ -264,6 +264,11 @@ static int d3d12va_encode_issue(AVCodecContext *avctx, av_log(avctx, AV_LOG_DEBUG, "Input surface is %p.\n", pic->input_surface->texture); + if (ctx->is_texture_array) { + base_pic->recon_image->data[0] = ctx->texture_array_frame; + pic->subresource_index = (ctx->subresource_used_index++) % ctx->max_subresource_array_size; + } + 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); @@ -325,11 +330,28 @@ static int d3d12va_encode_issue(AVCodecContext *avctx, 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; - for (j = 0; j < base_pic->nb_refs[1]; j++) - d3d12_refs.ppTexture2Ds[i++] = ((D3D12VAEncodePicture *)base_pic->refs[1][j]->priv)->recon_surface->texture; + 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; @@ -343,7 +365,11 @@ static int d3d12va_encode_issue(AVCodecContext *avctx, 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; + if (ctx->is_texture_array) { + output_args.ReconstructedPicture.ReconstructedPictureSubresource = pic->subresource_index; + } else { + output_args.ReconstructedPicture.ReconstructedPictureSubresource = 0; + } output_args.EncoderOutputMetadata.pBuffer = pic->encoded_metadata; output_args.EncoderOutputMetadata.Offset = 0; @@ -381,35 +407,87 @@ static int d3d12va_encode_issue(AVCodecContext *avctx, }, \ } +#define TRANSITION_BARRIER_SUBRESOURCE(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_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, + barriers[2] = TRANSITION_BARRIER(pic->encoded_metadata, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE); - barriers[3] = TRANSITION_BARRIER(pic->encoded_metadata, + barriers[3] = TRANSITION_BARRIER(pic->resolved_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_ResourceBarrier(cmd_list, 4, barriers); + + //set transit barriers for reference pic and recon pic + int barriers_ref_index = 0; + D3D12_RESOURCE_BARRIER *barriers_ref = NULL; + if (ctx->is_texture_array) { + barriers_ref = av_calloc(ctx->max_subresource_array_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) { + // In Texture array mode, the D3D12 uses the same texture array for all the input + // reference pics in ppTexture2Ds and also for the pReconstructedPicture output allocations, + //just different subresources. + 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++) { + + //D3D12 DecomposeSubresource + uint32_t mip_slice, plane_slice, array_slice, array_size; + array_size = references_tex_array_desc.DepthOrArraySize; + mip_slice = reference_subresource % references_tex_array_desc.MipLevels; + array_slice = (reference_subresource / references_tex_array_desc.MipLevels) % array_size; + + for (plane_slice = 0; plane_slice < ctx->plane_count; plane_slice++) { + //Calculate the subresource index + uint32_t planeOutputSubresource = 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_SUBRESOURCE(pic->recon_surface->texture, planeOutputSubresource, + D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE); + } else { + barriers_ref[barriers_ref_index++] = TRANSITION_BARRIER_SUBRESOURCE(pic->recon_surface->texture, planeOutputSubresource, + D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ); + } + } + } + } else { + barriers_ref[barriers_ref_index++] = TRANSITION_BARRIER(pic->recon_surface->texture, + 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_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); @@ -422,16 +500,15 @@ static int d3d12va_encode_issue(AVCodecContext *avctx, 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); + //swap the barriers_ref transition state + if (barriers_ref_index > 0) { + for (i = 0; i < barriers_ref_index; i++) { + D3D12_RESOURCE_STATES temp_statue = barriers_ref[i].Transition.StateBefore; + barriers_ref[i].Transition.StateBefore = barriers_ref[i].Transition.StateAfter; + barriers_ref[i].Transition.StateAfter = temp_statue; + } + ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, barriers_ref_index, + barriers_ref); } barriers[0] = TRANSITION_BARRIER(pic->input_surface->texture, @@ -440,17 +517,14 @@ static int d3d12va_encode_issue(AVCodecContext *avctx, 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, + barriers[2] = TRANSITION_BARRIER(pic->encoded_metadata, D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ, D3D12_RESOURCE_STATE_COMMON); - barriers[4] = TRANSITION_BARRIER(pic->resolved_metadata, + barriers[3] = TRANSITION_BARRIER(pic->resolved_metadata, D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE, D3D12_RESOURCE_STATE_COMMON); - ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, 5, barriers); + ID3D12VideoEncodeCommandList2_ResourceBarrier(cmd_list, 4, barriers); hr = ID3D12VideoEncodeCommandList2_Close(cmd_list); if (FAILED(hr)) { @@ -489,6 +563,14 @@ static int d3d12va_encode_issue(AVCodecContext *avctx, if (d3d12_refs.ppTexture2Ds) av_freep(&d3d12_refs.ppTexture2Ds); + if (ctx->is_texture_array) { + if (d3d12_refs.pSubresources) + av_freep(&d3d12_refs.pSubresources); + } + + if (barriers_ref) + av_freep(&barriers_ref); + return 0; fail: @@ -498,6 +580,14 @@ fail: if (d3d12_refs.ppTexture2Ds) av_freep(&d3d12_refs.ppTexture2Ds); + if (ctx->is_texture_array) { + if (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); @@ -1088,13 +1178,15 @@ static int d3d12va_encode_init_gop_structure(AVCodecContext *avctx) switch (ctx->codec->d3d12_codec) { case D3D12_VIDEO_ENCODER_CODEC_H264: ref_l0 = FFMIN(support.PictureSupport.pH264Support->MaxL0ReferencesForP, - support.PictureSupport.pH264Support->MaxL1ReferencesForB); + support.PictureSupport.pHEVCSupport->MaxL1ReferencesForB ? + support.PictureSupport.pHEVCSupport->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 ? + support.PictureSupport.pHEVCSupport->MaxL1ReferencesForB : UINT_MAX); ref_l1 = support.PictureSupport.pHEVCSupport->MaxL1ReferencesForB; break; @@ -1336,6 +1428,47 @@ fail: return err; } +static int d3d12va_create_texture_array(AVHWFramesContext *ctx, D3D12VAEncodeContext *encode_context) +{ + AVD3D12VAFramesContext *hwctx = ctx->hwctx; + AVD3D12VADeviceContext *device_hwctx = ctx->device_ctx->hwctx; + + AVD3D12VAFrame *frame; + D3D12_HEAP_PROPERTIES props = { .Type = D3D12_HEAP_TYPE_DEFAULT }; + + encode_context->max_subresource_array_size = MAX_DPB_SIZE + D3D12VA_VIDEO_ENC_ASYNC_DEPTH + 1; + + D3D12_RESOURCE_DESC desc = { + .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D, + .Alignment = 0, + .Width = ctx->width, + .Height = ctx->height, + .DepthOrArraySize = encode_context->max_subresource_array_size, + .MipLevels = 1, + .Format = hwctx->format, + .SampleDesc = {.Count = 1, .Quality = 0 }, + .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN, + .Flags = hwctx->flags, + }; + + frame = av_mallocz(sizeof(AVD3D12VAFrame)); + if (!frame) + return AVERROR(ENOMEM); + + if (FAILED(ID3D12Device_CreateCommittedResource(device_hwctx->device, &props, D3D12_HEAP_FLAG_NONE, &desc, + D3D12_RESOURCE_STATE_COMMON, NULL, &IID_ID3D12Resource, (void **)&frame->texture))) { + av_log(ctx, AV_LOG_ERROR, "Could not create the texture\n"); + return AVERROR(EINVAL); + } + + ID3D12Device_CreateFence(device_hwctx->device, 0, D3D12_FENCE_FLAG_NONE, + &IID_ID3D12Fence, (void **)&frame->sync_ctx.fence); + + frame->sync_ctx.event = CreateEvent(NULL, FALSE, FALSE, NULL); + encode_context->texture_array_frame = frame; + return 0; +} + static int d3d12va_encode_create_recon_frames(AVCodecContext *avctx) { FFHWBaseEncodeContext *base_ctx = avctx->priv_data; @@ -1394,6 +1527,7 @@ 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; @@ -1429,6 +1563,15 @@ int ff_d3d12va_encode_init(AVCodecContext *avctx) 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; @@ -1485,6 +1628,10 @@ int ff_d3d12va_encode_init(AVCodecContext *avctx) goto fail; } + if (ctx->is_texture_array) { + d3d12va_create_texture_array(base_ctx->recon_frames, avctx->priv_data); + } + base_ctx->output_delay = base_ctx->b_per_p; base_ctx->decode_delay = base_ctx->max_b_depth; @@ -1528,6 +1675,18 @@ int ff_d3d12va_encode_close(AVCodecContext *avctx) av_buffer_pool_uninit(&ctx->output_buffer_pool); + if (ctx->is_texture_array) { + ID3D12Resource *pResource = ctx->texture_array_frame->texture; + if (pResource) { + D3D12_OBJECT_RELEASE(pResource); + ctx->texture_array_frame->texture = NULL; + } + D3D12_OBJECT_RELEASE(ctx->texture_array_frame->sync_ctx.fence); + if (ctx->texture_array_frame->sync_ctx.event) + CloseHandle(ctx->texture_array_frame->sync_ctx.event); + av_free(ctx->texture_array_frame); + } + D3D12_OBJECT_RELEASE(ctx->command_list); D3D12_OBJECT_RELEASE(ctx->command_queue); diff --git a/libavcodec/d3d12va_encode.h b/libavcodec/d3d12va_encode.h index 3b0b8153d5..fc31857f1a 100644 --- a/libavcodec/d3d12va_encode.h +++ b/libavcodec/d3d12va_encode.h @@ -52,6 +52,8 @@ typedef struct D3D12VAEncodePicture { ID3D12Resource *encoded_metadata; ID3D12Resource *resolved_metadata; + int subresource_index; + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA pic_ctl; int fence_value; @@ -189,6 +191,33 @@ typedef struct D3D12VAEncodeContext { */ AVBufferPool *output_buffer_pool; + /** + * Flag indicates if the HW is texture array mode. + */ + int is_texture_array; + + /** + * In texture array mode, the D3D12 uses the same texture array for all the input + * reference pics in ppTexture2Ds and also for the pReconstructedPicture output + * allocations, just different subresources. + */ + AVD3D12VAFrame *texture_array_frame; + + /** + * The max number of subresources in the texture array. + */ + int max_subresource_array_size; + + /** + * The used subresource index for pic in the texture array. + */ + int subresource_used_index; + + /** + * The number of planes in the input DXGI FORMAT . + */ + int plane_count; + /** * D3D12 video encoder. */ diff --git a/libavcodec/d3d12va_encode_hevc.c b/libavcodec/d3d12va_encode_hevc.c index 938ba01f54..7e1d973f7e 100644 --- a/libavcodec/d3d12va_encode_hevc.c +++ b/libavcodec/d3d12va_encode_hevc.c @@ -280,9 +280,8 @@ static int d3d12va_encode_hevc_init_sequence_params(AVCodecContext *avctx) } if (support.SupportFlags & D3D12_VIDEO_ENCODER_SUPPORT_FLAG_RECONSTRUCTED_FRAMES_REQUIRE_TEXTURE_ARRAYS) { - av_log(avctx, AV_LOG_ERROR, "D3D12 video encode on this device requires texture array support, " - "but it's not implemented.\n"); - return AVERROR_PATCHWELCOME; + ctx->is_texture_array = 1; + av_log(avctx, AV_LOG_DEBUG, "D3D12 video encode on this device uses texture array mode.\n"); } desc = av_pix_fmt_desc_get(base_ctx->input_frames->sw_format); -- 2.45.2.windows.1 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".