PR #23680 opened by Lynne URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23680 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23680.patch
This adds a simple, dumb DNxHD encoder that codes with a constant quantizer. >From 2eb2cf3533a78a691f008e8fb8504842ecb45aed Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Thu, 2 Jul 2026 18:25:02 +0900 Subject: [PATCH] avcodec/dnxhdenc: add a Vulkan DNxHD encoder This adds a simple, dumb DNxHD encoder that codes with a constant quantizer. --- configure | 1 + libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/dnxhdenc_vulkan.c | 757 ++++++++++++++++++++++ libavcodec/vulkan/Makefile | 3 + libavcodec/vulkan/dnxhd_enc.comp.glsl | 138 ++++ libavcodec/vulkan/dnxhd_enc_dct.comp.glsl | 131 ++++ 7 files changed, 1032 insertions(+) create mode 100644 libavcodec/dnxhdenc_vulkan.c create mode 100644 libavcodec/vulkan/dnxhd_enc.comp.glsl create mode 100644 libavcodec/vulkan/dnxhd_enc_dct.comp.glsl diff --git a/configure b/configure index 070dd3f40f..86e9ab6927 100755 --- a/configure +++ b/configure @@ -3179,6 +3179,7 @@ dds_decoder_select="texturedsp" dirac_decoder_select="dirac_parse dwt golomb mpegvideoencdsp qpeldsp videodsp" dnxhd_decoder_select="blockdsp idctdsp" dnxhd_encoder_select="blockdsp fdctdsp idctdsp mpegvideoenc pixblockdsp videodsp" +dnxhd_vulkan_encoder_select="vulkan spirv_compiler" dvvideo_decoder_select="dvprofile idctdsp" dvvideo_encoder_select="dvprofile fdctdsp me_cmp pixblockdsp" dxa_decoder_deps="zlib" diff --git a/libavcodec/Makefile b/libavcodec/Makefile index b0e9e4f17b..f6802eea6f 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -341,6 +341,7 @@ OBJS-$(CONFIG_DFPWM_DECODER) += dfpwmdec.o OBJS-$(CONFIG_DFPWM_ENCODER) += dfpwmenc.o OBJS-$(CONFIG_DNXHD_DECODER) += dnxhddec.o dnxhddata.o OBJS-$(CONFIG_DNXHD_ENCODER) += dnxhdenc.o dnxhddata.o +OBJS-$(CONFIG_DNXHD_VULKAN_ENCODER) += dnxhdenc_vulkan.o dnxhddata.o OBJS-$(CONFIG_DOLBY_E_DECODER) += dolby_e.o dolby_e_parse.o kbdwin.o OBJS-$(CONFIG_DPX_DECODER) += dpx.o OBJS-$(CONFIG_DPX_ENCODER) += dpxenc.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index e1668f1e80..21972c6df9 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -93,6 +93,7 @@ extern const FFCodec ff_dds_decoder; extern const FFCodec ff_dfa_decoder; extern const FFCodec ff_dirac_decoder; extern const FFCodec ff_dnxhd_encoder; +extern const FFCodec ff_dnxhd_vulkan_encoder; extern const FFCodec ff_dnxhd_decoder; extern const FFCodec ff_dpx_encoder; extern const FFCodec ff_dpx_decoder; diff --git a/libavcodec/dnxhdenc_vulkan.c b/libavcodec/dnxhdenc_vulkan.c new file mode 100644 index 0000000000..e189a4cfd1 --- /dev/null +++ b/libavcodec/dnxhdenc_vulkan.c @@ -0,0 +1,757 @@ +/* + * Copyright (c) 2026 Lynne <[email protected]> + * + * 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/mem.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavutil/vulkan.h" +#include "libavutil/vulkan_spirv.h" + +#include "avcodec.h" +#include "codec_internal.h" +#include "encode.h" +#include "hwconfig.h" +#include "dnxhddata.h" + +extern const unsigned char ff_dnxhd_enc_dct_comp_spv_data[]; +extern const unsigned int ff_dnxhd_enc_dct_comp_spv_len; + +extern const unsigned char ff_dnxhd_enc_comp_spv_data[]; +extern const unsigned int ff_dnxhd_enc_comp_spv_len; + +#define BLOCKS_PER_MB 8 + +/* VLC tables, uploaded once and shared by every frame. Mirrors the layout + * the entropy shader reads. Index into vlc_* is (level*2 | run) + 2048. */ +typedef struct DNXHDTables { + uint32_t vlc_codes[4096]; + uint32_t vlc_bits[4096]; + uint32_t run_codes[64]; + uint32_t run_bits[64]; + uint32_t dc_codes[16]; + uint32_t dc_bits[16]; +} DNXHDTables; + +typedef struct DNXHDWeights { + uint8_t luma_weight[64]; + uint8_t chroma_weight[64]; +} DNXHDWeights; + +typedef struct DCTPushData { + int mb_width; + int qscale; + float dct_scale; +} DCTPushData; + +typedef struct EntropyPushData { + VkDeviceAddress bytestream; + int mb_width; + int mb_height; + int qscale; + uint32_t slice_max; +} EntropyPushData; + +typedef struct VulkanEncodeDNXHDFrameData { + AVBufferRef *coeffs_ref; + AVBufferRef *bytestream_ref; + AVBufferRef *sizes_ref; + + int64_t pts; + int64_t duration; + void *frame_opaque; + AVBufferRef *frame_opaque_ref; +} VulkanEncodeDNXHDFrameData; + +typedef struct VulkanEncodeDNXHDContext { + const AVClass *class; + + FFVulkanContext s; + AVVulkanDeviceQueueFamily *qf; + FFVkExecPool exec_pool; + + FFVulkanShader shd_dct; + FFVulkanShader shd_entropy; + + /* Constant tables, built at init and kept for the encoder's lifetime. */ + AVBufferPool *tables_pool; + AVBufferRef *tables_ref; + AVBufferPool *weights_pool; + AVBufferRef *weights_ref; + + /* Per-frame pools */ + AVBufferPool *coeffs_pool; + AVBufferPool *bytestream_pool; + AVBufferPool *sizes_pool; + + AVFrame *frame; + + int async_depth; + int in_flight; + VulkanEncodeDNXHDFrameData *exec_ctx_info; + + /* Derived encoder state */ + int cid; + const CIDEntry *cid_table; + int mb_width, mb_height; + int data_offset; + int coding_unit_size; + int frame_size; + uint32_t slice_max; /* bytes reserved per slice */ + size_t coeffs_size; + size_t bytestream_size; + size_t sizes_size; + + /* Options */ + int qscale; + int profile; + double dct_scale; +} VulkanEncodeDNXHDContext; + +/* + * Build the level/run VLC lookup the entropy shader indexes directly. This + * mirrors dnxhd_init_vlc(): for every (level, run) it locates the AC symbol in + * the CID table and folds in the sign and any index-extension bits. + */ +static void build_tables(DNXHDTables *t, const CIDEntry *cid, int bit_depth) +{ + int max_level = 1 << (bit_depth + 2); + + for (int level = -max_level; level < max_level; level++) { + for (int run = 0; run < 2; run++) { + int index = (level * 2 | run) + max_level * 2; + int sign = level >> 31; + int alevel = (level ^ sign) - sign; + int offset = 0; + uint32_t code = 0, bits = 0; + + if (alevel > 64) { + offset = (alevel - 1) >> 6; + alevel -= offset << 6; + } + for (int j = 0; j < 257; j++) { + if (cid->ac_info[2 * j + 0] >> 1 == alevel && + (!offset || (cid->ac_info[2 * j + 1] & 1)) && + (!run || (cid->ac_info[2 * j + 1] & 2))) { + if (alevel) { + code = (cid->ac_codes[j] << 1) | (sign & 1); + bits = cid->ac_bits[j] + 1; + } else { + code = cid->ac_codes[j]; + bits = cid->ac_bits[j]; + } + break; + } + } + if (offset) { + code = (code << cid->index_bits) | offset; + bits += cid->index_bits; + } + t->vlc_codes[index] = code; + t->vlc_bits[index] = bits; + } + } + + for (int i = 0; i < 62; i++) { + int run = cid->run[i]; + t->run_codes[run] = cid->run_codes[i]; + t->run_bits[run] = cid->run_bits[i]; + } + + for (int i = 0; i < bit_depth + 4; i++) { + t->dc_codes[i] = cid->dc_codes[i]; + t->dc_bits[i] = cid->dc_bits[i]; + } +} + +static int alloc_const_buffer(VulkanEncodeDNXHDContext *ev, AVBufferPool **pool, + AVBufferRef **ref, const void *data, size_t size) +{ + int err = ff_vk_get_pooled_buffer(&ev->s, pool, ref, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + NULL, size, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + if (err < 0) + return err; + + FFVkBuffer *buf = (FFVkBuffer *)(*ref)->data; + memcpy(buf->mapped_mem, data, size); + return 0; +} + +static int init_dct_shader(AVCodecContext *avctx) +{ + int err; + VulkanEncodeDNXHDContext *ev = avctx->priv_data; + FFVulkanShader *shd = &ev->shd_dct; + + SPEC_LIST_CREATE(sl, 2, 2 * sizeof(uint32_t)) + SPEC_LIST_ADD(sl, 16, 32, BLOCKS_PER_MB); /* nb_blocks */ + SPEC_LIST_ADD(sl, 17, 32, 1); /* nb_components */ + + ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, sl, + (uint32_t []) { 8, BLOCKS_PER_MB, 1 }, 0); + + ff_vk_shader_add_push_const(shd, 0, sizeof(DCTPushData), + VK_SHADER_STAGE_COMPUTE_BIT); + + const FFVulkanDescriptorSetBinding desc_set[] = { + { + .name = "coeffs_buf", + .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + }, + { + .name = "src", + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .elems = av_pix_fmt_count_planes(AV_PIX_FMT_YUV422P), + }, + { + .name = "weights_buf", + .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + }, + }; + RET(ff_vk_shader_add_descriptor_set(&ev->s, shd, desc_set, 3, 0, 0)); + + RET(ff_vk_shader_link(&ev->s, shd, + ff_dnxhd_enc_dct_comp_spv_data, + ff_dnxhd_enc_dct_comp_spv_len, "main")); + + RET(ff_vk_shader_register_exec(&ev->s, &ev->exec_pool, shd)); + +fail: + return err; +} + +static int init_entropy_shader(AVCodecContext *avctx) +{ + int err; + VulkanEncodeDNXHDContext *ev = avctx->priv_data; + FFVulkanShader *shd = &ev->shd_entropy; + + ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL, + (uint32_t []) { 1, 1, 1 }, 0); + + ff_vk_shader_add_push_const(shd, 0, sizeof(EntropyPushData), + VK_SHADER_STAGE_COMPUTE_BIT); + + const FFVulkanDescriptorSetBinding desc_set[] = { + { + .name = "coeffs_buf", + .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + }, + { + .name = "tables_buf", + .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + }, + { + .name = "sizes_buf", + .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + }, + }; + RET(ff_vk_shader_add_descriptor_set(&ev->s, shd, desc_set, 3, 0, 0)); + + RET(ff_vk_shader_link(&ev->s, shd, + ff_dnxhd_enc_comp_spv_data, + ff_dnxhd_enc_comp_spv_len, "main")); + + RET(ff_vk_shader_register_exec(&ev->s, &ev->exec_pool, shd)); + +fail: + return err; +} + +static int submit_frame(AVCodecContext *avctx, FFVkExecContext *exec, + AVFrame *frame) +{ + int err = 0; + VulkanEncodeDNXHDContext *ev = avctx->priv_data; + FFVulkanFunctions *vk = &ev->s.vkfn; + VulkanEncodeDNXHDFrameData *fd = exec->opaque; + VkImageView views[AV_NUM_DATA_POINTERS]; + + VkImageMemoryBarrier2 img_bar[AV_NUM_DATA_POINTERS]; + int nb_img_bar = 0; + VkBufferMemoryBarrier2 buf_bar[4]; + int nb_buf_bar = 0; + + FFVkBuffer *coeffs_buf, *bytestream_buf, *sizes_buf; + FFVkBuffer *tables_buf = (FFVkBuffer *)ev->tables_ref->data; + FFVkBuffer *weights_buf = (FFVkBuffer *)ev->weights_ref->data; + + RET(ff_vk_get_pooled_buffer(&ev->s, &ev->coeffs_pool, &fd->coeffs_ref, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + NULL, ev->coeffs_size, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)); + coeffs_buf = (FFVkBuffer *)fd->coeffs_ref->data; + + /* Entropy output, read back by the CPU. Host-visible and host-cached so + * the readback is a cached copy. */ + RET(ff_vk_get_pooled_buffer(&ev->s, &ev->bytestream_pool, + &fd->bytestream_ref, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + NULL, ev->bytestream_size, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_CACHED_BIT)); + bytestream_buf = (FFVkBuffer *)fd->bytestream_ref->data; + + RET(ff_vk_get_pooled_buffer(&ev->s, &ev->sizes_pool, &fd->sizes_ref, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + NULL, ev->sizes_size, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)); + sizes_buf = (FFVkBuffer *)fd->sizes_ref->data; + + ff_vk_exec_start(&ev->s, exec); + + ff_vk_exec_add_dep_buf(&ev->s, exec, &fd->coeffs_ref, 1, 1); + ff_vk_exec_add_dep_buf(&ev->s, exec, &fd->bytestream_ref, 1, 1); + ff_vk_exec_add_dep_buf(&ev->s, exec, &fd->sizes_ref, 1, 1); + + RET(ff_vk_exec_add_dep_frame(&ev->s, exec, frame, + VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT)); + + RET(ff_vk_create_imageviews(&ev->s, exec, views, frame, FF_VK_REP_UINT)); + + ff_vk_frame_barrier(&ev->s, 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); + + vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pImageMemoryBarriers = img_bar, + .imageMemoryBarrierCount = nb_img_bar, + }); + nb_img_bar = 0; + + /* DCT + quantize */ + { + DCTPushData pd = { + .mb_width = ev->mb_width, + .qscale = ev->qscale, + .dct_scale = (float)ev->dct_scale, + }; + + ff_vk_shader_update_desc_buffer(&ev->s, exec, &ev->shd_dct, 0, 0, 0, + coeffs_buf, 0, coeffs_buf->size, + VK_FORMAT_UNDEFINED); + ff_vk_shader_update_img_array(&ev->s, exec, &ev->shd_dct, frame, views, + 0, 1, VK_IMAGE_LAYOUT_GENERAL, + VK_NULL_HANDLE); + ff_vk_shader_update_desc_buffer(&ev->s, exec, &ev->shd_dct, 0, 2, 0, + weights_buf, 0, weights_buf->size, + VK_FORMAT_UNDEFINED); + + ff_vk_exec_bind_shader(&ev->s, exec, &ev->shd_dct); + ff_vk_shader_update_push_const(&ev->s, exec, &ev->shd_dct, + VK_SHADER_STAGE_COMPUTE_BIT, + 0, sizeof(pd), &pd); + + vk->CmdDispatch(exec->buf, ev->mb_width, ev->mb_height, 1); + } + + ff_vk_buf_barrier(buf_bar[nb_buf_bar++], coeffs_buf, + COMPUTE_SHADER_BIT, SHADER_WRITE_BIT, NONE, + COMPUTE_SHADER_BIT, SHADER_READ_BIT, NONE, + 0, coeffs_buf->size); + vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) { + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pBufferMemoryBarriers = buf_bar, + .bufferMemoryBarrierCount = nb_buf_bar, + }); + nb_buf_bar = 0; + + /* Entropy coding, one workgroup per slice. */ + { + EntropyPushData pd = { + .bytestream = bytestream_buf->address, + .mb_width = ev->mb_width, + .mb_height = ev->mb_height, + .qscale = ev->qscale, + .slice_max = ev->slice_max, + }; + + ff_vk_shader_update_desc_buffer(&ev->s, exec, &ev->shd_entropy, 0, 0, 0, + coeffs_buf, 0, coeffs_buf->size, + VK_FORMAT_UNDEFINED); + ff_vk_shader_update_desc_buffer(&ev->s, exec, &ev->shd_entropy, 0, 1, 0, + tables_buf, 0, tables_buf->size, + VK_FORMAT_UNDEFINED); + ff_vk_shader_update_desc_buffer(&ev->s, exec, &ev->shd_entropy, 0, 2, 0, + sizes_buf, 0, sizes_buf->size, + VK_FORMAT_UNDEFINED); + + ff_vk_exec_bind_shader(&ev->s, exec, &ev->shd_entropy); + ff_vk_shader_update_push_const(&ev->s, exec, &ev->shd_entropy, + VK_SHADER_STAGE_COMPUTE_BIT, + 0, sizeof(pd), &pd); + + vk->CmdDispatch(exec->buf, ev->mb_height, 1, 1); + } + + err = ff_vk_exec_submit(&ev->s, exec); + if (err < 0) + return err; + + return 0; + +fail: + ff_vk_exec_discard_deps(&ev->s, exec); + return err; +} + +/* Fixed-layout frame header for progressive 8-bit 4:2:2. */ +static void write_header(VulkanEncodeDNXHDContext *ev, AVCodecContext *avctx, + uint8_t *buf) +{ + memset(buf, 0, ev->data_offset); + + AV_WB16(buf + 0x02, ev->data_offset); + buf[4] = 0x01; + buf[5] = 0x01; + buf[6] = 0x80; + buf[7] = 0xa0; + AV_WB16(buf + 0x18, avctx->height); + AV_WB16(buf + 0x1a, avctx->width); + AV_WB16(buf + 0x1d, avctx->height); + buf[0x21] = 0x38; /* 8-bit */ + buf[0x22] = 0x88; + AV_WB32(buf + 0x28, ev->cid); + buf[0x2c] = 0x80; /* progressive, 4:2:2 */ + buf[0x5f] = 0x01; + buf[0x167] = 0x02; + AV_WB16(buf + 0x16a, ev->mb_height * 4 + 4); + AV_WB16(buf + 0x16c, ev->mb_height); + buf[0x16f] = 0x10; +} + +static int build_packet(AVCodecContext *avctx, FFVkExecContext *exec, + AVPacket *pkt) +{ + int err; + VulkanEncodeDNXHDContext *ev = avctx->priv_data; + VulkanEncodeDNXHDFrameData *fd = exec->opaque; + FFVkBuffer *bytestream_buf = (FFVkBuffer *)fd->bytestream_ref->data; + FFVkBuffer *sizes_buf = (FFVkBuffer *)fd->sizes_ref->data; + uint8_t *buf, *msip; + + ff_vk_exec_wait(&ev->s, exec); + + const uint32_t *sizes = (const uint32_t *)sizes_buf->mapped_mem; + const uint8_t *payload = bytestream_buf->mapped_mem; + + err = ff_get_encode_buffer(avctx, pkt, ev->frame_size, 0); + if (err < 0) + goto done; + buf = pkt->data; + + write_header(ev, avctx, buf); + msip = buf + 0x170; + + /* Each slice occupies a fixed region, so its offset is just its index + * times the reserved size. Warn if any slice overflowed its budget. */ + for (int i = 0; i < ev->mb_height; i++) { + AV_WB32(msip + i * 4, (uint32_t)i * ev->slice_max); + if (sizes[i] > ev->slice_max) + av_log(avctx, AV_LOG_WARNING, + "Slice %d overflowed its budget (%u > %u bytes); " + "raise qscale.\n", i, sizes[i], ev->slice_max); + } + + memcpy(buf + ev->data_offset, payload, + (size_t)ev->mb_height * ev->slice_max); + + memset(buf + ev->data_offset + (size_t)ev->mb_height * ev->slice_max, 0, + ev->coding_unit_size - 4 - ev->data_offset - + (size_t)ev->mb_height * ev->slice_max); + + AV_WB32(buf + ev->coding_unit_size - 4, 0x600DC0DE); + + pkt->pts = fd->pts; + pkt->dts = fd->pts; + pkt->duration = fd->duration; + pkt->flags |= AV_PKT_FLAG_KEY; + + if (avctx->flags & AV_CODEC_FLAG_COPY_OPAQUE) { + pkt->opaque = fd->frame_opaque; + pkt->opaque_ref = fd->frame_opaque_ref; + fd->frame_opaque_ref = NULL; + } + + err = 0; +done: + av_buffer_unref(&fd->coeffs_ref); + av_buffer_unref(&fd->bytestream_ref); + av_buffer_unref(&fd->sizes_ref); + return err; +} + +static int vulkan_encode_dnxhd_receive_packet(AVCodecContext *avctx, + AVPacket *pkt) +{ + int err; + VulkanEncodeDNXHDContext *ev = avctx->priv_data; + VulkanEncodeDNXHDFrameData *fd; + FFVkExecContext *exec; + AVFrame *frame; + + while (1) { + exec = ff_vk_exec_get(&ev->s, &ev->exec_pool); + + if (exec->had_submission) { + exec->had_submission = 0; + ev->in_flight--; + return build_packet(avctx, exec, pkt); + } + + frame = ev->frame; + err = ff_encode_get_frame(avctx, frame); + if (err < 0 && err != AVERROR_EOF) + return err; + else if (err == AVERROR_EOF) { + if (!ev->in_flight) + return err; + continue; + } + + fd = exec->opaque; + fd->pts = frame->pts; + fd->duration = frame->duration; + if (avctx->flags & AV_CODEC_FLAG_COPY_OPAQUE) { + fd->frame_opaque = frame->opaque; + fd->frame_opaque_ref = frame->opaque_ref; + frame->opaque_ref = NULL; + } + + err = submit_frame(avctx, exec, frame); + av_frame_unref(frame); + if (err < 0) + return err; + + ev->in_flight++; + if (ev->in_flight < ev->async_depth) + return AVERROR(EAGAIN); + } +} + +static av_cold int vulkan_encode_dnxhd_close(AVCodecContext *avctx) +{ + VulkanEncodeDNXHDContext *ev = avctx->priv_data; + + ff_vk_exec_pool_free(&ev->s, &ev->exec_pool); + ff_vk_shader_free(&ev->s, &ev->shd_dct); + ff_vk_shader_free(&ev->s, &ev->shd_entropy); + + if (ev->exec_ctx_info) { + for (int i = 0; i < ev->async_depth; i++) { + VulkanEncodeDNXHDFrameData *fd = &ev->exec_ctx_info[i]; + av_buffer_unref(&fd->coeffs_ref); + av_buffer_unref(&fd->bytestream_ref); + av_buffer_unref(&fd->sizes_ref); + av_buffer_unref(&fd->frame_opaque_ref); + } + av_freep(&ev->exec_ctx_info); + } + + av_buffer_unref(&ev->tables_ref); + av_buffer_unref(&ev->weights_ref); + av_buffer_pool_uninit(&ev->tables_pool); + av_buffer_pool_uninit(&ev->weights_pool); + av_buffer_pool_uninit(&ev->coeffs_pool); + av_buffer_pool_uninit(&ev->bytestream_pool); + av_buffer_pool_uninit(&ev->sizes_pool); + + av_frame_free(&ev->frame); + ff_vk_uninit(&ev->s); + + return 0; +} + +static av_cold int vulkan_encode_dnxhd_init(AVCodecContext *avctx) +{ + int err; + VulkanEncodeDNXHDContext *ev = avctx->priv_data; + AVHWFramesContext *hwfc; + DNXHDTables *tables = NULL; + DNXHDWeights weights; + + if (!avctx->hw_frames_ctx) { + av_log(avctx, AV_LOG_ERROR, "An AVHWFramesContext is required.\n"); + return AVERROR(EINVAL); + } + hwfc = (AVHWFramesContext *)avctx->hw_frames_ctx->data; + if (hwfc->sw_format != AV_PIX_FMT_YUV422P) { + av_log(avctx, AV_LOG_ERROR, + "Only 8-bit 4:2:2 (yuv422p) is supported.\n"); + return AVERROR(EINVAL); + } + + avctx->profile = ev->profile; + ev->cid = ff_dnxhd_find_cid(avctx, 8); + if (!ev->cid) { + av_log(avctx, AV_LOG_ERROR, + "No DNxHD profile matches %dx%d at %"PRId64" bit/s. " + "Set an explicit -b:v.\n", + avctx->width, avctx->height, avctx->bit_rate); + return AVERROR(EINVAL); + } + ev->cid_table = ff_dnxhd_get_cid_table(ev->cid); + av_log(avctx, AV_LOG_VERBOSE, "Selected DNxHD CID %d\n", ev->cid); + + ev->mb_width = (avctx->width + 15) / 16; + ev->mb_height = (avctx->height + 15) / 16; + + ev->frame_size = ev->cid_table->frame_size; + ev->coding_unit_size = ev->cid_table->coding_unit_size; + ev->data_offset = ev->mb_height > 68 ? 0x170 + (ev->mb_height << 2) : 0x280; + + if (ev->qscale < 1 || ev->qscale > 1024) { + av_log(avctx, AV_LOG_ERROR, "qscale must be in [1, 1024].\n"); + return AVERROR(EINVAL); + } + + /* Split the payload into equal, 4-byte-aligned slice budgets. */ + int payload = ev->coding_unit_size - ev->data_offset - 4; + ev->slice_max = (payload / ev->mb_height) & ~3u; + if (ev->slice_max == 0) + return AVERROR(EINVAL); + + ev->coeffs_size = (size_t)ev->mb_width * ev->mb_height * + BLOCKS_PER_MB * 64 * sizeof(int16_t); + ev->bytestream_size = (size_t)ev->mb_height * ev->slice_max; + ev->sizes_size = (size_t)ev->mb_height * sizeof(uint32_t); + + av_log(avctx, AV_LOG_VERBOSE, + "DNxHD Vulkan: %dx%d, %dx%d MBs, qscale=%d, slice budget %u B, " + "frame %d B\n", avctx->width, avctx->height, ev->mb_width, + ev->mb_height, ev->qscale, ev->slice_max, ev->frame_size); + + err = ff_vk_init(&ev->s, avctx, NULL, avctx->hw_frames_ctx); + if (err < 0) + return err; + + ev->qf = ff_vk_qf_find(&ev->s, VK_QUEUE_COMPUTE_BIT, 0); + if (!ev->qf) { + av_log(avctx, AV_LOG_ERROR, "Device has no compute queues!\n"); + return AVERROR(ENOTSUP); + } + + err = ff_vk_exec_pool_init(&ev->s, ev->qf, &ev->exec_pool, + ev->async_depth, 0, 0, 0, NULL); + if (err < 0) + return err; + + RET(init_dct_shader(avctx)); + RET(init_entropy_shader(avctx)); + + /* Upload the constant VLC and weight tables. */ + tables = av_mallocz(sizeof(*tables)); + if (!tables) + return AVERROR(ENOMEM); + build_tables(tables, ev->cid_table, 8); + RET(alloc_const_buffer(ev, &ev->tables_pool, &ev->tables_ref, + tables, sizeof(*tables))); + + memcpy(weights.luma_weight, ev->cid_table->luma_weight, 64); + memcpy(weights.chroma_weight, ev->cid_table->chroma_weight, 64); + RET(alloc_const_buffer(ev, &ev->weights_pool, &ev->weights_ref, + &weights, sizeof(weights))); + + ev->frame = av_frame_alloc(); + if (!ev->frame) { + err = AVERROR(ENOMEM); + goto fail; + } + + ev->async_depth = ev->exec_pool.pool_size; + ev->exec_ctx_info = av_calloc(ev->async_depth, sizeof(*ev->exec_ctx_info)); + if (!ev->exec_ctx_info) { + err = AVERROR(ENOMEM); + goto fail; + } + for (int i = 0; i < ev->async_depth; i++) + ev->exec_pool.contexts[i].opaque = &ev->exec_ctx_info[i]; + + err = 0; +fail: + av_free(tables); + return err; +} + +#define OFFSET(x) offsetof(VulkanEncodeDNXHDContext, x) +#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM +static const AVOption vulkan_encode_dnxhd_options[] = { + { "quant", "Fixed quantization scale", OFFSET(qscale), + AV_OPT_TYPE_INT, { .i64 = 4 }, 1, 1024, VE }, + { "dct_scale", "Forward DCT scale (calibration knob)", OFFSET(dct_scale), + AV_OPT_TYPE_DOUBLE, { .dbl = 1.0 }, 0.001, 64.0, VE }, + { "profile", NULL, OFFSET(profile), AV_OPT_TYPE_INT, + { .i64 = AV_PROFILE_DNXHD }, AV_PROFILE_DNXHD, AV_PROFILE_DNXHD, VE, + .unit = "profile" }, + { "dnxhd", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_PROFILE_DNXHD }, + 0, 0, VE, .unit = "profile" }, + { "async_depth", "Internal parallelization depth", OFFSET(async_depth), + AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX, VE }, + { NULL } +}; + +static const AVClass vulkan_encode_dnxhd_class = { + .class_name = "dnxhd_vulkan", + .item_name = av_default_item_name, + .option = vulkan_encode_dnxhd_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +static const AVCodecHWConfigInternal *const vulkan_encode_dnxhd_hw_configs[] = { + HW_CONFIG_ENCODER_FRAMES(VULKAN, VULKAN), + NULL, +}; + +const FFCodec ff_dnxhd_vulkan_encoder = { + .p.name = "dnxhd_vulkan", + CODEC_LONG_NAME("VC3/DNxHD (Vulkan)"), + .p.type = AVMEDIA_TYPE_VIDEO, + .p.id = AV_CODEC_ID_DNXHD, + .priv_data_size = sizeof(VulkanEncodeDNXHDContext), + .init = &vulkan_encode_dnxhd_init, + FF_CODEC_RECEIVE_PACKET_CB(&vulkan_encode_dnxhd_receive_packet), + .close = &vulkan_encode_dnxhd_close, + .p.priv_class = &vulkan_encode_dnxhd_class, + .p.capabilities = AV_CODEC_CAP_DELAY | + AV_CODEC_CAP_HARDWARE | + AV_CODEC_CAP_DR1 | + AV_CODEC_CAP_ENCODER_FLUSH | + AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE, + .caps_internal = FF_CODEC_CAP_INIT_CLEANUP | FF_CODEC_CAP_EOF_FLUSH, + CODEC_PIXFMTS(AV_PIX_FMT_VULKAN), + .hw_configs = vulkan_encode_dnxhd_hw_configs, + .p.wrapper_name = "vulkan", +}; diff --git a/libavcodec/vulkan/Makefile b/libavcodec/vulkan/Makefile index 48afd639f4..fdf8776f30 100644 --- a/libavcodec/vulkan/Makefile +++ b/libavcodec/vulkan/Makefile @@ -8,6 +8,9 @@ OBJS-$(CONFIG_APV_VULKAN_ENCODER) += vulkan/apv_encode_dct.comp.spv.o \ vulkan/apv_encode_tiles.comp.spv.o \ vulkan/seg_gather.comp.spv.o +OBJS-$(CONFIG_DNXHD_VULKAN_ENCODER) += vulkan/dnxhd_enc_dct.comp.spv.o \ + vulkan/dnxhd_enc.comp.spv.o + OBJS-$(CONFIG_FFV1_VULKAN_ENCODER) += vulkan/ffv1_enc_setup.comp.spv.o \ vulkan/ffv1_enc_reset.comp.spv.o \ vulkan/ffv1_enc_reset_golomb.comp.spv.o \ diff --git a/libavcodec/vulkan/dnxhd_enc.comp.glsl b/libavcodec/vulkan/dnxhd_enc.comp.glsl new file mode 100644 index 0000000000..d1061927ae --- /dev/null +++ b/libavcodec/vulkan/dnxhd_enc.comp.glsl @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2026 Lynne <[email protected]> + * + * 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 + */ + +#version 460 +#pragma shader_stage(compute) +#extension GL_GOOGLE_include_directive : require + +#define PB_UNALIGNED +#include "common.glsl" + +#define BLOCKS_PER_MB 8 +#define DC_BIAS 1024 /* 1 << (bit_depth + 2), 8-bit */ +#define VLC_OFFSET 2048 /* max_level * 2, 8-bit */ + +layout (set = 0, binding = 0, scalar) readonly buffer coeffs_buf { + int16_t coeffs[]; +}; + +/* Precomputed VLC tables (built on the host from the CID entry). */ +layout (set = 0, binding = 1, scalar) readonly buffer tables_buf { + uint32_t vlc_codes[4096]; + uint32_t vlc_bits[4096]; + uint32_t run_codes[64]; + uint32_t run_bits[64]; + uint32_t dc_codes[16]; + uint32_t dc_bits[16]; +}; + +layout (set = 0, binding = 2, scalar) writeonly buffer sizes_buf { + uint32_t slice_sizes[]; +}; + +layout (push_constant, scalar) uniform pushConstants { + u8buf bytestream; + int mb_width; + int mb_height; + int qscale; + uint slice_max; /* bytes reserved per slice */ +}; + +/* ff_zigzag_direct: scan position -> raster index (y*8 + x). */ +const uint8_t zigzag[64] = { + uint8_t( 0), uint8_t( 1), uint8_t( 8), uint8_t(16), + uint8_t( 9), uint8_t( 2), uint8_t( 3), uint8_t(10), + uint8_t(17), uint8_t(24), uint8_t(32), uint8_t(25), + uint8_t(18), uint8_t(11), uint8_t( 4), uint8_t( 5), + uint8_t(12), uint8_t(19), uint8_t(26), uint8_t(33), + uint8_t(40), uint8_t(48), uint8_t(41), uint8_t(34), + uint8_t(27), uint8_t(20), uint8_t(13), uint8_t( 6), + uint8_t( 7), uint8_t(14), uint8_t(21), uint8_t(28), + uint8_t(35), uint8_t(42), uint8_t(49), uint8_t(56), + uint8_t(57), uint8_t(50), uint8_t(43), uint8_t(36), + uint8_t(29), uint8_t(22), uint8_t(15), uint8_t(23), + uint8_t(30), uint8_t(37), uint8_t(44), uint8_t(51), + uint8_t(58), uint8_t(59), uint8_t(52), uint8_t(45), + uint8_t(38), uint8_t(31), uint8_t(39), uint8_t(46), + uint8_t(53), uint8_t(60), uint8_t(61), uint8_t(54), + uint8_t(47), uint8_t(55), uint8_t(62), uint8_t(63), +}; + +/* Y, Cb, Cr for the eight blocks of a 4:2:2 macroblock. */ +const int blk_comp[8] = { 0, 0, 1, 2, 0, 0, 1, 2 }; + +void encode_dc(inout PutBitContext pb, int diff) +{ + uint m = diff < 0 ? uint(-2 * diff) : uint(2 * diff); + int nbits = m == 0u ? 0 : findMSB(m); + if (diff < 0) + diff -= 1; + + uint low = uint(diff) & ((1u << nbits) - 1u); + put_bits(pb, dc_bits[nbits] + uint(nbits), + (dc_codes[nbits] << nbits) + low); +} + +void encode_block(inout PutBitContext pb, uint base, inout int prev_dc) +{ + int dc = int(coeffs[base]); + encode_dc(pb, dc - prev_dc); + prev_dc = dc; + + int last_nz = 0; + for (int scan = 1; scan < 64; scan++) { + int level = int(coeffs[base + uint(zigzag[scan])]); + if (level != 0) { + int run = scan - last_nz - 1; + int idx = ((level << 1) | (run != 0 ? 1 : 0)) + VLC_OFFSET; + put_bits(pb, vlc_bits[idx], vlc_codes[idx]); + if (run != 0) + put_bits(pb, run_bits[run], run_codes[run]); + last_nz = scan; + } + } + put_bits(pb, vlc_bits[VLC_OFFSET], vlc_codes[VLC_OFFSET]); /* EOB */ +} + +void main(void) +{ + const int mb_y = int(gl_WorkGroupID.x); + + PutBitContext pb; + init_put_bits(pb, OFFBUF(u8buf, bytestream, uint(mb_y) * slice_max), + uint64_t(slice_max)); + + int prev_dc[3] = { DC_BIAS, DC_BIAS, DC_BIAS }; + + for (int mb_x = 0; mb_x < mb_width; mb_x++) { + put_bits(pb, 11, uint(qscale)); + put_bits(pb, 1, 0u); /* 4:2:2, not 4:4:4 */ + + uint mb_base = uint((mb_y * mb_width + mb_x) * BLOCKS_PER_MB) * 64u; + for (int i = 0; i < BLOCKS_PER_MB; i++) { + int c = blk_comp[i]; + encode_block(pb, mb_base + uint(i) * 64u, prev_dc[c]); + } + } + + uint bits = uint(put_bits_count(pb)); + flush_put_bits(pb); + slice_sizes[mb_y] = (bits + 7u) >> 3u; +} diff --git a/libavcodec/vulkan/dnxhd_enc_dct.comp.glsl b/libavcodec/vulkan/dnxhd_enc_dct.comp.glsl new file mode 100644 index 0000000000..f5d6fea41a --- /dev/null +++ b/libavcodec/vulkan/dnxhd_enc_dct.comp.glsl @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2026 Lynne <[email protected]> + * + * 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 + */ + +#version 460 +#pragma shader_stage(compute) +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : require + +#include "common.glsl" +#include "dct.glsl" + +/* 8-bit 4:2:2: 16x16 MB = 4 luma + 2 Cb + 2 Cr 8x8 blocks. */ +#define BLOCKS_PER_MB 8 + +/* Quantized coefficients, one MB after another, blocks in coding order, + * raster within a block. The entropy pass applies the zig-zag scan. */ +layout (set = 0, binding = 0, scalar) writeonly buffer coeffs_buf { + int16_t coeffs[]; +}; + +layout (set = 0, binding = 1) uniform readonly uimage2D src[]; + +/* Quantiser weights indexed by scan position (weight[0] unused). */ +layout (set = 0, binding = 2, scalar) readonly buffer weights_buf { + uint8_t luma_weight[64]; + uint8_t chroma_weight[64]; +}; + +layout (push_constant, scalar) uniform pushConstants { + int mb_width; + int qscale; + float dct_scale; /* orthonormal DCT to decoder-IDCT input scale */ +}; + +/* Inverse zig-zag: raster index -> scan position. */ +const uint8_t zz_inv[64] = { + uint8_t( 0), uint8_t( 1), uint8_t( 5), uint8_t( 6), + uint8_t(14), uint8_t(15), uint8_t(27), uint8_t(28), + uint8_t( 2), uint8_t( 4), uint8_t( 7), uint8_t(13), + uint8_t(16), uint8_t(26), uint8_t(29), uint8_t(42), + uint8_t( 3), uint8_t( 8), uint8_t(12), uint8_t(17), + uint8_t(25), uint8_t(30), uint8_t(41), uint8_t(43), + uint8_t( 9), uint8_t(11), uint8_t(18), uint8_t(24), + uint8_t(31), uint8_t(40), uint8_t(44), uint8_t(53), + uint8_t(10), uint8_t(19), uint8_t(23), uint8_t(32), + uint8_t(39), uint8_t(45), uint8_t(52), uint8_t(54), + uint8_t(20), uint8_t(22), uint8_t(33), uint8_t(38), + uint8_t(46), uint8_t(51), uint8_t(55), uint8_t(60), + uint8_t(21), uint8_t(34), uint8_t(37), uint8_t(47), + uint8_t(50), uint8_t(56), uint8_t(59), uint8_t(61), + uint8_t(35), uint8_t(36), uint8_t(48), uint8_t(49), + uint8_t(57), uint8_t(58), uint8_t(62), uint8_t(63), +}; + +/* Plane (0=Y, 1=Cb, 2=Cr) and pixel offset within the plane's MB region, + * per block in coding order. */ +const int blk_plane[8] = { 0, 0, 1, 2, 0, 0, 1, 2 }; +const ivec2 blk_off[8] = { + ivec2(0, 0), ivec2(8, 0), ivec2(0, 0), ivec2(0, 0), + ivec2(0, 8), ivec2(8, 8), ivec2(0, 8), ivec2(0, 8), +}; + +void main(void) +{ + const int mb_x = int(gl_WorkGroupID.x); + const int mb_y = int(gl_WorkGroupID.y); + const uint b = gl_LocalInvocationID.y; /* block in the MB */ + const uint r = gl_LocalInvocationID.x; /* row inside the block */ + + const int plane = blk_plane[b]; + const ivec2 mb_origin = (plane == 0) ? ivec2(mb_x * 16, mb_y * 16) + : ivec2(mb_x * 8, mb_y * 16); + const ivec2 blk_origin = mb_origin + blk_off[b]; + + /* plane varies per block within the workgroup, so the image-array index + * is not dynamically uniform and must be flagged nonuniform. */ + const ivec2 dim = imageSize(src[nonuniformEXT(plane)]); + + /* Load one row, clamped to the plane bounds. */ + [[unroll]] + for (int col = 0; col < 8; col++) { + ivec2 c = min(blk_origin + ivec2(col, int(r)), dim - ivec2(1)); + blocks[b][r * 9u + uint(col)] = + float(imageLoad(src[nonuniformEXT(plane)], c).x); + } + barrier(); + + fdct8(b, r, 9); /* vertical */ + barrier(); + fdct8(b, r * 9u, 1); /* horizontal */ + barrier(); + + const uint coeff_base = uint((mb_y * mb_width + mb_x) * BLOCKS_PER_MB + + int(b)) * 64u; + + [[unroll]] + for (int col = 0; col < 8; col++) { + const int p = int(r) * 8 + col; + const float c = blocks[b][r * 9u + uint(col)]; + int level; + + if (p == 0) { + /* DC is coded differentially at full precision, no weighting. */ + level = int(round(c * dct_scale)); + } else { + const int scan = int(zz_inv[p]); + const int w = int(plane == 0 ? luma_weight[scan] : chroma_weight[scan]); + const float x = c * dct_scale * 64.0f / float(qscale * w); + level = int(sign(x) * floor(abs(x))); + level = clamp(level, -1023, 1023); + } + coeffs[coeff_base + uint(p)] = int16_t(level); + } +} -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
