../../../ffmpeg/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 / 381
0.0%
Branches:
0 / 171
0.0%
Functions:
0 / 12
0.0%

libavcodec/apv_encode_vulkan.c
Line Branch Exec Source
1 /*
2 * Copyright (c) 2026 Lynne <dev@lynne.ee>
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include <math.h>
22 #include <stdlib.h>
23
24 #include "libavutil/mem.h"
25 #include "libavutil/opt.h"
26 #include "libavutil/pixdesc.h"
27 #include "libavutil/vulkan.h"
28
29 #include "avcodec.h"
30 #include "codec_internal.h"
31 #include "encode.h"
32 #include "hwconfig.h"
33 #include "vulkan_video.h"
34 #include "internal.h"
35
36 #include "apv.h"
37 #include "cbs.h"
38 #include "cbs_apv.h"
39
40 extern const unsigned char ff_apv_encode_dct_comp_spv_data[];
41 extern const unsigned int ff_apv_encode_dct_comp_spv_len;
42
43 extern const unsigned char ff_apv_encode_tiles_comp_spv_data[];
44 extern const unsigned int ff_apv_encode_tiles_comp_spv_len;
45
46 #define APV_DEFAULT_QMAT 16
47 #define APV_MAX_NUM_COMP 4
48
49 typedef struct DCTPushData {
50 int frame_dim[2];
51 int tile_count[2];
52 int tile_mb_dim[2];
53 int log2_chroma_sub[2];
54 int num_comp;
55 int bit_depth;
56 float qf[APV_MAX_NUM_COMP]; /* per-component fact/(level_scale*2^qp_shift) */
57 uint8_t qmat[64]; /* quantisation matrix, raster order */
58 } DCTPushData;
59
60 typedef struct EntropyPushData {
61 VkDeviceAddress bytestream;
62 int tile_count[2];
63 int num_comp;
64 uint32_t slot_size;
65 uint32_t comp_base; /* component index this dispatch's z=0 maps to */
66 uint32_t blocks_per_tile; /* uniform coeff stride, in blocks */
67 int frame_mb[2]; /* frame size in MBs (luma basis) */
68 int tile_mb_dim[2]; /* full-tile size in MBs */
69 uint32_t blocks_per_mb; /* blocks per MB of this dispatch's components */
70 } EntropyPushData;
71
72 typedef struct VulkanEncodeAPVFrameData {
73 FFVkBuffer *coeffs_ref;
74 FFVkBuffer *bytestream_ref;
75 FFVkBuffer *compacted_ref;
76 FFVkBuffer *sizes_ref;
77
78 /* Copied from the source */
79 enum AVColorPrimaries color_primaries;
80 enum AVColorTransferCharacteristic color_trc;
81 enum AVColorSpace colorspace;
82 enum AVColorRange color_range;
83 } VulkanEncodeAPVFrameData;
84
85 typedef struct VulkanEncodeAPVContext {
86 const AVClass *class;
87
88 FFVulkanContext s;
89 AVVulkanDeviceQueueFamily *qf;
90 FFVkExecPool exec_pool;
91
92 FFVulkanShader shd_dct;
93 FFVulkanShader shd_entropy[2]; /* [0] luma-sized, [1] chroma-sized */
94 FFVulkanShader shd_compact;
95
96 /* Per-frame buffer pools */
97 AVRefStructPool *coeffs_pool;
98 AVRefStructPool *bytestream_pool;
99 AVRefStructPool *compacted_pool;
100 AVRefStructPool *sizes_pool;
101
102 /* DCT/quantize push constants -- encoder-constant, built once at init. */
103 DCTPushData dct_push;
104
105 /* CBS used to assemble the output packet */
106 CodedBitstreamContext *cbc;
107 CodedBitstreamFragment au;
108 APVRawFrame raw_frame;
109
110 FFVkEncodeLoop loop;
111
112 /* Async machinery */
113 int async_depth;
114 VulkanEncodeAPVFrameData *exec_ctx_info;
115
116 /* Derived per-encoder state */
117 int frame_mb_x, frame_mb_y; /* MBs in the frame (luma basis) */
118 int tile_cols, tile_rows;
119 int tile_mb_w, tile_mb_h; /* MBs per tile (luma basis) */
120 int tile_count;
121 int blocks_per_mb; /* luma; always 4 */
122 int chroma_blocks_per_mb; /* 4 for 4:4:4, 2 for 4:2:2 */
123 int num_comp;
124 int bit_depth;
125 enum AVPixelFormat sw_format;
126
127 int profile_idc;
128 int level_idc;
129 int band_idc;
130 int chroma_format_idc;
131
132 size_t coeffs_size; /* total size of coeffs buffer */
133 size_t bytestream_size; /* total size of bytestream buffer */
134 size_t slot_size; /* per-tile-component bytestream slot size */
135 size_t sizes_size; /* total size of sizes buffer */
136
137 /* User options */
138 int tile_w_mbs_opt;
139 int tile_h_mbs_opt;
140 int qp_y;
141 int qp_c;
142 int qmatrix; /* APV_QMATRIX_*: quantisation matrix select */
143
144 /* Benchmark knob (env APV_VULKAN_HEADERS_ONLY): the GPU still encodes,
145 * but the tiles are never downloaded and packets carry headers only. */
146 int headers_only;
147
148 /* Benchmark knob (env APV_VULKAN_SKIP_ENTROPY): skip the entropy
149 * dispatch to isolate the DCT pass. Implies headers_only. */
150 int skip_entropy;
151 } VulkanEncodeAPVContext;
152
153 /*
154 * HEVC default 8x8 intra scaling list (ITU-T H.265, Table 7-6): flat through
155 * the low-frequency core, a gentle ramp toward the high-frequency corner.
156 * Raster order; the matrix is symmetric, so APV's [y][x]/[x][y] indexing is
157 * immaterial. APV and HEVC share the "16 = neutral" convention, so the list
158 * transfers without rescaling.
159 */
160 static const uint8_t apv_qmat_hevc_intra[64] = {
161 16, 16, 16, 16, 17, 18, 21, 24,
162 16, 16, 16, 16, 17, 19, 22, 25,
163 16, 16, 17, 18, 20, 22, 25, 29,
164 16, 16, 18, 21, 24, 27, 31, 36,
165 17, 17, 20, 24, 30, 35, 41, 47,
166 18, 19, 22, 27, 35, 44, 54, 65,
167 21, 22, 25, 31, 41, 54, 70, 88,
168 24, 25, 29, 36, 47, 65, 88, 115,
169 };
170
171 enum {
172 APV_QMATRIX_FLAT = 0, /* uniform 16 (the spec default) */
173 APV_QMATRIX_HEVC = 1, /* HEVC default intra scaling list */
174 };
175
176 /*
177 * The active quantisation-matrix value at raster index i. Both the q_matrix
178 * signalled in the frame header and the encoder's pf table are derived from
179 * this single accessor, so they cannot disagree -- a mismatch would quantise
180 * against a different matrix than the decoder dequantises with.
181 */
182 ✗ static int apv_qmatrix_value(int qmatrix, int i)
183 {
184 ✗ return qmatrix == APV_QMATRIX_HEVC ? apv_qmat_hevc_intra[i]
185
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ : APV_DEFAULT_QMAT;
186 }
187
188 static const uint8_t apv_level_scale[6] = { 40, 45, 51, 57, 64, 71 };
189
190 ✗ static int chroma_format_from_pix_fmt(enum AVPixelFormat sw_fmt)
191 {
192
0/5
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 5 not taken.
✗ Branch 2 → 6 not taken.
✗ Branch 2 → 7 not taken.
✗ switch (sw_fmt) {
193 ✗ case AV_PIX_FMT_YUV422P10:
194 case AV_PIX_FMT_YUV422P12:
195 ✗ return APV_CHROMA_FORMAT_422;
196 ✗ case AV_PIX_FMT_YUV444P10:
197 case AV_PIX_FMT_YUV444P12:
198 ✗ return APV_CHROMA_FORMAT_444;
199 ✗ case AV_PIX_FMT_GRAY10:
200 case AV_PIX_FMT_GRAY12:
201 ✗ return APV_CHROMA_FORMAT_400;
202 ✗ case AV_PIX_FMT_YUVA444P10:
203 case AV_PIX_FMT_YUVA444P12:
204 ✗ return APV_CHROMA_FORMAT_4444;
205 ✗ default:
206 ✗ return -1;
207 }
208 }
209
210 ✗ static int profile_idc_from_pix_fmt(enum AVPixelFormat sw_fmt)
211 {
212
0/8
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 5 not taken.
✗ Branch 2 → 6 not taken.
✗ Branch 2 → 7 not taken.
✗ Branch 2 → 8 not taken.
✗ Branch 2 → 9 not taken.
✗ Branch 2 → 10 not taken.
✗ switch (sw_fmt) {
213 ✗ case AV_PIX_FMT_GRAY10: return APV_PROFILE_400_10;
214 ✗ case AV_PIX_FMT_YUV422P10: return APV_PROFILE_422_10;
215 ✗ case AV_PIX_FMT_YUV422P12: return APV_PROFILE_422_12;
216 ✗ case AV_PIX_FMT_YUV444P10: return APV_PROFILE_444_10;
217 ✗ case AV_PIX_FMT_YUV444P12: return APV_PROFILE_444_12;
218 ✗ case AV_PIX_FMT_YUVA444P10: return APV_PROFILE_4444_10;
219 ✗ case AV_PIX_FMT_YUVA444P12: return APV_PROFILE_4444_12;
220 ✗ default: return -1;
221 }
222 }
223
224 ✗ static int init_dct_shader(AVCodecContext *avctx)
225 {
226 int err;
227 ✗ VulkanEncodeAPVContext *ev = avctx->priv_data;
228 ✗ FFVulkanShader *shd = &ev->shd_dct;
229
230 ✗ SPEC_LIST_CREATE(sl, 1, sizeof(uint32_t))
231 ✗ SPEC_LIST_ADD(sl, 16, 32, 4); /* nb_blocks: blocks_per_mb per workgroup */
232
233 ✗ ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
234 ✗ (uint32_t []) { 8, 4, 1 }, 0);
235
236 ✗ ff_vk_shader_add_push_const(shd, 0, sizeof(DCTPushData),
237 VK_SHADER_STAGE_COMPUTE_BIT);
238
239 ✗ const FFVulkanDescriptorSetBinding desc_set[] = {
240 {
241 .name = "coeffs_buf",
242 .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
243 .stages = VK_SHADER_STAGE_COMPUTE_BIT,
244 },
245 {
246 .name = "src",
247 .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
248 .stages = VK_SHADER_STAGE_COMPUTE_BIT,
249 ✗ .elems = av_pix_fmt_count_planes(ev->sw_format),
250 },
251 };
252 ✗ ff_vk_shader_add_descriptor_set(&ev->s, shd, desc_set, 2, 0);
253
254
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
✗ RET(ff_vk_shader_link(&ev->s, shd,
255 ff_apv_encode_dct_comp_spv_data,
256 ff_apv_encode_dct_comp_spv_len, "main"));
257
258
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
✗ RET(ff_vk_shader_register_exec(&ev->s, &ev->exec_pool, shd));
259
260 ✗ fail:
261 ✗ return err;
262 }
263
264 ✗ static int init_entropy_shader(AVCodecContext *avctx, int blocks_per_mb,
265 FFVulkanShader *shd)
266 {
267 int err;
268 ✗ VulkanEncodeAPVContext *ev = avctx->priv_data;
269
270 /* One workgroup per tile-component, one invocation per transform block.
271 * Luma and chroma tile-components hold different block counts under
272 * chroma sub-sampling, so each gets a pipeline with its own size. */
273 ✗ uint32_t wg = ev->tile_mb_w * ev->tile_mb_h * blocks_per_mb;
274 ✗ uint32_t min_sg = ev->s.subgroup_props.minSubgroupSize;
275
276 /* The driver picks the subgroup size; size for the most it may use */
277 ✗ SPEC_LIST_CREATE(sl, 1, sizeof(uint32_t))
278 ✗ SPEC_LIST_ADD(sl, 0, 32, FFALIGN(wg, min_sg) / min_sg);
279
280 ✗ ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
281 ✗ (uint32_t []) { wg, 1, 1 }, 0);
282
283 ✗ ff_vk_shader_add_push_const(shd, 0, sizeof(EntropyPushData),
284 VK_SHADER_STAGE_COMPUTE_BIT);
285
286 ✗ const FFVulkanDescriptorSetBinding desc_set[] = {
287 {
288 .name = "coeffs_buf",
289 .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
290 .stages = VK_SHADER_STAGE_COMPUTE_BIT,
291 },
292 {
293 .name = "sizes_buf",
294 .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
295 .stages = VK_SHADER_STAGE_COMPUTE_BIT,
296 },
297 };
298 ✗ ff_vk_shader_add_descriptor_set(&ev->s, shd, desc_set, 2, 0);
299
300
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
✗ RET(ff_vk_shader_link(&ev->s, shd,
301 ff_apv_encode_tiles_comp_spv_data,
302 ff_apv_encode_tiles_comp_spv_len, "main"));
303
304
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
✗ RET(ff_vk_shader_register_exec(&ev->s, &ev->exec_pool, shd));
305
306 ✗ fail:
307 ✗ return err;
308 }
309
310 /*
311 * The DCT/quantize shader's push constants are entirely encoder-constant:
312 * frame geometry, the per-component quant scale qf, and the quantisation
313 * matrix. Build them once -- nothing here changes between frames.
314 */
315 ✗ static void build_dct_push_const(AVCodecContext *avctx)
316 {
317 ✗ VulkanEncodeAPVContext *ev = avctx->priv_data;
318 ✗ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(ev->sw_format);
319 ✗ DCTPushData *pd = &ev->dct_push;
320 ✗ const double fact = (double)(1 << (ev->bit_depth - 1));
321
322 ✗ pd->frame_dim[0] = avctx->width;
323 ✗ pd->frame_dim[1] = avctx->height;
324 ✗ pd->tile_count[0] = ev->tile_cols;
325 ✗ pd->tile_count[1] = ev->tile_rows;
326 ✗ pd->tile_mb_dim[0] = ev->tile_mb_w;
327 ✗ pd->tile_mb_dim[1] = ev->tile_mb_h;
328 ✗ pd->log2_chroma_sub[0] = desc->log2_chroma_w;
329 ✗ pd->log2_chroma_sub[1] = desc->log2_chroma_h;
330 ✗ pd->num_comp = ev->num_comp;
331 ✗ pd->bit_depth = ev->bit_depth;
332
333 /*
334 * qf[c] = fact / (level_scale * 2^qp_shift). The encoder uses one QP per
335 * component, so this never varies by tile. Component 3 is alpha
336 * (4:4:4:4): full-resolution, so it takes the luma QP.
337 */
338
0/2
✗ Branch 9 → 4 not taken.
✗ Branch 9 → 10 not taken.
✗ for (int c = 0; c < APV_MAX_NUM_COMP; c++) {
339
0/4
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
✗ int qp = (c == 0 || c == 3) ? ev->qp_y : ev->qp_c;
340 ✗ int level_scale = apv_level_scale[qp % 6];
341 ✗ int qp_shift = qp / 6;
342 ✗ pd->qf[c] =
343 ✗ (float)(fact / ((double)level_scale * (double)(1 << qp_shift)));
344 }
345
346 /*
347 * The 8-bit quantisation matrix. The shader stages it to shared memory
348 * and quantises with 1024 / qmat[i], the reciprocal partner of the
349 * decoder's per-coefficient dequant -- the same matrix that gets
350 * signalled in the frame header.
351 */
352
0/2
✗ Branch 13 → 11 not taken.
✗ Branch 13 → 14 not taken.
✗ for (int i = 0; i < 64; i++)
353 ✗ pd->qmat[i] = apv_qmatrix_value(ev->qmatrix, i);
354 ✗ }
355
356 ✗ static int submit_frame(AVCodecContext *avctx, FFVkExecContext *exec,
357 AVFrame *frame)
358 {
359 ✗ int err = 0;
360 ✗ VulkanEncodeAPVContext *ev = avctx->priv_data;
361 ✗ FFVulkanFunctions *vk = &ev->s.vkfn;
362 ✗ VulkanEncodeAPVFrameData *fd = exec->opaque;
363 VkImageView views[AV_NUM_DATA_POINTERS];
364
365 VkImageMemoryBarrier2 img_bar[AV_NUM_DATA_POINTERS];
366 ✗ int nb_img_bar = 0;
367 VkBufferMemoryBarrier2 buf_bar[4];
368 ✗ int nb_buf_bar = 0;
369
370 FFVkBuffer *coeffs_buf;
371 FFVkBuffer *bytestream_buf;
372
373 FFVkBuffer *compacted_buf;
374 FFVkBuffer *sizes_buf;
375
376 ✗ fd->color_primaries = frame->color_primaries;
377 ✗ fd->color_trc = frame->color_trc;
378 ✗ fd->colorspace = frame->colorspace;
379 ✗ fd->color_range = frame->color_range;
380
381 /* Start recording */
382 ✗ err = ff_vk_exec_start(&ev->s, exec);
383
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✗ if (err < 0)
384 ✗ return err;
385
386 /* Allocate per-frame buffers */
387
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
✗ RET(ff_vk_get_pooled_buffer(&ev->s, &ev->coeffs_pool, &fd->coeffs_ref,
388 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
389 NULL, ev->coeffs_size,
390 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
391 ✗ coeffs_buf = fd->coeffs_ref;
392
393 /* The entropy shader writes the bitstream here, sparsely -- one
394 * worst-case-sized slot per tile-component. Device-local, so those GPU
395 * writes stay in VRAM and never cross PCIe. */
396
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
✗ RET(ff_vk_get_pooled_buffer(&ev->s, &ev->bytestream_pool,
397 &fd->bytestream_ref,
398 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
399 VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
400 NULL, ev->bytestream_size,
401 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
402 ✗ bytestream_buf = fd->bytestream_ref;
403
404 /* The compaction shader gathers the sparse slots into here, and the CPU
405 * assembles the packet from it. */
406
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ RET(ff_vk_get_pooled_buffer(&ev->s, &ev->compacted_pool,
407 &fd->compacted_ref,
408 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
409 VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
410 NULL, ev->bytestream_size,
411 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
412 VK_MEMORY_PROPERTY_HOST_CACHED_BIT));
413 ✗ compacted_buf = fd->compacted_ref;
414
415
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
✗ RET(ff_vk_get_pooled_buffer(&ev->s, &ev->sizes_pool, &fd->sizes_ref,
416 VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
417 NULL, ev->sizes_size,
418 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
419 VK_MEMORY_PROPERTY_HOST_CACHED_BIT));
420 ✗ sizes_buf = fd->sizes_ref;
421
422 ✗ ff_vk_exec_add_dep_refstruct(&ev->s, exec, fd->coeffs_ref);
423 ✗ ff_vk_exec_add_dep_refstruct(&ev->s, exec, fd->bytestream_ref);
424 ✗ ff_vk_exec_add_dep_refstruct(&ev->s, exec, fd->compacted_ref);
425 ✗ ff_vk_exec_add_dep_refstruct(&ev->s, exec, fd->sizes_ref);
426
427
0/2
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 24 not taken.
✗ RET(ff_vk_exec_add_dep_frame(&ev->s, exec, frame,
428 VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
429 VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT));
430
431
0/2
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
✗ RET(ff_vk_create_imageviews(&ev->s, exec, views, frame, FF_VK_REP_INT));
432
433 ✗ ff_vk_frame_barrier(&ev->s, exec, frame,
434 img_bar, &nb_img_bar,
435 VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
436 VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
437 VK_ACCESS_SHADER_READ_BIT,
438 VK_IMAGE_LAYOUT_GENERAL,
439 VK_QUEUE_FAMILY_IGNORED);
440
441 ✗ vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
442 .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
443 .pImageMemoryBarriers = img_bar,
444 .imageMemoryBarrierCount = nb_img_bar,
445 });
446 ✗ nb_img_bar = 0;
447
448 /* DCT + Quantize pass */
449 {
450 ✗ ff_vk_shader_update_desc_buffer(&ev->s, exec, &ev->shd_dct,
451 0, 0, 0,
452 ✗ coeffs_buf, 0, coeffs_buf->size,
453 VK_FORMAT_UNDEFINED);
454 ✗ ff_vk_shader_update_img_array(&ev->s, exec, &ev->shd_dct,
455 frame, views,
456 0, 1,
457 VK_IMAGE_LAYOUT_GENERAL,
458 VK_NULL_HANDLE);
459
460 ✗ ff_vk_exec_bind_shader(&ev->s, exec, &ev->shd_dct);
461 ✗ ff_vk_shader_update_push_const(&ev->s, exec, &ev->shd_dct,
462 VK_SHADER_STAGE_COMPUTE_BIT,
463 ✗ 0, sizeof(ev->dct_push), &ev->dct_push);
464
465 ✗ vk->CmdDispatch(exec->buf,
466 ✗ ev->frame_mb_x, ev->frame_mb_y, ev->num_comp);
467 }
468
469 /* Barrier: wait for coeff writes before entropy */
470 ✗ ff_vk_buf_barrier(buf_bar[nb_buf_bar++], coeffs_buf,
471 COMPUTE_SHADER_BIT, SHADER_WRITE_BIT, NONE,
472 COMPUTE_SHADER_BIT, SHADER_READ_BIT, NONE,
473 0, coeffs_buf->size);
474
475 ✗ vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
476 .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
477 .pBufferMemoryBarriers = buf_bar,
478 .bufferMemoryBarrierCount = nb_buf_bar,
479 });
480 ✗ nb_buf_bar = 0;
481
482 /*
483 * Entropy encoding pass. Luma (component 0) and chroma (components
484 * 1..num_comp-1) run as two dispatches: under chroma sub-sampling their
485 * tile-components hold different block counts, hence different workgroup
486 * sizes -- one pipeline each. The two write disjoint memory and need no
487 * barrier between them, so the GPU is free to overlap them.
488 */
489
0/4
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
✗ Branch 53 → 36 not taken.
✗ Branch 53 → 54 not taken.
✗ for (int p = 0; !ev->skip_entropy && p < 2; p++) {
490 ✗ FFVulkanShader *shd = &ev->shd_entropy[p];
491
0/2
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
✗ uint32_t z_comps = (p == 0) ? 1 : ev->num_comp - 1;
492
493
0/2
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
✗ if (z_comps == 0)
494 ✗ continue; /* 4:0:0 (monochrome) has no chroma components */
495
496 ✗ EntropyPushData pd = {
497 ✗ .bytestream = bytestream_buf->address,
498 ✗ .tile_count = { ev->tile_cols, ev->tile_rows },
499 ✗ .num_comp = ev->num_comp,
500 ✗ .slot_size = (uint32_t)ev->slot_size,
501 ✗ .comp_base = (uint32_t)p,
502 ✗ .blocks_per_tile = (uint32_t)ev->tile_mb_w * ev->tile_mb_h *
503 ✗ ev->blocks_per_mb,
504 ✗ .frame_mb = { ev->frame_mb_x, ev->frame_mb_y },
505 ✗ .tile_mb_dim = { ev->tile_mb_w, ev->tile_mb_h },
506
0/2
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 43 not taken.
✗ .blocks_per_mb = (uint32_t)(p == 0 ? ev->blocks_per_mb
507 ✗ : ev->chroma_blocks_per_mb),
508 };
509
510 ✗ ff_vk_shader_update_desc_buffer(&ev->s, exec, shd, 0, 0, 0,
511 ✗ coeffs_buf, 0, coeffs_buf->size,
512 VK_FORMAT_UNDEFINED);
513 ✗ ff_vk_shader_update_desc_buffer(&ev->s, exec, shd, 0, 1, 0,
514 ✗ sizes_buf, 0, sizes_buf->size,
515 VK_FORMAT_UNDEFINED);
516
517 ✗ ff_vk_exec_bind_shader(&ev->s, exec, shd);
518 ✗ ff_vk_shader_update_push_const(&ev->s, exec, shd,
519 VK_SHADER_STAGE_COMPUTE_BIT,
520 0, sizeof(pd), &pd);
521
522 ✗ vk->CmdDispatch(exec->buf, ev->tile_cols, ev->tile_rows, z_comps);
523 }
524
525 /* Compaction pass: gather the sparse per-tile-component slots into the
526 * output buffer. */
527
0/2
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 58 not taken.
✗ if (!ev->headers_only)
528
0/2
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 58 not taken.
✗ RET(ff_vk_seg_gather(&ev->s, exec, &ev->shd_compact,
529 sizes_buf, 0, ev->tile_count * ev->num_comp,
530 bytestream_buf, ev->slot_size,
531 compacted_buf, 0, 0));
532
533 ✗ err = ff_vk_exec_submit(&ev->s, exec);
534
0/2
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 61 not taken.
✗ if (err < 0)
535 ✗ return err;
536
537 ✗ return 0;
538
539 ✗ fail:
540 ✗ ff_vk_exec_discard(&ev->s, exec);
541 ✗ return err;
542 }
543
544 ✗ static int build_packet(AVCodecContext *avctx, FFVkExecContext *exec,
545 AVPacket *pkt)
546 {
547 ✗ int err = 0;
548 ✗ VulkanEncodeAPVContext *ev = avctx->priv_data;
549 ✗ FFVulkanFunctions *vk = &ev->s.vkfn;
550 ✗ VulkanEncodeAPVFrameData *fd = exec->opaque;
551 ✗ FFVkBuffer *compacted_buf = fd->compacted_ref;
552 ✗ FFVkBuffer *sizes_buf = fd->sizes_ref;
553 ✗ APVRawFrame *raw_frame = &ev->raw_frame;
554
555 /* Wait for the GPU encode to finish */
556 ✗ ff_vk_exec_wait(&ev->s, exec);
557
558 ✗ const uint32_t *sizes = NULL;
559 static uint8_t headers_only_tile; /* 1-byte token tile data */
560
561 /* Headers-only benchmark mode never touches the GPU output. */
562
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 11 not taken.
✗ if (!ev->headers_only) {
563 /* Invalidate mapped memory if needed */
564
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 7 not taken.
✗ if (!(compacted_buf->flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
565 ✗ VkMappedMemoryRange r = {
566 .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
567 ✗ .memory = compacted_buf->mem,
568 .offset = 0,
569 .size = VK_WHOLE_SIZE,
570 };
571 ✗ vk->InvalidateMappedMemoryRanges(ev->s.hwctx->act_dev, 1, &r);
572 }
573
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 10 not taken.
✗ if (!(sizes_buf->flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
574 ✗ VkMappedMemoryRange r = {
575 .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
576 ✗ .memory = sizes_buf->mem,
577 .offset = 0,
578 .size = VK_WHOLE_SIZE,
579 };
580 ✗ vk->InvalidateMappedMemoryRanges(ev->s.hwctx->act_dev, 1, &r);
581 }
582 ✗ sizes = (const uint32_t *)sizes_buf->mapped_mem;
583 }
584
585 ✗ memset(raw_frame, 0, sizeof(*raw_frame));
586
587 ✗ raw_frame->pbu_header.pbu_type = APV_PBU_PRIMARY_FRAME;
588 ✗ raw_frame->pbu_header.group_id = 1;
589
590 ✗ APVRawFrameHeader *fh = &raw_frame->frame_header;
591 ✗ fh->frame_info.profile_idc = ev->profile_idc;
592 ✗ fh->frame_info.level_idc = ev->level_idc;
593 ✗ fh->frame_info.band_idc = ev->band_idc;
594 ✗ fh->frame_info.frame_width = avctx->width;
595 ✗ fh->frame_info.frame_height = avctx->height;
596 ✗ fh->frame_info.chroma_format_idc = ev->chroma_format_idc;
597 ✗ fh->frame_info.bit_depth_minus8 = ev->bit_depth - 8;
598 ✗ fh->frame_info.capture_time_distance = 0;
599
600 ✗ fh->color_primaries = fd->color_primaries;
601 ✗ fh->transfer_characteristics = fd->color_trc;
602 ✗ fh->matrix_coefficients = fd->colorspace;
603 ✗ fh->full_range_flag = fd->color_range == AVCOL_RANGE_JPEG;
604 ✗ fh->color_description_present_flag = fh->color_primaries != AVCOL_PRI_UNSPECIFIED ||
605
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 15 not taken.
✗ fh->transfer_characteristics != AVCOL_TRC_UNSPECIFIED ||
606
0/4
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 15 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
✗ fh->matrix_coefficients != AVCOL_SPC_UNSPECIFIED ||
607
0/2
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
✗ fh->full_range_flag;
608
609 /* compute_pf_table() builds the encoder's pf scale from the same matrix;
610 * the two must stay in sync. use_q_matrix is only signalled when the
611 * matrix is non-uniform (a flat 16 matrix is the inferred default). */
612 ✗ fh->use_q_matrix = ev->qmatrix != APV_QMATRIX_FLAT;
613
0/2
✗ Branch 26 → 18 not taken.
✗ Branch 26 → 27 not taken.
✗ for (int c = 0; c < ev->num_comp; c++)
614
0/2
✗ Branch 24 → 19 not taken.
✗ Branch 24 → 25 not taken.
✗ for (int y = 0; y < 8; y++)
615
0/2
✗ Branch 22 → 20 not taken.
✗ Branch 22 → 23 not taken.
✗ for (int x = 0; x < 8; x++)
616 ✗ fh->quantization_matrix.q_matrix[c][y][x] =
617 ✗ apv_qmatrix_value(ev->qmatrix, y * 8 + x);
618
619 ✗ fh->tile_info.tile_width_in_mbs = ev->tile_mb_w;
620 ✗ fh->tile_info.tile_height_in_mbs = ev->tile_mb_h;
621 ✗ fh->tile_info.tile_size_present_in_fh_flag = 0;
622
623 /* Populate each tile. The compacted buffer holds each tile-component's
624 * data back to back, in (tile, component) order -- the same layout the
625 * gather shader produced. */
626 ✗ uint32_t comp_off = 0;
627
0/2
✗ Branch 39 → 28 not taken.
✗ Branch 39 → 40 not taken.
✗ for (int t = 0; t < ev->tile_count; t++) {
628 ✗ APVRawTile *tile = &raw_frame->tile[t];
629 ✗ uint32_t total_tile_data = 0;
630
631 ✗ tile->tile_header.tile_header_size =
632 ✗ 4 + ev->num_comp * (4 + 1) + 1;
633 ✗ tile->tile_header.tile_index = t;
634
635
0/2
✗ Branch 37 → 29 not taken.
✗ Branch 37 → 38 not taken.
✗ for (int c = 0; c < ev->num_comp; c++) {
636 uint32_t sz;
637
0/2
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
✗ if (ev->headers_only) {
638 /* No readback: one token byte (CBS requires size >= 1). */
639 ✗ sz = 1;
640 ✗ tile->tile_data[c] = &headers_only_tile;
641 } else {
642 ✗ sz = sizes[t * ev->num_comp + c];
643 ✗ tile->tile_data[c] = compacted_buf->mapped_mem + comp_off;
644 ✗ comp_off += sz;
645 }
646 ✗ tile->tile_header.tile_data_size[c] = sz;
647
0/2
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
✗ tile->tile_header.tile_qp[c] =
648
0/2
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 35 not taken.
✗ (c == 0 || c == 3) ? ev->qp_y : ev->qp_c;
649 ✗ total_tile_data += sz;
650 }
651 ✗ tile->tile_header.reserved_zero_8bits = 0;
652 ✗ tile->tile_dummy_byte_size = 0;
653 ✗ tile->tile_dummy_byte = NULL;
654
655 ✗ raw_frame->tile_size[t] =
656 ✗ tile->tile_header.tile_header_size + total_tile_data;
657 }
658
659 /* Assemble fragment using cbs_apv */
660 ✗ ff_cbs_fragment_reset(&ev->au);
661
662 ✗ err = ff_cbs_insert_unit_content(&ev->au, -1, APV_PBU_PRIMARY_FRAME,
663 raw_frame, NULL);
664
0/2
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 44 not taken.
✗ if (err < 0)
665 ✗ return err;
666
667 /* Assemble straight into the packet: ff_cbs_write_packet() hands pkt a
668 * reference to CBS's own assembled buffer -- no copy. */
669 ✗ err = ff_cbs_write_packet(ev->cbc, pkt, &ev->au);
670
0/2
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 47 not taken.
✗ if (err < 0)
671 ✗ return err;
672
673 ✗ av_log(avctx, AV_LOG_VERBOSE, "Encoded APV frame: %i bytes (%.2f MiB)\n",
674 ✗ pkt->size, pkt->size / (1024.0 * 1024.0));
675
676 ✗ av_refstruct_unref(&fd->coeffs_ref);
677 ✗ av_refstruct_unref(&fd->bytestream_ref);
678 ✗ av_refstruct_unref(&fd->compacted_ref);
679 ✗ av_refstruct_unref(&fd->sizes_ref);
680
681 ✗ return 0;
682 }
683
684 ✗ static int vulkan_encode_apv_receive_packet(AVCodecContext *avctx,
685 AVPacket *pkt)
686 {
687 ✗ VulkanEncodeAPVContext *ev = avctx->priv_data;
688 ✗ return ff_vk_encode_loop_receive_packet(avctx, &ev->loop, pkt);
689 }
690
691 ✗ static av_cold void vulkan_encode_apv_flush(AVCodecContext *avctx)
692 {
693 ✗ VulkanEncodeAPVContext *ev = avctx->priv_data;
694 ✗ ff_vk_encode_loop_flush(avctx, &ev->loop);
695 ✗ }
696
697 ✗ static av_cold int vulkan_encode_apv_close(AVCodecContext *avctx)
698 {
699 ✗ VulkanEncodeAPVContext *ev = avctx->priv_data;
700
701 ✗ ff_vk_exec_pool_free(&ev->s, &ev->exec_pool);
702
703 ✗ ff_vk_shader_free(&ev->s, &ev->shd_dct);
704 ✗ ff_vk_shader_free(&ev->s, &ev->shd_entropy[0]);
705 ✗ ff_vk_shader_free(&ev->s, &ev->shd_entropy[1]);
706 ✗ ff_vk_shader_free(&ev->s, &ev->shd_compact);
707
708
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 16 not taken.
✗ if (ev->exec_ctx_info) {
709
0/2
✗ Branch 14 → 9 not taken.
✗ Branch 14 → 15 not taken.
✗ for (int i = 0; i < ev->async_depth; i++) {
710 ✗ VulkanEncodeAPVFrameData *fd = &ev->exec_ctx_info[i];
711 ✗ av_refstruct_unref(&fd->coeffs_ref);
712 ✗ av_refstruct_unref(&fd->bytestream_ref);
713 ✗ av_refstruct_unref(&fd->compacted_ref);
714 ✗ av_refstruct_unref(&fd->sizes_ref);
715 }
716 ✗ av_freep(&ev->exec_ctx_info);
717 }
718
719 ✗ av_refstruct_pool_uninit(&ev->coeffs_pool);
720 ✗ av_refstruct_pool_uninit(&ev->bytestream_pool);
721 ✗ av_refstruct_pool_uninit(&ev->compacted_pool);
722 ✗ av_refstruct_pool_uninit(&ev->sizes_pool);
723
724 ✗ ff_cbs_fragment_free(&ev->au);
725 ✗ ff_cbs_close(&ev->cbc);
726
727 ✗ ff_vk_encode_loop_uninit(&ev->loop);
728 ✗ ff_vk_uninit(&ev->s);
729
730 ✗ return 0;
731 }
732
733 ✗ static av_cold int vulkan_encode_apv_init(AVCodecContext *avctx)
734 {
735 int err;
736 ✗ VulkanEncodeAPVContext *ev = avctx->priv_data;
737 AVHWFramesContext *hwfc;
738
739
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ if (!avctx->hw_frames_ctx) {
740 ✗ av_log(avctx, AV_LOG_ERROR, "An AVHWFramesContext is required.\n");
741 ✗ return AVERROR(EINVAL);
742 }
743 ✗ hwfc = (AVHWFramesContext *)avctx->hw_frames_ctx->data;
744 ✗ ev->sw_format = hwfc->sw_format;
745
746 ✗ ev->profile_idc = profile_idc_from_pix_fmt(ev->sw_format);
747 ✗ ev->chroma_format_idc = chroma_format_from_pix_fmt(ev->sw_format);
748
0/4
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 12 not taken.
✗ if (ev->profile_idc < 0 || ev->chroma_format_idc < 0) {
749 ✗ av_log(avctx, AV_LOG_ERROR, "Unsupported sw_format %s for APV.\n",
750 av_get_pix_fmt_name(ev->sw_format));
751 ✗ return AVERROR(EINVAL);
752 }
753
754 /* All four APV chroma formats are supported -- 4:0:0, 4:2:2, 4:4:4 and
755 * 4:4:4:4. The profile_idc / chroma_format_idc checks above already
756 * reject any pixel format that is not one of them. */
757 ✗ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(ev->sw_format);
758 ✗ ev->bit_depth = desc->comp[0].depth;
759 ✗ ev->num_comp = desc->nb_components;
760 ✗ ev->blocks_per_mb = 4; /* luma: 16x16 MB -> 4 8x8 blocks */
761 ✗ ev->chroma_blocks_per_mb = 4 >> (desc->log2_chroma_w + desc->log2_chroma_h);
762 ✗ ev->level_idc = 33; /* placeholder, real value depends on resolution and bitrate */
763 ✗ ev->band_idc = 0;
764
765 /* Frame dimensions in macroblocks */
766 ✗ ev->frame_mb_x = (avctx->width + APV_MB_WIDTH - 1) / APV_MB_WIDTH;
767 ✗ ev->frame_mb_y = (avctx->height + APV_MB_HEIGHT - 1) / APV_MB_HEIGHT;
768
769 /* The 20x20 tile grid cap is structural (fixed-size arrays everywhere);
770 * the spec additionally demands tiles of at least 16x8 MBs. Each
771 * tile-component maps to one entropy workgroup, one invocation per
772 * transform block. */
773 ✗ int grid_tw = (ev->frame_mb_x + APV_MAX_TILE_COLS - 1) / APV_MAX_TILE_COLS;
774 ✗ int grid_th = (ev->frame_mb_y + APV_MAX_TILE_ROWS - 1) / APV_MAX_TILE_ROWS;
775 ✗ int min_tw = FFMAX(APV_MIN_TILE_WIDTH_IN_MBS, grid_tw);
776 ✗ int min_th = FFMAX(APV_MIN_TILE_HEIGHT_IN_MBS, grid_th);
777
778 /* tile_w/tile_h pick the tile size in MBs; 0 selects the spec minimum.
779 * An explicit request below the spec minimum is honoured down to the
780 * grid cap -- non-conformant, but more tiles mean shorter (serial)
781 * entropy streams, which is the decode speed lever. */
782
0/2
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
✗ ev->tile_mb_w = ev->tile_w_mbs_opt > 0 ? ev->tile_w_mbs_opt : min_tw;
783
0/2
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
✗ ev->tile_mb_h = ev->tile_h_mbs_opt > 0 ? ev->tile_h_mbs_opt : min_th;
784 ✗ ev->tile_mb_w = FFMIN(FFMAX(ev->tile_mb_w, grid_tw), ev->frame_mb_x);
785 ✗ ev->tile_mb_h = FFMIN(FFMAX(ev->tile_mb_h, grid_th), ev->frame_mb_y);
786
0/2
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 21 not taken.
✗ if (ev->tile_mb_w < APV_MIN_TILE_WIDTH_IN_MBS ||
787
0/2
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 22 not taken.
✗ ev->tile_mb_h < APV_MIN_TILE_HEIGHT_IN_MBS)
788 ✗ av_log(avctx, AV_LOG_WARNING,
789 "Tile size %dx%d MBs is below the spec minimum of %dx%d: "
790 "NON-CONFORMANT bitstream, most decoders will reject it.\n",
791 ev->tile_mb_w, ev->tile_mb_h,
792 APV_MIN_TILE_WIDTH_IN_MBS, APV_MIN_TILE_HEIGHT_IN_MBS);
793
794 /* Left to default, grow the tile toward 1024 transform blocks (the
795 * entropy workgroup ceiling) while it still divides the frame. Bigger
796 * tiles mean fewer tile-components, which the compaction pass strongly
797 * prefers -- it is the dominant win for throughput. */
798
0/4
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 34 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 34 not taken.
✗ if (!ev->tile_w_mbs_opt && !ev->tile_h_mbs_opt) {
799 ✗ while (ev->tile_mb_w * 2 <= ev->frame_mb_x &&
800
0/4
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 29 not taken.
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 29 not taken.
✗ ev->frame_mb_x % (ev->tile_mb_w * 2) == 0 &&
801
0/2
✗ Branch 28 → 25 not taken.
✗ Branch 28 → 29 not taken.
✗ (ev->tile_mb_w * 2) * ev->tile_mb_h * ev->blocks_per_mb <= 1024)
802 ✗ ev->tile_mb_w *= 2;
803 ✗ while (ev->tile_mb_h * 2 <= ev->frame_mb_y &&
804
0/4
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 34 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
✗ ev->frame_mb_y % (ev->tile_mb_h * 2) == 0 &&
805
0/2
✗ Branch 33 → 30 not taken.
✗ Branch 33 → 34 not taken.
✗ ev->tile_mb_w * (ev->tile_mb_h * 2) * ev->blocks_per_mb <= 1024)
806 ✗ ev->tile_mb_h *= 2;
807 }
808
809 /* Ceil division: the rightmost column / bottom row of tiles take the
810 * remainder MBs (spec-legal; the tile grid is closed at the frame edge,
811 * so those tiles may be smaller than the signalled tile size). */
812 ✗ ev->tile_cols = (ev->frame_mb_x + ev->tile_mb_w - 1) / ev->tile_mb_w;
813 ✗ ev->tile_rows = (ev->frame_mb_y + ev->tile_mb_h - 1) / ev->tile_mb_h;
814 ✗ ev->tile_count = ev->tile_cols * ev->tile_rows;
815
816
0/2
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 37 not taken.
✗ if (ev->tile_count > APV_MAX_TILE_COUNT) {
817 ✗ av_log(avctx, AV_LOG_ERROR, "Too many tiles (%d).\n", ev->tile_count);
818 ✗ return AVERROR(EINVAL);
819 }
820
821 /* The entropy shader runs one invocation per block in a tile-component
822 * and its shared buffers are sized for 1024. */
823
0/2
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 40 not taken.
✗ if (ev->tile_mb_w * ev->tile_mb_h * ev->blocks_per_mb > 1024) {
824 ✗ av_log(avctx, AV_LOG_ERROR,
825 "Tile-component has too many transform blocks (%d > 1024).\n",
826 ✗ ev->tile_mb_w * ev->tile_mb_h * ev->blocks_per_mb);
827 ✗ return AVERROR_PATCHWELCOME;
828 }
829
830 /* qp_chroma left at 0 means "use the luma QP". */
831
0/2
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 42 not taken.
✗ if (ev->qp_c == 0)
832 ✗ ev->qp_c = ev->qp_y;
833
834 /* Validate QP range */
835 ✗ int max_qp = 3 + ev->bit_depth * 6;
836
0/8
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 46 not taken.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 46 not taken.
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 46 not taken.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 48 not taken.
✗ if (ev->qp_y < 0 || ev->qp_y > max_qp || ev->qp_c < 0 || ev->qp_c > max_qp) {
837 ✗ av_log(avctx, AV_LOG_ERROR,
838 "QP out of range [0, %d]: qp_y=%d, qp_c=%d.\n",
839 max_qp, ev->qp_y, ev->qp_c);
840 ✗ return AVERROR(EINVAL);
841 }
842
843 /* Buffer sizing */
844 ✗ size_t blocks_per_tile = (size_t)ev->tile_mb_w * ev->tile_mb_h * ev->blocks_per_mb;
845 ✗ ev->coeffs_size = (size_t)ev->tile_count * ev->num_comp *
846 ✗ blocks_per_tile * APV_BLK_COEFFS * sizeof(int16_t);
847
848 /* Worst-case per-tile-component bytestream: each coefficient at most ~32 bits.
849 * Round up generously. */
850 ✗ ev->slot_size = blocks_per_tile * APV_BLK_COEFFS * 8;
851 ✗ ev->slot_size = FFALIGN(ev->slot_size, 64);
852 ✗ ev->bytestream_size = (size_t)ev->tile_count * ev->num_comp * ev->slot_size;
853 ✗ ev->sizes_size = ((size_t)ev->tile_count * ev->num_comp + 1) * sizeof(uint32_t);
854
855 ✗ av_log(avctx, AV_LOG_VERBOSE,
856 "APV Vulkan encoder: %dx%d, %d tiles (%dx%d MBs each), "
857 "qp_y=%d qp_c=%d, coeffs=%zu KiB, bytestream=%zu KiB\n",
858 avctx->width, avctx->height, ev->tile_count,
859 ev->tile_mb_w, ev->tile_mb_h, ev->qp_y, ev->qp_c,
860 ✗ ev->coeffs_size / 1024, ev->bytestream_size / 1024);
861
862 ✗ ev->headers_only = !!getenv("APV_VULKAN_HEADERS_ONLY");
863 ✗ ev->skip_entropy = !!getenv("APV_VULKAN_SKIP_ENTROPY");
864
0/2
✗ Branch 51 → 52 not taken.
✗ Branch 51 → 53 not taken.
✗ if (ev->skip_entropy)
865 ✗ ev->headers_only = 1; /* the bitstream is never produced */
866
0/2
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 55 not taken.
✗ if (ev->headers_only)
867 ✗ av_log(avctx, AV_LOG_WARNING,
868 "APV_VULKAN_HEADERS_ONLY set: tiles will not be downloaded "
869 "or assembled; output packets contain headers only.\n");
870
0/2
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
✗ if (ev->skip_entropy)
871 ✗ av_log(avctx, AV_LOG_WARNING,
872 "APV_VULKAN_SKIP_ENTROPY set: entropy dispatch skipped "
873 "(DCT-only benchmark mode).\n");
874
875 /* Init Vulkan */
876 ✗ err = ff_vk_init(&ev->s, avctx, NULL, avctx->hw_frames_ctx);
877
0/2
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 60 not taken.
✗ if (err < 0)
878 ✗ return err;
879
880 ✗ ev->qf = ff_vk_qf_find(&ev->s, VK_QUEUE_COMPUTE_BIT, 0);
881
0/2
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 64 not taken.
✗ if (!ev->qf) {
882 ✗ av_log(avctx, AV_LOG_ERROR, "Device has no compute queues!\n");
883 ✗ return AVERROR(ENOTSUP);
884 }
885
886 ✗ err = ff_vk_exec_pool_init(&ev->s, ev->qf, &ev->exec_pool,
887 ev->async_depth, 0, 0, 0, NULL);
888
0/2
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
✗ if (err < 0)
889 ✗ return err;
890
891 /* Init CBS for assembling output */
892 ✗ err = ff_cbs_init(&ev->cbc, AV_CODEC_ID_APV, avctx);
893
0/2
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 70 not taken.
✗ if (err < 0)
894 ✗ return err;
895
896 /* Shaders */
897 ✗ err = init_dct_shader(avctx);
898
0/2
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 73 not taken.
✗ if (err < 0)
899 ✗ return err;
900 ✗ err = init_entropy_shader(avctx, ev->blocks_per_mb, &ev->shd_entropy[0]);
901
0/2
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
✗ if (err < 0)
902 ✗ return err;
903 ✗ err = init_entropy_shader(avctx, ev->chroma_blocks_per_mb,
904 &ev->shd_entropy[1]);
905
0/2
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 79 not taken.
✗ if (err < 0)
906 ✗ return err;
907 ✗ err = ff_vk_seg_gather_init(&ev->s, &ev->exec_pool, &ev->shd_compact);
908
0/2
✗ Branch 80 → 81 not taken.
✗ Branch 80 → 82 not taken.
✗ if (err < 0)
909 ✗ return err;
910
911 /* The DCT/quantize shader's push constants never change frame to frame;
912 * build them once. */
913 ✗ build_dct_push_const(avctx);
914
915 ✗ err = ff_vk_encode_loop_init(&ev->s, &ev->exec_pool, &ev->loop, submit_frame, build_packet);
916
0/2
✗ Branch 84 → 85 not taken.
✗ Branch 84 → 86 not taken.
✗ if (err < 0)
917 ✗ return err;
918
919 /* Async data pool */
920 ✗ ev->async_depth = ev->exec_pool.pool_size;
921 ✗ ev->exec_ctx_info = av_calloc(ev->async_depth, sizeof(*ev->exec_ctx_info));
922
0/2
✗ Branch 87 → 88 not taken.
✗ Branch 87 → 89 not taken.
✗ if (!ev->exec_ctx_info)
923 ✗ return AVERROR(ENOMEM);
924
0/2
✗ Branch 91 → 90 not taken.
✗ Branch 91 → 92 not taken.
✗ for (int i = 0; i < ev->async_depth; i++)
925 ✗ ev->exec_pool.contexts[i].opaque = &ev->exec_ctx_info[i];
926
927 ✗ return 0;
928 }
929
930 #define OFFSET(x) offsetof(VulkanEncodeAPVContext, x)
931 #define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
932 static const AVOption vulkan_encode_apv_options[] = {
933 { "qp", "Quantization parameter (luma)", OFFSET(qp_y),
934 AV_OPT_TYPE_INT, { .i64 = 22 }, 0, 255, VE },
935 { "qp_chroma", "Chroma quantization parameter (0 = same as luma qp)", OFFSET(qp_c),
936 AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 255, VE },
937 { "qmatrix", "Quantization matrix", OFFSET(qmatrix),
938 AV_OPT_TYPE_INT, { .i64 = APV_QMATRIX_HEVC }, 0, 1, VE, "qmatrix" },
939 { "flat", "Uniform matrix, all 16 (APV spec default)", 0,
940 AV_OPT_TYPE_CONST, { .i64 = APV_QMATRIX_FLAT }, 0, 0, VE, "qmatrix" },
941 { "hevc", "HEVC default intra scaling list (mild perceptual shaping)", 0,
942 AV_OPT_TYPE_CONST, { .i64 = APV_QMATRIX_HEVC }, 0, 0, VE, "qmatrix" },
943 /* The minimum legal tile is 16x8 MBs; the maxima are this encoder's
944 * ceiling of 1024 transform blocks per tile-component (256 MBs): with
945 * the other dimension at its minimum, width <= 32 and height <= 16. A
946 * value of 0 is the sentinel for the adaptive per-frame default. */
947 { "tile_width", "Tile width in macroblocks (0 = adaptive, auto-sized per frame)", OFFSET(tile_w_mbs_opt),
948 AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 32, VE },
949 { "tile_height", "Tile height in macroblocks (0 = adaptive, auto-sized per frame)", OFFSET(tile_h_mbs_opt),
950 AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 16, VE },
951 { "async_depth", "Internal parallelization depth", OFFSET(async_depth),
952 AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX, VE },
953 { NULL }
954 };
955
956 static const FFCodecDefault vulkan_encode_apv_defaults[] = {
957 { "g", "1" },
958 { NULL },
959 };
960
961 static const AVClass vulkan_encode_apv_class = {
962 .class_name = "apv_vulkan",
963 .item_name = av_default_item_name,
964 .option = vulkan_encode_apv_options,
965 .version = LIBAVUTIL_VERSION_INT,
966 };
967
968 static const AVCodecHWConfigInternal *const vulkan_encode_apv_hw_configs[] = {
969 HW_CONFIG_ENCODER_FRAMES(VULKAN, VULKAN),
970 NULL,
971 };
972
973 const FFCodec ff_apv_vulkan_encoder = {
974 .p.name = "apv_vulkan",
975 CODEC_LONG_NAME("Advanced Professional Video (Vulkan)"),
976 .p.type = AVMEDIA_TYPE_VIDEO,
977 .p.id = AV_CODEC_ID_APV,
978 .priv_data_size = sizeof(VulkanEncodeAPVContext),
979 .init = &vulkan_encode_apv_init,
980 FF_CODEC_RECEIVE_PACKET_CB(&vulkan_encode_apv_receive_packet),
981 .flush = &vulkan_encode_apv_flush,
982 .close = &vulkan_encode_apv_close,
983 .p.priv_class = &vulkan_encode_apv_class,
984 .p.capabilities = AV_CODEC_CAP_DELAY |
985 AV_CODEC_CAP_HARDWARE |
986 AV_CODEC_CAP_DR1 |
987 AV_CODEC_CAP_ENCODER_FLUSH |
988 AV_CODEC_CAP_ENCODER_REORDERED_OPAQUE,
989 .caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
990 .defaults = vulkan_encode_apv_defaults,
991 CODEC_PIXFMTS(AV_PIX_FMT_VULKAN),
992 .hw_configs = vulkan_encode_apv_hw_configs,
993 .p.wrapper_name = "vulkan",
994 };
995