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]

Reply via email to