[PR] avcodec/dnxhdenc: add a Vulkan DNxHD encoder (PR #23680)

Lynne via ffmpeg-devel <[email protected]>
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178298460711.59.14487377383327676484@29965ddac10e>
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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.