blob: 11b11675b7a8ca5aeec468e1d968aed41ed01c3c [file] [edit]
/*
* Apple ProRes encoder
*
* Copyright (c) 2011 Anatoliy Wasserman
* Copyright (c) 2012 Konstantin Shishkov
*
* 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/buffer.h"
#include "libavutil/macros.h"
#include "libavutil/mem.h"
#include "libavutil/mem_internal.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "libavutil/vulkan_spirv.h"
#include "libavutil/hwcontext_vulkan.h"
#include "libavutil/vulkan_loader.h"
#include "libavutil/vulkan.h"
#include "avcodec.h"
#include "codec.h"
#include "codec_internal.h"
#include "encode.h"
#include "packet.h"
#include "put_bits.h"
#include "profiles.h"
#include "bytestream.h"
#include "proresdata.h"
#include "proresenc_kostya_common.h"
#include "hwconfig.h"
#define DCTSIZE 8
typedef struct ProresDataTables {
int16_t qmat[128][64];
int16_t qmat_chroma[128][64];
} ProresDataTables;
typedef struct SliceDataInfo {
int plane;
int line_add;
int bits_per_sample;
} SliceDataInfo;
typedef struct EncodeSliceInfo {
VkDeviceAddress bytestream;
VkDeviceAddress seek_table;
} EncodeSliceInfo;
typedef struct SliceData {
uint32_t mbs_per_slice;
int16_t rows[MAX_PLANES * MAX_MBS_PER_SLICE * 256];
} SliceData;
typedef struct SliceScore {
int bits[MAX_STORED_Q][4];
int error[MAX_STORED_Q][4];
int total_bits[MAX_STORED_Q];
int total_error[MAX_STORED_Q];
int overquant;
int buf_start;
int quant;
} SliceScore;
typedef struct VulkanEncodeProresFrameData {
/* Intermediate buffers */
AVBufferRef *out_data_ref[2];
AVBufferRef *slice_data_ref[2];
AVBufferRef *slice_score_ref[2];
AVBufferRef *frame_size_ref[2];
/* Copied from the source */
int64_t pts;
int64_t duration;
void *frame_opaque;
AVBufferRef *frame_opaque_ref;
enum AVColorTransferCharacteristic color_trc;
enum AVColorSpace colorspace;
enum AVColorPrimaries color_primaries;
int key_frame;
int flags;
} VulkanEncodeProresFrameData;
typedef struct ProresVulkanContext {
ProresContext ctx;
/* Vulkan state */
FFVulkanContext vkctx;
AVVulkanDeviceQueueFamily *qf;
FFVkExecPool e;
AVVulkanDeviceQueueFamily *transfer_qf;
FFVkExecPool transfer_exec_pool;
AVBufferPool *pkt_buf_pool;
AVBufferPool *slice_data_buf_pool;
AVBufferPool *slice_score_buf_pool;
AVBufferPool *frame_size_buf_pool;
FFVulkanShader alpha_data_shd;
FFVulkanShader slice_data_shd[2];
FFVulkanShader estimate_slice_shd;
FFVulkanShader encode_slice_shd;
FFVulkanShader trellis_node_shd;
FFVkBuffer prores_data_tables_buf;
int *slice_quants;
SliceScore *slice_scores;
ProresDataTables *tables;
int in_flight;
int async_depth;
AVFrame *frame;
VulkanEncodeProresFrameData *exec_ctx_info;
} ProresVulkanContext;
extern const unsigned char ff_prores_ks_alpha_data_comp_spv_data[];
extern const unsigned int ff_prores_ks_alpha_data_comp_spv_len;
extern const unsigned char ff_prores_ks_slice_data_comp_spv_data[];
extern const unsigned int ff_prores_ks_slice_data_comp_spv_len;
extern const unsigned char ff_prores_ks_estimate_slice_comp_spv_data[];
extern const unsigned int ff_prores_ks_estimate_slice_comp_spv_len;
extern const unsigned char ff_prores_ks_trellis_node_comp_spv_data[];
extern const unsigned int ff_prores_ks_trellis_node_comp_spv_len;
extern const unsigned char ff_prores_ks_encode_slice_comp_spv_data[];
extern const unsigned int ff_prores_ks_encode_slice_comp_spv_len;
static int init_slice_data_pipeline(ProresVulkanContext *pv, FFVulkanShader *shd, int blocks_per_mb)
{
int err = 0;
FFVulkanContext *vkctx = &pv->vkctx;
FFVulkanDescriptorSetBinding *desc;
SPEC_LIST_CREATE(sl, 5, 5 * sizeof(uint32_t))
SPEC_LIST_ADD(sl, 0, 32, pv->ctx.mbs_per_slice);
SPEC_LIST_ADD(sl, 1, 32, blocks_per_mb);
SPEC_LIST_ADD(sl, 2, 32, pv->ctx.mb_width);
SPEC_LIST_ADD(sl, 3, 32, pv->ctx.pictures_per_frame);
SPEC_LIST_ADD(sl, 16, 32, blocks_per_mb * pv->ctx.mbs_per_slice); /* nb_blocks */
ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
(uint32_t []) { DCTSIZE, blocks_per_mb, pv->ctx.mbs_per_slice }, 0);
desc = (FFVulkanDescriptorSetBinding []) {
{
.name = "SliceBuffer",
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
{
.name = "planes",
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
.elems = av_pix_fmt_count_planes(vkctx->frames->sw_format),
},
};
RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0, 0));
ff_vk_shader_add_push_const(shd, 0, sizeof(SliceDataInfo), VK_SHADER_STAGE_COMPUTE_BIT);
RET(ff_vk_shader_link(vkctx, shd,
ff_prores_ks_slice_data_comp_spv_data,
ff_prores_ks_slice_data_comp_spv_len, "main"));
RET(ff_vk_shader_register_exec(vkctx, &pv->e, shd));
fail:
return err;
}
static int init_alpha_data_pipeline(ProresVulkanContext *pv, FFVulkanShader* shd)
{
int err = 0;
FFVulkanContext *vkctx = &pv->vkctx;
FFVulkanDescriptorSetBinding *desc;
SPEC_LIST_CREATE(sl, 4, 4 * sizeof(uint32_t))
SPEC_LIST_ADD(sl, 0, 32, pv->ctx.alpha_bits);
SPEC_LIST_ADD(sl, 1, 32, pv->ctx.slices_width);
SPEC_LIST_ADD(sl, 2, 32, pv->ctx.mb_width);
SPEC_LIST_ADD(sl, 3, 32, pv->ctx.mbs_per_slice);
ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
(uint32_t []) { 16, 16, 1 }, 0);
desc = (FFVulkanDescriptorSetBinding []) {
{
.name = "SliceBuffer",
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
{
.name = "plane",
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
};
RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0, 0));
RET(ff_vk_shader_link(vkctx, shd,
ff_prores_ks_alpha_data_comp_spv_data,
ff_prores_ks_alpha_data_comp_spv_len, "main"));
RET(ff_vk_shader_register_exec(vkctx, &pv->e, shd));
fail:
return err;
}
static int init_estimate_slice_pipeline(ProresVulkanContext *pv, FFVulkanShader* shd)
{
int err = 0;
FFVulkanContext *vkctx = &pv->vkctx;
FFVulkanDescriptorSetBinding *desc;
int subgroup_size = vkctx->subgroup_props.maxSubgroupSize;
int dim_x = pv->ctx.alpha_bits ? subgroup_size : (subgroup_size / 3) * 3;
SPEC_LIST_CREATE(sl, 8, 8 * sizeof(uint32_t))
SPEC_LIST_ADD(sl, 0, 32, pv->ctx.mbs_per_slice);
SPEC_LIST_ADD(sl, 1, 32, pv->ctx.chroma_factor);
SPEC_LIST_ADD(sl, 2, 32, pv->ctx.alpha_bits);
SPEC_LIST_ADD(sl, 3, 32, pv->ctx.num_planes);
SPEC_LIST_ADD(sl, 4, 32, pv->ctx.slices_per_picture);
SPEC_LIST_ADD(sl, 5, 32, pv->ctx.force_quant ? 0 : pv->ctx.profile_info->min_quant);
SPEC_LIST_ADD(sl, 6, 32, pv->ctx.force_quant ? 0 : pv->ctx.profile_info->max_quant);
SPEC_LIST_ADD(sl, 7, 32, pv->ctx.bits_per_mb);
ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
(uint32_t []) { dim_x, 1, 1 }, 0);
desc = (FFVulkanDescriptorSetBinding []) {
{
.name = "SliceBuffer",
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
{
.name = "SliceScores",
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
{
.name = "ProresDataTables",
.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
};
RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 3, 0, 0));
RET(ff_vk_shader_link(vkctx, shd,
ff_prores_ks_estimate_slice_comp_spv_data,
ff_prores_ks_estimate_slice_comp_spv_len, "main"));
RET(ff_vk_shader_register_exec(vkctx, &pv->e, shd));
fail:
return err;
}
static int init_trellis_node_pipeline(ProresVulkanContext *pv, FFVulkanShader* shd)
{
int err = 0;
FFVulkanContext *vkctx = &pv->vkctx;
FFVulkanDescriptorSetBinding *desc;
int subgroup_size = vkctx->subgroup_props.maxSubgroupSize;
int num_subgroups = FFALIGN(pv->ctx.mb_height, subgroup_size) / subgroup_size;
SPEC_LIST_CREATE(sl, 8, 8 * sizeof(uint32_t))
SPEC_LIST_ADD(sl, 0, 32, pv->ctx.slices_width);
SPEC_LIST_ADD(sl, 1, 32, num_subgroups);
SPEC_LIST_ADD(sl, 2, 32, pv->ctx.num_planes);
SPEC_LIST_ADD(sl, 3, 32, pv->ctx.force_quant);
SPEC_LIST_ADD(sl, 4, 32, pv->ctx.profile_info->min_quant);
SPEC_LIST_ADD(sl, 5, 32, pv->ctx.profile_info->max_quant);
SPEC_LIST_ADD(sl, 6, 32, pv->ctx.mbs_per_slice);
SPEC_LIST_ADD(sl, 7, 32, pv->ctx.bits_per_mb);
ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
(uint32_t []) { pv->ctx.mb_height, 1, 1 }, 0);
desc = (FFVulkanDescriptorSetBinding []) {
{
.name = "FrameSize",
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
{
.name = "SliceScores",
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
};
RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0, 0));
RET(ff_vk_shader_link(vkctx, shd,
ff_prores_ks_trellis_node_comp_spv_data,
ff_prores_ks_trellis_node_comp_spv_len, "main"));
RET(ff_vk_shader_register_exec(vkctx, &pv->e, shd));
fail:
return err;
}
static int init_encode_slice_pipeline(ProresVulkanContext *pv, FFVulkanShader* shd)
{
int err = 0;
FFVulkanContext *vkctx = &pv->vkctx;
FFVulkanDescriptorSetBinding *desc;
SPEC_LIST_CREATE(sl, 6, 6 * sizeof(uint32_t))
SPEC_LIST_ADD(sl, 0, 32, pv->ctx.mbs_per_slice);
SPEC_LIST_ADD(sl, 1, 32, pv->ctx.chroma_factor);
SPEC_LIST_ADD(sl, 2, 32, pv->ctx.alpha_bits);
SPEC_LIST_ADD(sl, 3, 32, pv->ctx.num_planes);
SPEC_LIST_ADD(sl, 4, 32, pv->ctx.slices_per_picture);
SPEC_LIST_ADD(sl, 5, 32, pv->ctx.force_quant ? pv->ctx.force_quant : pv->ctx.profile_info->max_quant);
ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
(uint32_t []) { 64, 1, 1 }, 0);
desc = (FFVulkanDescriptorSetBinding []) {
{
.name = "SliceBuffer",
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
{
.name = "SliceScores",
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
{
.name = "ProresDataTables",
.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
};
RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 3, 0, 0));
ff_vk_shader_add_push_const(shd, 0, sizeof(EncodeSliceInfo), VK_SHADER_STAGE_COMPUTE_BIT);
RET(ff_vk_shader_link(vkctx, shd,
ff_prores_ks_encode_slice_comp_spv_data,
ff_prores_ks_encode_slice_comp_spv_len, "main"));
RET(ff_vk_shader_register_exec(vkctx, &pv->e, shd));
fail:
return err;
}
static int vulkan_encode_prores_submit_frame(AVCodecContext *avctx, FFVkExecContext *exec,
AVFrame *frame, int picture_idx)
{
ProresVulkanContext *pv = avctx->priv_data;
ProresContext *ctx = &pv->ctx;
VulkanEncodeProresFrameData *pd = exec->opaque;
FFVulkanContext *vkctx = &pv->vkctx;
FFVulkanFunctions *vk = &vkctx->vkfn;
int err = 0, nb_img_bar = 0, i, is_chroma;
int min_quant = ctx->profile_info->min_quant;
int max_quant = ctx->profile_info->max_quant;
int subgroup_size = vkctx->subgroup_props.maxSubgroupSize;
int estimate_dim_x = ctx->alpha_bits ? subgroup_size : (subgroup_size / 3) * 3;
int transfer_slices = vkctx->extensions & FF_VK_EXT_EXTERNAL_HOST_MEMORY;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(vkctx->frames->sw_format);
VkImageView views[AV_NUM_DATA_POINTERS];
VkImageMemoryBarrier2 img_bar[AV_NUM_DATA_POINTERS];
FFVkBuffer *pkt_vk_buf, *slice_data_buf, *slice_score_buf, *frame_size_buf;
SliceDataInfo slice_data_info;
EncodeSliceInfo encode_info;
FFVulkanShader *shd;
/* Start recording */
ff_vk_exec_start(vkctx, exec);
/* Get a pooled buffer for writing output data */
RET(ff_vk_get_pooled_buffer(vkctx, &pv->pkt_buf_pool, &pd->out_data_ref[picture_idx],
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT, NULL,
ctx->frame_size_upper_bound + FF_INPUT_BUFFER_MIN_SIZE,
transfer_slices ? VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
: (VK_MEMORY_PROPERTY_HOST_CACHED_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)));
pkt_vk_buf = (FFVkBuffer*)pd->out_data_ref[picture_idx]->data;
ff_vk_exec_add_dep_buf(vkctx, exec, &pd->out_data_ref[picture_idx], 1, 1);
/* Allocate buffer for writing slice data */
RET(ff_vk_get_pooled_buffer(vkctx, &pv->slice_data_buf_pool, &pd->slice_data_ref[picture_idx],
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, NULL,
ctx->slices_per_picture * sizeof(SliceData),
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
slice_data_buf = (FFVkBuffer*)pd->slice_data_ref[picture_idx]->data;
ff_vk_exec_add_dep_buf(vkctx, exec, &pd->slice_data_ref[picture_idx], 1, 1);
/* Allocate buffer for writing slice scores */
RET(ff_vk_get_pooled_buffer(vkctx, &pv->slice_score_buf_pool, &pd->slice_score_ref[picture_idx],
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, NULL,
ctx->slices_per_picture * sizeof(SliceScore),
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
slice_score_buf = (FFVkBuffer*)pd->slice_score_ref[picture_idx]->data;
ff_vk_exec_add_dep_buf(vkctx, exec, &pd->slice_score_ref[picture_idx], 1, 1);
/* Allocate buffer for writing frame size */
RET(ff_vk_get_pooled_buffer(vkctx, &pv->frame_size_buf_pool, &pd->frame_size_ref[picture_idx],
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, NULL,
sizeof(int),
VK_MEMORY_PROPERTY_HOST_CACHED_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT));
frame_size_buf = (FFVkBuffer*)pd->frame_size_ref[picture_idx]->data;
ff_vk_exec_add_dep_buf(vkctx, exec, &pd->frame_size_ref[picture_idx], 1, 1);
/* Generate barriers and image views for frame images. */
RET(ff_vk_exec_add_dep_frame(vkctx, exec, frame,
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT));
RET(ff_vk_create_imageviews(vkctx, exec, views, frame, FF_VK_REP_INT));
ff_vk_frame_barrier(vkctx, exec, frame, img_bar, &nb_img_bar,
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
VK_ACCESS_SHADER_READ_BIT,
VK_IMAGE_LAYOUT_GENERAL,
VK_QUEUE_FAMILY_IGNORED);
/* Submit the image barriers. */
vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.pImageMemoryBarriers = img_bar,
.imageMemoryBarrierCount = nb_img_bar,
});
/* Apply FDCT on input image data for future passes */
slice_data_info = (SliceDataInfo) {
.line_add = ctx->pictures_per_frame == 1 ? 0 : picture_idx ^ !(frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST),
};
for (i = 0; i < ctx->num_planes; i++) {
is_chroma = (i == 1 || i == 2);
shd = &pv->slice_data_shd[!is_chroma || ctx->chroma_factor == CFACTOR_Y444];
if (i < 3) {
slice_data_info.plane = i;
slice_data_info.bits_per_sample = desc->comp[i].depth;
ff_vk_shader_update_desc_buffer(vkctx, exec, shd, 0, 0, 0,
slice_data_buf, 0, slice_data_buf->size,
VK_FORMAT_UNDEFINED);
ff_vk_shader_update_img_array(vkctx, exec, shd, frame, views, 0, 1,
VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE);
ff_vk_exec_bind_shader(vkctx, exec, shd);
ff_vk_shader_update_push_const(vkctx, exec, shd, VK_SHADER_STAGE_COMPUTE_BIT,
0, sizeof(SliceDataInfo), &slice_data_info);
vk->CmdDispatch(exec->buf, ctx->slices_width, ctx->mb_height, 1);
} else {
ff_vk_shader_update_desc_buffer(vkctx, exec, &pv->alpha_data_shd, 0, 0, 0,
slice_data_buf, 0, slice_data_buf->size,
VK_FORMAT_UNDEFINED);
ff_vk_shader_update_img(vkctx, exec, &pv->alpha_data_shd, 0, 1, 0, views[3],
VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE);
ff_vk_exec_bind_shader(vkctx, exec, &pv->alpha_data_shd);
vk->CmdDispatch(exec->buf, ctx->mb_width, ctx->mb_height, 1);
}
}
/* Wait for writes to slice buffer. */
vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.pBufferMemoryBarriers = & (VkBufferMemoryBarrier2) {
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
.pNext = NULL,
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = slice_data_buf->buf,
.offset = 0,
.size = slice_data_buf->size,
},
.bufferMemoryBarrierCount = 1,
});
/* Estimate slice bits and error for each quant */
ff_vk_shader_update_desc_buffer(vkctx, exec, &pv->estimate_slice_shd, 0, 0, 0,
slice_data_buf, 0, slice_data_buf->size,
VK_FORMAT_UNDEFINED);
ff_vk_shader_update_desc_buffer(vkctx, exec, &pv->estimate_slice_shd, 0, 1, 0,
slice_score_buf, 0, slice_score_buf->size,
VK_FORMAT_UNDEFINED);
ff_vk_shader_update_desc_buffer(vkctx, exec, &pv->estimate_slice_shd, 0, 2, 0,
&pv->prores_data_tables_buf, 0, pv->prores_data_tables_buf.size,
VK_FORMAT_UNDEFINED);
ff_vk_exec_bind_shader(vkctx, exec, &pv->estimate_slice_shd);
vk->CmdDispatch(exec->buf, (ctx->slices_per_picture * ctx->num_planes + estimate_dim_x - 1) / estimate_dim_x,
ctx->force_quant ? 1 : (max_quant - min_quant + 1), 1);
/* Wait for writes to score buffer. */
vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.pBufferMemoryBarriers = & (VkBufferMemoryBarrier2) {
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
.pNext = NULL,
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_READ_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = slice_score_buf->buf,
.offset = 0,
.size = slice_score_buf->size,
},
.bufferMemoryBarrierCount = 1,
});
/* Compute optimal quant value for each slice */
ff_vk_shader_update_desc_buffer(vkctx, exec, &pv->trellis_node_shd, 0, 0, 0,
frame_size_buf, 0, frame_size_buf->size,
VK_FORMAT_UNDEFINED);
ff_vk_shader_update_desc_buffer(vkctx, exec, &pv->trellis_node_shd, 0, 1, 0,
slice_score_buf, 0, slice_score_buf->size,
VK_FORMAT_UNDEFINED);
ff_vk_exec_bind_shader(vkctx, exec, &pv->trellis_node_shd);
vk->CmdDispatch(exec->buf, 1, 1, 1);
/* Wait for writes to quant buffer. */
vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.pBufferMemoryBarriers = & (VkBufferMemoryBarrier2) {
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
.pNext = NULL,
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = frame_size_buf->buf,
.offset = 0,
.size = frame_size_buf->size,
},
.bufferMemoryBarrierCount = 1,
});
/* Encode slices. */
encode_info = (EncodeSliceInfo) {
.seek_table = pkt_vk_buf->address,
.bytestream = pkt_vk_buf->address + ctx->slices_per_picture * 2,
};
ff_vk_shader_update_desc_buffer(vkctx, exec, &pv->encode_slice_shd, 0, 0, 0,
slice_data_buf, 0, slice_data_buf->size,
VK_FORMAT_UNDEFINED);
ff_vk_shader_update_desc_buffer(vkctx, exec, &pv->encode_slice_shd, 0, 1, 0,
slice_score_buf, 0, slice_score_buf->size,
VK_FORMAT_UNDEFINED);
ff_vk_shader_update_desc_buffer(vkctx, exec, &pv->encode_slice_shd, 0, 2, 0,
&pv->prores_data_tables_buf, 0, pv->prores_data_tables_buf.size,
VK_FORMAT_UNDEFINED);
ff_vk_exec_bind_shader(vkctx, exec, &pv->encode_slice_shd);
ff_vk_shader_update_push_const(vkctx, exec, &pv->encode_slice_shd,
VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(encode_info), &encode_info);
vk->CmdDispatch(exec->buf, FFALIGN(ctx->slices_per_picture, 64) / 64,
ctx->num_planes, 1);
fail:
return err;
}
static uint8_t *write_frame_header(AVCodecContext *avctx, ProresContext *ctx,
uint8_t **orig_buf, int flags,
enum AVColorPrimaries color_primaries,
enum AVColorTransferCharacteristic color_trc,
enum AVColorSpace colorspace)
{
uint8_t *buf, *tmp;
uint8_t frame_flags;
// frame atom
*orig_buf += 4; // frame size
bytestream_put_be32 (orig_buf, FRAME_ID); // frame container ID
buf = *orig_buf;
// frame header
tmp = buf;
buf += 2; // frame header size will be stored here
bytestream_put_be16 (&buf, ctx->chroma_factor != CFACTOR_Y422 || ctx->alpha_bits ? 1 : 0);
bytestream_put_buffer(&buf, (uint8_t*)ctx->vendor, 4);
bytestream_put_be16 (&buf, avctx->width);
bytestream_put_be16 (&buf, avctx->height);
frame_flags = ctx->chroma_factor << 6;
if (avctx->flags & AV_CODEC_FLAG_INTERLACED_DCT)
frame_flags |= (flags & AV_FRAME_FLAG_TOP_FIELD_FIRST) ? 0x04 : 0x08;
bytestream_put_byte (&buf, frame_flags);
bytestream_put_byte (&buf, 0); // reserved
bytestream_put_byte (&buf, color_primaries);
bytestream_put_byte (&buf, color_trc);
bytestream_put_byte (&buf, colorspace);
bytestream_put_byte (&buf, ctx->alpha_bits >> 3);
bytestream_put_byte (&buf, 0); // reserved
if (ctx->quant_sel != QUANT_MAT_DEFAULT) {
bytestream_put_byte (&buf, 0x03); // matrix flags - both matrices are present
bytestream_put_buffer(&buf, ctx->quant_mat, 64); // luma quantisation matrix
bytestream_put_buffer(&buf, ctx->quant_chroma_mat, 64); // chroma quantisation matrix
} else {
bytestream_put_byte (&buf, 0x00); // matrix flags - default matrices are used
}
bytestream_put_be16 (&tmp, buf - *orig_buf); // write back frame header size
return buf;
}
static int get_packet(AVCodecContext *avctx, FFVkExecContext *exec, AVPacket *pkt)
{
ProresVulkanContext *pv = avctx->priv_data;
ProresContext *ctx = &pv->ctx;
VulkanEncodeProresFrameData *pd = exec->opaque;
FFVulkanContext *vkctx = &pv->vkctx;
FFVulkanFunctions *vk = &vkctx->vkfn;
FFVkExecContext *transfer_exec;
uint8_t *orig_buf, *buf, *slice_sizes;
uint8_t *picture_size_pos;
int picture_idx, err = 0;
int frame_size, picture_size;
int pkt_size = ctx->frame_size_upper_bound;
int transfer_slices = vkctx->extensions & FF_VK_EXT_EXTERNAL_HOST_MEMORY;
FFVkBuffer *out_data_buf, *frame_size_buf;
VkMappedMemoryRange invalidate_data;
AVBufferRef *mapped_ref;
FFVkBuffer *mapped_buf;
/* Allocate packet */
RET(ff_get_encode_buffer(avctx, pkt, pkt_size + FF_INPUT_BUFFER_MIN_SIZE, 0));
/* Initialize packet. */
pkt->pts = pd->pts;
pkt->dts = pd->pts;
pkt->duration = pd->duration;
pkt->flags |= AV_PKT_FLAG_KEY * pd->key_frame;
if (avctx->flags & AV_CODEC_FLAG_COPY_OPAQUE) {
pkt->opaque = pd->frame_opaque;
pkt->opaque_ref = pd->frame_opaque_ref;
pd->frame_opaque_ref = NULL;
}
/* Write frame atom */
orig_buf = pkt->data;
buf = write_frame_header(avctx, ctx, &orig_buf, pd->flags,
pd->color_primaries, pd->color_trc,
pd->colorspace);
/* Make sure encoding's done */
ff_vk_exec_wait(vkctx, exec);
/* Roll transfer execution context */
if (transfer_slices) {
RET(ff_vk_host_map_buffer(vkctx, &mapped_ref, pkt->data, pkt->buf,
VK_BUFFER_USAGE_TRANSFER_DST_BIT));
mapped_buf = (FFVkBuffer *)mapped_ref->data;
transfer_exec = ff_vk_exec_get(vkctx, &pv->transfer_exec_pool);
ff_vk_exec_start(vkctx, transfer_exec);
}
for (picture_idx = 0; picture_idx < ctx->pictures_per_frame; picture_idx++) {
/* Fetch buffers for the current picture. */
out_data_buf = (FFVkBuffer *)pd->out_data_ref[picture_idx]->data;
frame_size_buf = (FFVkBuffer *)pd->frame_size_ref[picture_idx]->data;
/* Invalidate slice/output data if needed */
invalidate_data = (VkMappedMemoryRange) {
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
.offset = 0,
.size = VK_WHOLE_SIZE,
};
if (!(frame_size_buf->flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
invalidate_data.memory = frame_size_buf->mem;
vk->InvalidateMappedMemoryRanges(vkctx->hwctx->act_dev, 1, &invalidate_data);
}
/* Write picture header */
picture_size_pos = buf + 1;
bytestream_put_byte(&buf, 0x40); // picture header size (in bits)
buf += 4; // picture data size will be stored here
bytestream_put_be16(&buf, ctx->slices_per_picture);
bytestream_put_byte(&buf, av_log2(ctx->mbs_per_slice) << 4); // slice width and height in MBs
/* Skip over seek table */
slice_sizes = buf;
buf += ctx->slices_per_picture * 2;
/* Calculate final size */
buf += *(int*)frame_size_buf->mapped_mem;
if (transfer_slices) {
/* Perform host mapped transfer of slice data */
ff_vk_exec_add_dep_buf(vkctx, transfer_exec, &pd->out_data_ref[picture_idx], 1, 0);
ff_vk_exec_add_dep_buf(vkctx, transfer_exec, &mapped_ref, 1, 0);
vk->CmdCopyBuffer(transfer_exec->buf, out_data_buf->buf, mapped_buf->buf, 1, & (VkBufferCopy) {
.srcOffset = 0,
.dstOffset = mapped_buf->virtual_offset + slice_sizes - pkt->data,
.size = buf - slice_sizes,
});
} else {
/* Fallback to regular memcpy if transfer is not available */
if (!(out_data_buf->flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
invalidate_data.memory = out_data_buf->mem;
vk->InvalidateMappedMemoryRanges(vkctx->hwctx->act_dev, 1, &invalidate_data);
}
memcpy(slice_sizes, out_data_buf->mapped_mem, buf - slice_sizes);
av_buffer_unref(&pd->out_data_ref[picture_idx]);
}
/* Write picture size with header */
picture_size = buf - (picture_size_pos - 1);
bytestream_put_be32(&picture_size_pos, picture_size);
/* Slice output buffers no longer needed */
av_buffer_unref(&pd->slice_data_ref[picture_idx]);
av_buffer_unref(&pd->slice_score_ref[picture_idx]);
av_buffer_unref(&pd->frame_size_ref[picture_idx]);
}
/* Write frame size in header */
orig_buf -= 8;
frame_size = buf - orig_buf;
bytestream_put_be32(&orig_buf, frame_size);
av_shrink_packet(pkt, frame_size);
av_log(avctx, AV_LOG_VERBOSE, "Encoded data: %iMiB\n", pkt->size / (1024*1024));
/* Wait for slice transfer */
if (transfer_slices) {
RET(ff_vk_exec_submit(vkctx, transfer_exec));
ff_vk_exec_wait(vkctx, transfer_exec);
}
fail:
return err;
}
static int vulkan_encode_prores_receive_packet(AVCodecContext *avctx, AVPacket *pkt)
{
int err;
ProresVulkanContext *pv = avctx->priv_data;
ProresContext *ctx = &pv->ctx;
VulkanEncodeProresFrameData *pd;
FFVkExecContext *exec;
AVFrame *frame;
while (1) {
/* Roll an execution context */
exec = ff_vk_exec_get(&pv->vkctx, &pv->e);
/* If it had a frame, immediately output it */
if (exec->had_submission) {
exec->had_submission = 0;
pv->in_flight--;
return get_packet(avctx, exec, pkt);
}
/* Get next frame to encode */
frame = pv->frame;
err = ff_encode_get_frame(avctx, frame);
if (err < 0 && err != AVERROR_EOF) {
return err;
} else if (err == AVERROR_EOF) {
if (!pv->in_flight)
return err;
continue;
}
/* Encode frame */
pd = exec->opaque;
pd->color_primaries = frame->color_primaries;
pd->color_trc = frame->color_trc;
pd->colorspace = frame->colorspace;
pd->pts = frame->pts;
pd->duration = frame->duration;
pd->flags = frame->flags;
if (avctx->flags & AV_CODEC_FLAG_COPY_OPAQUE) {
pd->frame_opaque = frame->opaque;
pd->frame_opaque_ref = frame->opaque_ref;
frame->opaque_ref = NULL;
}
err = vulkan_encode_prores_submit_frame(avctx, exec, frame, 0);
if (ctx->pictures_per_frame > 1)
vulkan_encode_prores_submit_frame(avctx, exec, frame, 1);
/* Submit execution context */
ff_vk_exec_submit(&pv->vkctx, exec);
av_frame_unref(frame);
if (err < 0)
return err;
pv->in_flight++;
if (pv->in_flight < pv->async_depth)
return AVERROR(EAGAIN);
}
return 0;
}
static av_cold int encode_close(AVCodecContext *avctx)
{
ProresVulkanContext *pv = avctx->priv_data;
ProresContext *ctx = &pv->ctx;
FFVulkanContext *vkctx = &pv->vkctx;
ff_vk_exec_pool_free(vkctx, &pv->e);
ff_vk_exec_pool_free(vkctx, &pv->transfer_exec_pool);
if (ctx->alpha_bits)
ff_vk_shader_free(vkctx, &pv->alpha_data_shd);
ff_vk_shader_free(vkctx, &pv->slice_data_shd[0]);
ff_vk_shader_free(vkctx, &pv->slice_data_shd[1]);
ff_vk_shader_free(vkctx, &pv->estimate_slice_shd);
ff_vk_shader_free(vkctx, &pv->encode_slice_shd);
ff_vk_shader_free(vkctx, &pv->trellis_node_shd);
ff_vk_free_buf(vkctx, &pv->prores_data_tables_buf);
av_buffer_pool_uninit(&pv->pkt_buf_pool);
av_buffer_pool_uninit(&pv->slice_data_buf_pool);
av_buffer_pool_uninit(&pv->slice_score_buf_pool);
av_buffer_pool_uninit(&pv->frame_size_buf_pool);
ff_vk_uninit(vkctx);
return 0;
}
static av_cold int encode_init(AVCodecContext *avctx)
{
ProresVulkanContext *pv = avctx->priv_data;
ProresContext *ctx = &pv->ctx;
int err = 0, i, q;
FFVulkanContext *vkctx = &pv->vkctx;
/* Init vulkan */
RET(ff_vk_init(vkctx, avctx, NULL, avctx->hw_frames_ctx));
pv->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0);
if (!pv->qf) {
av_log(avctx, AV_LOG_ERROR, "Device has no compute queues!\n");
return AVERROR(ENOTSUP);
}
RET(ff_vk_exec_pool_init(vkctx, pv->qf, &pv->e, 1, 0, 0, 0, NULL));
pv->transfer_qf = ff_vk_qf_find(vkctx, VK_QUEUE_TRANSFER_BIT, 0);
if (!pv->transfer_qf) {
av_log(avctx, AV_LOG_ERROR, "Device has no transfer queues!\n");
return err;
}
RET(ff_vk_exec_pool_init(vkctx, pv->transfer_qf, &pv->transfer_exec_pool,
pv->async_depth, 0, 0, 0, NULL));
/* Init common prores structures */
err = ff_prores_kostya_encode_init(avctx, ctx, vkctx->frames->sw_format);
if (err < 0)
return err;
/* Temporary frame */
pv->frame = av_frame_alloc();
if (!pv->frame)
return AVERROR(ENOMEM);
/* Async data pool */
pv->async_depth = pv->e.pool_size;
pv->exec_ctx_info = av_calloc(pv->async_depth, sizeof(*pv->exec_ctx_info));
if (!pv->exec_ctx_info)
return AVERROR(ENOMEM);
for (int i = 0; i < pv->async_depth; i++)
pv->e.contexts[i].opaque = &pv->exec_ctx_info[i];
/* Compile shaders used by encoder */
init_slice_data_pipeline(pv, &pv->slice_data_shd[0], 2);
init_slice_data_pipeline(pv, &pv->slice_data_shd[1], 4);
init_estimate_slice_pipeline(pv, &pv->estimate_slice_shd);
init_trellis_node_pipeline(pv, &pv->trellis_node_shd);
init_encode_slice_pipeline(pv, &pv->encode_slice_shd);
if (ctx->alpha_bits)
init_alpha_data_pipeline(pv, &pv->alpha_data_shd);
/* Create prores data tables uniform buffer. */
RET(ff_vk_create_buf(vkctx, &pv->prores_data_tables_buf,
sizeof(ProresDataTables), NULL, NULL,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT));
RET(ff_vk_map_buffer(vkctx, &pv->prores_data_tables_buf, (void *)&pv->tables, 0));
for (q = 0; q < MAX_STORED_Q; ++q) {
for (i = 0; i < 64; i++) {
pv->tables->qmat[q][i] = ctx->quants[q][ctx->scantable[i]];
pv->tables->qmat_chroma[q][i] = ctx->quants_chroma[q][ctx->scantable[i]];
}
}
for (q = MAX_STORED_Q; q < 128; ++q) {
for (i = 0; i < 64; i++) {
pv->tables->qmat[q][i] = ctx->quant_mat[ctx->scantable[i]] * q;
pv->tables->qmat_chroma[q][i] = ctx->quant_chroma_mat[ctx->scantable[i]] * q;
}
}
fail:
return err;
}
#define OFFSET(x) offsetof(ProresVulkanContext, x)
#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
{ "mbs_per_slice", "macroblocks per slice", OFFSET(ctx.mbs_per_slice),
AV_OPT_TYPE_INT, { .i64 = 8 }, 1, MAX_MBS_PER_SLICE, VE },
{ "profile", NULL, OFFSET(ctx.profile), AV_OPT_TYPE_INT,
{ .i64 = PRORES_PROFILE_AUTO },
PRORES_PROFILE_AUTO, PRORES_PROFILE_4444XQ, VE, .unit = "profile" },
{ "auto", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PRORES_PROFILE_AUTO },
0, 0, VE, .unit = "profile" },
{ "proxy", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PRORES_PROFILE_PROXY },
0, 0, VE, .unit = "profile" },
{ "lt", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PRORES_PROFILE_LT },
0, 0, VE, .unit = "profile" },
{ "standard", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PRORES_PROFILE_STANDARD },
0, 0, VE, .unit = "profile" },
{ "hq", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PRORES_PROFILE_HQ },
0, 0, VE, .unit = "profile" },
{ "4444", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PRORES_PROFILE_4444 },
0, 0, VE, .unit = "profile" },
{ "4444xq", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = PRORES_PROFILE_4444XQ },
0, 0, VE, .unit = "profile" },
{ "vendor", "vendor ID", OFFSET(ctx.vendor),
AV_OPT_TYPE_STRING, { .str = "Lavc" }, 0, 0, VE },
{ "bits_per_mb", "desired bits per macroblock", OFFSET(ctx.bits_per_mb),
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 8192, VE },
{ "quant_mat", "quantiser matrix", OFFSET(ctx.quant_sel), AV_OPT_TYPE_INT,
{ .i64 = -1 }, -1, QUANT_MAT_DEFAULT, VE, .unit = "quant_mat" },
{ "auto", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = -1 },
0, 0, VE, .unit = "quant_mat" },
{ "proxy", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QUANT_MAT_PROXY },
0, 0, VE, .unit = "quant_mat" },
{ "lt", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QUANT_MAT_LT },
0, 0, VE, .unit = "quant_mat" },
{ "standard", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QUANT_MAT_STANDARD },
0, 0, VE, .unit = "quant_mat" },
{ "hq", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QUANT_MAT_HQ },
0, 0, VE, .unit = "quant_mat" },
{ "default", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QUANT_MAT_DEFAULT },
0, 0, VE, .unit = "quant_mat" },
{ "alpha_bits", "bits for alpha plane", OFFSET(ctx.alpha_bits), AV_OPT_TYPE_INT,
{ .i64 = 16 }, 0, 16, VE },
{ "async_depth", "Internal parallelization depth", OFFSET(async_depth), AV_OPT_TYPE_INT,
{ .i64 = 1 }, 1, INT_MAX, VE },
{ NULL }
};
static const AVClass proresenc_class = {
.class_name = "ProRes vulkan encoder",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
};
static const AVCodecHWConfigInternal *const prores_ks_hw_configs[] = {
HW_CONFIG_ENCODER_FRAMES(VULKAN, VULKAN),
HW_CONFIG_ENCODER_DEVICE(NONE, VULKAN),
NULL,
};
const FFCodec ff_prores_ks_vulkan_encoder = {
.p.name = "prores_ks_vulkan",
CODEC_LONG_NAME("Apple ProRes (iCodec Pro)"),
.p.type = AVMEDIA_TYPE_VIDEO,
.p.id = AV_CODEC_ID_PRORES,
.priv_data_size = sizeof(ProresVulkanContext),
.init = encode_init,
.close = encode_close,
FF_CODEC_RECEIVE_PACKET_CB(&vulkan_encode_prores_receive_packet),
.p.capabilities = AV_CODEC_CAP_DELAY |
AV_CODEC_CAP_HARDWARE |
AV_CODEC_CAP_ENCODER_FLUSH |
AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
CODEC_PIXFMTS(AV_PIX_FMT_VULKAN),
.hw_configs = prores_ks_hw_configs,
.color_ranges = AVCOL_RANGE_MPEG,
.p.priv_class = &proresenc_class,
.p.profiles = NULL_IF_CONFIG_SMALL(ff_prores_profiles),
.caps_internal = FF_CODEC_CAP_INIT_CLEANUP | FF_CODEC_CAP_EOF_FLUSH,
};