mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2026-08-09 01:21:06 +00:00
1140 lines
43 KiB
C
1140 lines
43 KiB
C
/*
|
|
* Copyright (c) 2026 Lynne <dev@lynne.ee>
|
|
*
|
|
* 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 <math.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "libavutil/mem.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/pixdesc.h"
|
|
#include "libavutil/vulkan.h"
|
|
|
|
#include "avcodec.h"
|
|
#include "codec_internal.h"
|
|
#include "encode.h"
|
|
#include "hwconfig.h"
|
|
#include "internal.h"
|
|
|
|
#include "apv.h"
|
|
#include "cbs.h"
|
|
#include "cbs_apv.h"
|
|
|
|
extern const unsigned char ff_apv_encode_dct_comp_spv_data[];
|
|
extern const unsigned int ff_apv_encode_dct_comp_spv_len;
|
|
|
|
extern const unsigned char ff_apv_encode_tiles_comp_spv_data[];
|
|
extern const unsigned int ff_apv_encode_tiles_comp_spv_len;
|
|
|
|
extern const unsigned char ff_seg_gather_comp_spv_data[];
|
|
extern const unsigned int ff_seg_gather_comp_spv_len;
|
|
|
|
#define APV_DEFAULT_QMAT 16
|
|
#define APV_MAX_NUM_COMP 4
|
|
|
|
typedef struct DCTPushData {
|
|
int frame_dim[2];
|
|
int tile_count[2];
|
|
int tile_mb_dim[2];
|
|
int log2_chroma_sub[2];
|
|
int num_comp;
|
|
int bit_depth;
|
|
float qf[APV_MAX_NUM_COMP]; /* per-component fact/(level_scale*2^qp_shift) */
|
|
uint8_t qmat[64]; /* quantisation matrix, raster order */
|
|
} DCTPushData;
|
|
|
|
typedef struct EntropyPushData {
|
|
VkDeviceAddress bytestream;
|
|
int tile_count[2];
|
|
int num_comp;
|
|
uint32_t slot_size;
|
|
uint32_t comp_base; /* component index this dispatch's z=0 maps to */
|
|
uint32_t blocks_per_tile; /* uniform coeff stride, in blocks */
|
|
int frame_mb[2]; /* frame size in MBs (luma basis) */
|
|
int tile_mb_dim[2]; /* full-tile size in MBs */
|
|
uint32_t blocks_per_mb; /* blocks per MB of this dispatch's components */
|
|
} EntropyPushData;
|
|
|
|
typedef struct CompactPushData {
|
|
VkDeviceAddress sparse;
|
|
VkDeviceAddress compacted;
|
|
uint32_t slot_size;
|
|
} CompactPushData;
|
|
|
|
typedef struct VulkanEncodeAPVFrameData {
|
|
AVBufferRef *coeffs_ref;
|
|
AVBufferRef *bytestream_ref;
|
|
AVBufferRef *compacted_ref;
|
|
AVBufferRef *sizes_ref;
|
|
|
|
int64_t pts;
|
|
int64_t duration;
|
|
void *frame_opaque;
|
|
AVBufferRef *frame_opaque_ref;
|
|
int flags;
|
|
} VulkanEncodeAPVFrameData;
|
|
|
|
typedef struct VulkanEncodeAPVContext {
|
|
const AVClass *class;
|
|
|
|
FFVulkanContext s;
|
|
AVVulkanDeviceQueueFamily *qf;
|
|
FFVkExecPool exec_pool;
|
|
|
|
FFVulkanShader shd_dct;
|
|
FFVulkanShader shd_entropy[2]; /* [0] luma-sized, [1] chroma-sized */
|
|
FFVulkanShader shd_compact;
|
|
|
|
/* Per-frame buffer pools */
|
|
AVBufferPool *coeffs_pool;
|
|
AVBufferPool *bytestream_pool;
|
|
AVBufferPool *gathered_pool;
|
|
AVBufferPool *compacted_pool;
|
|
AVBufferPool *sizes_pool;
|
|
|
|
/* DCT/quantize push constants -- encoder-constant, built once at init. */
|
|
DCTPushData dct_push;
|
|
|
|
/* CBS used to assemble the output packet */
|
|
CodedBitstreamContext *cbc;
|
|
CodedBitstreamFragment au;
|
|
|
|
AVFrame *frame;
|
|
|
|
/* Async machinery */
|
|
int async_depth;
|
|
int in_flight;
|
|
VulkanEncodeAPVFrameData *exec_ctx_info;
|
|
|
|
/* Derived per-encoder state */
|
|
int frame_mb_x, frame_mb_y; /* MBs in the frame (luma basis) */
|
|
int tile_cols, tile_rows;
|
|
int tile_mb_w, tile_mb_h; /* MBs per tile (luma basis) */
|
|
int tile_count;
|
|
int blocks_per_mb; /* luma; always 4 */
|
|
int chroma_blocks_per_mb; /* 4 for 4:4:4, 2 for 4:2:2 */
|
|
int num_comp;
|
|
int bit_depth;
|
|
enum AVPixelFormat sw_format;
|
|
|
|
int profile_idc;
|
|
int level_idc;
|
|
int band_idc;
|
|
int chroma_format_idc;
|
|
|
|
size_t coeffs_size; /* total size of coeffs buffer */
|
|
size_t bytestream_size; /* total size of bytestream buffer */
|
|
size_t slot_size; /* per-tile-component bytestream slot size */
|
|
size_t sizes_size; /* total size of sizes buffer */
|
|
|
|
/* User options */
|
|
int tile_w_mbs_opt;
|
|
int tile_h_mbs_opt;
|
|
int qp_y;
|
|
int qp_c;
|
|
int qmatrix; /* APV_QMATRIX_*: quantisation matrix select */
|
|
|
|
/* Benchmark knob (env APV_VULKAN_HEADERS_ONLY): the GPU still encodes,
|
|
* but the tiles are never downloaded and packets carry headers only. */
|
|
int headers_only;
|
|
|
|
/* Benchmark knob (env APV_VULKAN_SKIP_ENTROPY): skip the entropy
|
|
* dispatch to isolate the DCT pass. Implies headers_only. */
|
|
int skip_entropy;
|
|
} VulkanEncodeAPVContext;
|
|
|
|
/*
|
|
* HEVC default 8x8 intra scaling list (ITU-T H.265, Table 7-6): flat through
|
|
* the low-frequency core, a gentle ramp toward the high-frequency corner.
|
|
* Raster order; the matrix is symmetric, so APV's [y][x]/[x][y] indexing is
|
|
* immaterial. APV and HEVC share the "16 = neutral" convention, so the list
|
|
* transfers without rescaling.
|
|
*/
|
|
static const uint8_t apv_qmat_hevc_intra[64] = {
|
|
16, 16, 16, 16, 17, 18, 21, 24,
|
|
16, 16, 16, 16, 17, 19, 22, 25,
|
|
16, 16, 17, 18, 20, 22, 25, 29,
|
|
16, 16, 18, 21, 24, 27, 31, 36,
|
|
17, 17, 20, 24, 30, 35, 41, 47,
|
|
18, 19, 22, 27, 35, 44, 54, 65,
|
|
21, 22, 25, 31, 41, 54, 70, 88,
|
|
24, 25, 29, 36, 47, 65, 88, 115,
|
|
};
|
|
|
|
enum {
|
|
APV_QMATRIX_FLAT = 0, /* uniform 16 (the spec default) */
|
|
APV_QMATRIX_HEVC = 1, /* HEVC default intra scaling list */
|
|
};
|
|
|
|
/*
|
|
* The active quantisation-matrix value at raster index i. Both the q_matrix
|
|
* signalled in the frame header and the encoder's pf table are derived from
|
|
* this single accessor, so they cannot disagree -- a mismatch would quantise
|
|
* against a different matrix than the decoder dequantises with.
|
|
*/
|
|
static int apv_qmatrix_value(int qmatrix, int i)
|
|
{
|
|
return qmatrix == APV_QMATRIX_HEVC ? apv_qmat_hevc_intra[i]
|
|
: APV_DEFAULT_QMAT;
|
|
}
|
|
|
|
static const uint8_t apv_level_scale[6] = { 40, 45, 51, 57, 64, 71 };
|
|
|
|
static int chroma_format_from_pix_fmt(enum AVPixelFormat sw_fmt)
|
|
{
|
|
switch (sw_fmt) {
|
|
case AV_PIX_FMT_YUV422P10:
|
|
case AV_PIX_FMT_YUV422P12:
|
|
return APV_CHROMA_FORMAT_422;
|
|
case AV_PIX_FMT_YUV444P10:
|
|
case AV_PIX_FMT_YUV444P12:
|
|
return APV_CHROMA_FORMAT_444;
|
|
case AV_PIX_FMT_GRAY10:
|
|
case AV_PIX_FMT_GRAY12:
|
|
return APV_CHROMA_FORMAT_400;
|
|
case AV_PIX_FMT_YUVA444P10:
|
|
case AV_PIX_FMT_YUVA444P12:
|
|
return APV_CHROMA_FORMAT_4444;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int profile_idc_from_pix_fmt(enum AVPixelFormat sw_fmt)
|
|
{
|
|
switch (sw_fmt) {
|
|
case AV_PIX_FMT_GRAY10: return APV_PROFILE_400_10;
|
|
case AV_PIX_FMT_YUV422P10: return APV_PROFILE_422_10;
|
|
case AV_PIX_FMT_YUV422P12: return APV_PROFILE_422_12;
|
|
case AV_PIX_FMT_YUV444P10: return APV_PROFILE_444_10;
|
|
case AV_PIX_FMT_YUV444P12: return APV_PROFILE_444_12;
|
|
case AV_PIX_FMT_YUVA444P10: return APV_PROFILE_4444_10;
|
|
case AV_PIX_FMT_YUVA444P12: return APV_PROFILE_4444_12;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
static int init_dct_shader(AVCodecContext *avctx)
|
|
{
|
|
int err;
|
|
VulkanEncodeAPVContext *ev = avctx->priv_data;
|
|
FFVulkanShader *shd = &ev->shd_dct;
|
|
|
|
SPEC_LIST_CREATE(sl, 1, sizeof(uint32_t))
|
|
SPEC_LIST_ADD(sl, 16, 32, 4); /* nb_blocks: blocks_per_mb per workgroup */
|
|
|
|
ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
|
|
(uint32_t []) { 8, 4, 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(ev->sw_format),
|
|
},
|
|
};
|
|
ff_vk_shader_add_descriptor_set(&ev->s, shd, desc_set, 2, 0);
|
|
|
|
RET(ff_vk_shader_link(&ev->s, shd,
|
|
ff_apv_encode_dct_comp_spv_data,
|
|
ff_apv_encode_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 blocks_per_mb,
|
|
FFVulkanShader *shd)
|
|
{
|
|
int err;
|
|
VulkanEncodeAPVContext *ev = avctx->priv_data;
|
|
|
|
/* One workgroup per tile-component, one invocation per transform block.
|
|
* Luma and chroma tile-components hold different block counts under
|
|
* chroma sub-sampling, so each gets a pipeline with its own size. */
|
|
uint32_t wg = ev->tile_mb_w * ev->tile_mb_h * blocks_per_mb;
|
|
|
|
ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
|
|
(uint32_t []) { wg, 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 = "sizes_buf",
|
|
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
|
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
|
|
},
|
|
};
|
|
ff_vk_shader_add_descriptor_set(&ev->s, shd, desc_set, 2, 0);
|
|
|
|
RET(ff_vk_shader_link(&ev->s, shd,
|
|
ff_apv_encode_tiles_comp_spv_data,
|
|
ff_apv_encode_tiles_comp_spv_len, "main"));
|
|
|
|
RET(ff_vk_shader_register_exec(&ev->s, &ev->exec_pool, shd));
|
|
|
|
fail:
|
|
return err;
|
|
}
|
|
|
|
static int init_compact_shader(AVCodecContext *avctx)
|
|
{
|
|
int err;
|
|
VulkanEncodeAPVContext *ev = avctx->priv_data;
|
|
FFVulkanShader *shd = &ev->shd_compact;
|
|
|
|
ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
|
|
(uint32_t []) { 256, 1, 1 }, 0);
|
|
|
|
ff_vk_shader_add_push_const(shd, 0, sizeof(CompactPushData),
|
|
VK_SHADER_STAGE_COMPUTE_BIT);
|
|
|
|
const FFVulkanDescriptorSetBinding desc_set[] = {
|
|
{
|
|
.name = "sizes_buf",
|
|
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
|
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
|
|
},
|
|
};
|
|
ff_vk_shader_add_descriptor_set(&ev->s, shd, desc_set, 1, 0);
|
|
|
|
RET(ff_vk_shader_link(&ev->s, shd,
|
|
ff_seg_gather_comp_spv_data,
|
|
ff_seg_gather_comp_spv_len, "main"));
|
|
|
|
RET(ff_vk_shader_register_exec(&ev->s, &ev->exec_pool, shd));
|
|
|
|
fail:
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* The DCT/quantize shader's push constants are entirely encoder-constant:
|
|
* frame geometry, the per-component quant scale qf, and the quantisation
|
|
* matrix. Build them once -- nothing here changes between frames.
|
|
*/
|
|
static void build_dct_push_const(AVCodecContext *avctx)
|
|
{
|
|
VulkanEncodeAPVContext *ev = avctx->priv_data;
|
|
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(ev->sw_format);
|
|
DCTPushData *pd = &ev->dct_push;
|
|
const double fact = (double)(1 << (ev->bit_depth - 1));
|
|
|
|
pd->frame_dim[0] = avctx->width;
|
|
pd->frame_dim[1] = avctx->height;
|
|
pd->tile_count[0] = ev->tile_cols;
|
|
pd->tile_count[1] = ev->tile_rows;
|
|
pd->tile_mb_dim[0] = ev->tile_mb_w;
|
|
pd->tile_mb_dim[1] = ev->tile_mb_h;
|
|
pd->log2_chroma_sub[0] = desc->log2_chroma_w;
|
|
pd->log2_chroma_sub[1] = desc->log2_chroma_h;
|
|
pd->num_comp = ev->num_comp;
|
|
pd->bit_depth = ev->bit_depth;
|
|
|
|
/*
|
|
* qf[c] = fact / (level_scale * 2^qp_shift). The encoder uses one QP per
|
|
* component, so this never varies by tile. Component 3 is alpha
|
|
* (4:4:4:4): full-resolution, so it takes the luma QP.
|
|
*/
|
|
for (int c = 0; c < APV_MAX_NUM_COMP; c++) {
|
|
int qp = (c == 0 || c == 3) ? ev->qp_y : ev->qp_c;
|
|
int level_scale = apv_level_scale[qp % 6];
|
|
int qp_shift = qp / 6;
|
|
pd->qf[c] =
|
|
(float)(fact / ((double)level_scale * (double)(1 << qp_shift)));
|
|
}
|
|
|
|
/*
|
|
* The 8-bit quantisation matrix. The shader stages it to shared memory
|
|
* and quantises with 1024 / qmat[i], the reciprocal partner of the
|
|
* decoder's per-coefficient dequant -- the same matrix that gets
|
|
* signalled in the frame header.
|
|
*/
|
|
for (int i = 0; i < 64; i++)
|
|
pd->qmat[i] = apv_qmatrix_value(ev->qmatrix, i);
|
|
}
|
|
|
|
static int submit_frame(AVCodecContext *avctx, FFVkExecContext *exec,
|
|
AVFrame *frame)
|
|
{
|
|
int err = 0;
|
|
VulkanEncodeAPVContext *ev = avctx->priv_data;
|
|
FFVulkanFunctions *vk = &ev->s.vkfn;
|
|
VulkanEncodeAPVFrameData *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;
|
|
FFVkBuffer *bytestream_buf;
|
|
AVBufferRef *gathered_ref = NULL;
|
|
FFVkBuffer *gathered_buf;
|
|
FFVkBuffer *compacted_buf;
|
|
FFVkBuffer *sizes_buf;
|
|
|
|
/* Allocate per-frame buffers */
|
|
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;
|
|
|
|
/* The entropy shader writes the bitstream here, sparsely -- one
|
|
* worst-case-sized slot per tile-component. Device-local, so those GPU
|
|
* writes stay in VRAM and never cross PCIe. */
|
|
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_DEVICE_LOCAL_BIT));
|
|
bytestream_buf = (FFVkBuffer *)fd->bytestream_ref->data;
|
|
|
|
/* The compaction shader gathers the sparse slots into here, contiguous.
|
|
* Device-local: shader stores over the bus are unreliably slow on some
|
|
* drivers, so the transfer to the host is left to the copy engine. */
|
|
RET(ff_vk_get_pooled_buffer(&ev->s, &ev->gathered_pool,
|
|
&gathered_ref,
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
|
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
|
NULL, ev->bytestream_size,
|
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
|
|
gathered_buf = (FFVkBuffer *)gathered_ref->data;
|
|
|
|
/* Copy-engine destination the CPU assembles the packet from.
|
|
* Host-visible + host-cached so the readback is a fast cached copy. */
|
|
RET(ff_vk_get_pooled_buffer(&ev->s, &ev->compacted_pool,
|
|
&fd->compacted_ref,
|
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
|
|
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));
|
|
compacted_buf = (FFVkBuffer *)fd->compacted_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, &gathered_ref, 1, 0);
|
|
gathered_ref = NULL; /* Ownership passed */
|
|
ff_vk_exec_add_dep_buf(&ev->s, exec, &fd->compacted_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_INT));
|
|
|
|
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 pass */
|
|
{
|
|
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_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(ev->dct_push), &ev->dct_push);
|
|
|
|
vk->CmdDispatch(exec->buf,
|
|
ev->frame_mb_x, ev->frame_mb_y, ev->num_comp);
|
|
}
|
|
|
|
/* Barrier: wait for coeff writes before entropy */
|
|
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 encoding pass. Luma (component 0) and chroma (components
|
|
* 1..num_comp-1) run as two dispatches: under chroma sub-sampling their
|
|
* tile-components hold different block counts, hence different workgroup
|
|
* sizes -- one pipeline each. The two write disjoint memory and need no
|
|
* barrier between them, so the GPU is free to overlap them.
|
|
*/
|
|
for (int p = 0; !ev->skip_entropy && p < 2; p++) {
|
|
FFVulkanShader *shd = &ev->shd_entropy[p];
|
|
uint32_t z_comps = (p == 0) ? 1 : ev->num_comp - 1;
|
|
|
|
if (z_comps == 0)
|
|
continue; /* 4:0:0 (monochrome) has no chroma components */
|
|
|
|
EntropyPushData pd = {
|
|
.bytestream = bytestream_buf->address,
|
|
.tile_count = { ev->tile_cols, ev->tile_rows },
|
|
.num_comp = ev->num_comp,
|
|
.slot_size = (uint32_t)ev->slot_size,
|
|
.comp_base = (uint32_t)p,
|
|
.blocks_per_tile = (uint32_t)ev->tile_mb_w * ev->tile_mb_h *
|
|
ev->blocks_per_mb,
|
|
.frame_mb = { ev->frame_mb_x, ev->frame_mb_y },
|
|
.tile_mb_dim = { ev->tile_mb_w, ev->tile_mb_h },
|
|
.blocks_per_mb = (uint32_t)(p == 0 ? ev->blocks_per_mb
|
|
: ev->chroma_blocks_per_mb),
|
|
};
|
|
|
|
ff_vk_shader_update_desc_buffer(&ev->s, exec, shd, 0, 0, 0,
|
|
coeffs_buf, 0, coeffs_buf->size,
|
|
VK_FORMAT_UNDEFINED);
|
|
ff_vk_shader_update_desc_buffer(&ev->s, exec, shd, 0, 1, 0,
|
|
sizes_buf, 0, sizes_buf->size,
|
|
VK_FORMAT_UNDEFINED);
|
|
|
|
ff_vk_exec_bind_shader(&ev->s, exec, shd);
|
|
ff_vk_shader_update_push_const(&ev->s, exec, shd,
|
|
VK_SHADER_STAGE_COMPUTE_BIT,
|
|
0, sizeof(pd), &pd);
|
|
|
|
vk->CmdDispatch(exec->buf, ev->tile_cols, ev->tile_rows, z_comps);
|
|
}
|
|
|
|
/* Compaction pass: gather the sparse per-tile-component slots into one
|
|
* contiguous device-local buffer, then read it back with the copy
|
|
* engine. */
|
|
if (!ev->headers_only) {
|
|
ff_vk_buf_barrier(buf_bar[nb_buf_bar++], bytestream_buf,
|
|
COMPUTE_SHADER_BIT, SHADER_WRITE_BIT, NONE,
|
|
COMPUTE_SHADER_BIT, SHADER_READ_BIT, NONE,
|
|
0, bytestream_buf->size);
|
|
ff_vk_buf_barrier(buf_bar[nb_buf_bar++], sizes_buf,
|
|
COMPUTE_SHADER_BIT, SHADER_WRITE_BIT, NONE,
|
|
COMPUTE_SHADER_BIT, SHADER_READ_BIT, NONE,
|
|
0, sizes_buf->size);
|
|
vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
|
|
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
|
.pBufferMemoryBarriers = buf_bar,
|
|
.bufferMemoryBarrierCount = nb_buf_bar,
|
|
});
|
|
nb_buf_bar = 0;
|
|
|
|
CompactPushData pd = {
|
|
.sparse = bytestream_buf->address,
|
|
.compacted = gathered_buf->address,
|
|
.slot_size = (uint32_t)ev->slot_size,
|
|
};
|
|
|
|
ff_vk_shader_update_desc_buffer(&ev->s, exec, &ev->shd_compact,
|
|
0, 0, 0,
|
|
sizes_buf, 0, sizes_buf->size,
|
|
VK_FORMAT_UNDEFINED);
|
|
ff_vk_exec_bind_shader(&ev->s, exec, &ev->shd_compact);
|
|
ff_vk_shader_update_push_const(&ev->s, exec, &ev->shd_compact,
|
|
VK_SHADER_STAGE_COMPUTE_BIT,
|
|
0, sizeof(pd), &pd);
|
|
|
|
vk->CmdDispatch(exec->buf, ev->tile_count * ev->num_comp, 1, 1);
|
|
|
|
/* The gathered size is only known once the encode is done, so the
|
|
* whole buffer is copied; the slots are sized to the entropy coder's
|
|
* worst case, which keeps this close to the payload size. */
|
|
ff_vk_buf_barrier(buf_bar[nb_buf_bar++], gathered_buf,
|
|
COMPUTE_SHADER_BIT, SHADER_WRITE_BIT, NONE,
|
|
TRANSFER_BIT, TRANSFER_READ_BIT, NONE,
|
|
0, gathered_buf->size);
|
|
vk->CmdPipelineBarrier2(exec->buf, &(VkDependencyInfo) {
|
|
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
|
.pBufferMemoryBarriers = buf_bar,
|
|
.bufferMemoryBarrierCount = nb_buf_bar,
|
|
});
|
|
nb_buf_bar = 0;
|
|
|
|
vk->CmdCopyBuffer(exec->buf, gathered_buf->buf, compacted_buf->buf,
|
|
1, &(VkBufferCopy) { .size = ev->bytestream_size });
|
|
}
|
|
|
|
err = ff_vk_exec_submit(&ev->s, exec);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
av_buffer_unref(&gathered_ref);
|
|
ff_vk_exec_discard_deps(&ev->s, exec);
|
|
return err;
|
|
}
|
|
|
|
static int build_packet(AVCodecContext *avctx, FFVkExecContext *exec,
|
|
AVPacket *pkt)
|
|
{
|
|
int err = 0;
|
|
VulkanEncodeAPVContext *ev = avctx->priv_data;
|
|
FFVulkanFunctions *vk = &ev->s.vkfn;
|
|
VulkanEncodeAPVFrameData *fd = exec->opaque;
|
|
FFVkBuffer *compacted_buf = (FFVkBuffer *)fd->compacted_ref->data;
|
|
FFVkBuffer *sizes_buf = (FFVkBuffer *)fd->sizes_ref->data;
|
|
APVRawFrame *raw_frame = NULL;
|
|
|
|
/* Wait for the GPU encode to finish */
|
|
ff_vk_exec_wait(&ev->s, exec);
|
|
|
|
const uint32_t *sizes = NULL;
|
|
static uint8_t headers_only_tile; /* 1-byte token tile data */
|
|
|
|
/* Headers-only benchmark mode never touches the GPU output. */
|
|
if (!ev->headers_only) {
|
|
/* Invalidate mapped memory if needed */
|
|
if (!(compacted_buf->flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
|
|
VkMappedMemoryRange r = {
|
|
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
|
.memory = compacted_buf->mem,
|
|
.offset = 0,
|
|
.size = VK_WHOLE_SIZE,
|
|
};
|
|
vk->InvalidateMappedMemoryRanges(ev->s.hwctx->act_dev, 1, &r);
|
|
}
|
|
if (!(sizes_buf->flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
|
|
VkMappedMemoryRange r = {
|
|
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
|
.memory = sizes_buf->mem,
|
|
.offset = 0,
|
|
.size = VK_WHOLE_SIZE,
|
|
};
|
|
vk->InvalidateMappedMemoryRanges(ev->s.hwctx->act_dev, 1, &r);
|
|
}
|
|
sizes = (const uint32_t *)sizes_buf->mapped_mem;
|
|
}
|
|
|
|
/* Allocate the cbs frame structure */
|
|
raw_frame = av_mallocz(sizeof(*raw_frame));
|
|
if (!raw_frame)
|
|
return AVERROR(ENOMEM);
|
|
|
|
raw_frame->pbu_header.pbu_type = APV_PBU_PRIMARY_FRAME;
|
|
raw_frame->pbu_header.group_id = 1;
|
|
|
|
APVRawFrameHeader *fh = &raw_frame->frame_header;
|
|
fh->frame_info.profile_idc = ev->profile_idc;
|
|
fh->frame_info.level_idc = ev->level_idc;
|
|
fh->frame_info.band_idc = ev->band_idc;
|
|
fh->frame_info.frame_width = avctx->width;
|
|
fh->frame_info.frame_height = avctx->height;
|
|
fh->frame_info.chroma_format_idc = ev->chroma_format_idc;
|
|
fh->frame_info.bit_depth_minus8 = ev->bit_depth - 8;
|
|
fh->frame_info.capture_time_distance = 0;
|
|
|
|
fh->color_description_present_flag = 0;
|
|
/* Inferred values when the flag is 0, per the spec. */
|
|
fh->color_primaries = 2;
|
|
fh->transfer_characteristics = 2;
|
|
fh->matrix_coefficients = 2;
|
|
fh->full_range_flag = 0;
|
|
|
|
/* compute_pf_table() builds the encoder's pf scale from the same matrix;
|
|
* the two must stay in sync. use_q_matrix is only signalled when the
|
|
* matrix is non-uniform (a flat 16 matrix is the inferred default). */
|
|
fh->use_q_matrix = ev->qmatrix != APV_QMATRIX_FLAT;
|
|
for (int c = 0; c < ev->num_comp; c++)
|
|
for (int y = 0; y < 8; y++)
|
|
for (int x = 0; x < 8; x++)
|
|
fh->quantization_matrix.q_matrix[c][y][x] =
|
|
apv_qmatrix_value(ev->qmatrix, y * 8 + x);
|
|
|
|
fh->tile_info.tile_width_in_mbs = ev->tile_mb_w;
|
|
fh->tile_info.tile_height_in_mbs = ev->tile_mb_h;
|
|
fh->tile_info.tile_size_present_in_fh_flag = 0;
|
|
|
|
/* Populate each tile. The compacted buffer holds each tile-component's
|
|
* data back to back, in (tile, component) order -- the same layout the
|
|
* gather shader produced. */
|
|
uint32_t comp_off = 0;
|
|
for (int t = 0; t < ev->tile_count; t++) {
|
|
APVRawTile *tile = &raw_frame->tile[t];
|
|
uint32_t total_tile_data = 0;
|
|
|
|
tile->tile_header.tile_header_size =
|
|
4 + ev->num_comp * (4 + 1) + 1;
|
|
tile->tile_header.tile_index = t;
|
|
|
|
for (int c = 0; c < ev->num_comp; c++) {
|
|
uint32_t sz;
|
|
if (ev->headers_only) {
|
|
/* No readback: one token byte (CBS requires size >= 1). */
|
|
sz = 1;
|
|
tile->tile_data[c] = &headers_only_tile;
|
|
} else {
|
|
sz = sizes[t * ev->num_comp + c];
|
|
tile->tile_data[c] = compacted_buf->mapped_mem + comp_off;
|
|
comp_off += sz;
|
|
}
|
|
tile->tile_header.tile_data_size[c] = sz;
|
|
tile->tile_header.tile_qp[c] =
|
|
(c == 0 || c == 3) ? ev->qp_y : ev->qp_c;
|
|
total_tile_data += sz;
|
|
}
|
|
tile->tile_header.reserved_zero_8bits = 0;
|
|
tile->tile_dummy_byte_size = 0;
|
|
tile->tile_dummy_byte = NULL;
|
|
|
|
raw_frame->tile_size[t] =
|
|
tile->tile_header.tile_header_size + total_tile_data;
|
|
}
|
|
|
|
/* Assemble fragment using cbs_apv */
|
|
ff_cbs_fragment_reset(&ev->au);
|
|
|
|
err = ff_cbs_insert_unit_content(&ev->au, -1, APV_PBU_PRIMARY_FRAME,
|
|
raw_frame, NULL);
|
|
if (err < 0) {
|
|
av_freep(&raw_frame);
|
|
return err;
|
|
}
|
|
/* raw_frame is now owned by the fragment unit */
|
|
raw_frame = NULL;
|
|
|
|
/* Assemble straight into the packet: ff_cbs_write_packet() hands pkt a
|
|
* reference to CBS's own assembled buffer -- no copy. */
|
|
err = ff_cbs_write_packet(ev->cbc, pkt, &ev->au);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
pkt->pts = fd->pts;
|
|
pkt->dts = fd->pts;
|
|
pkt->duration = fd->duration;
|
|
pkt->flags |= AV_PKT_FLAG_KEY; /* APV is all intra */
|
|
|
|
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;
|
|
}
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, "Encoded APV frame: %i bytes (%.2f MiB)\n",
|
|
pkt->size, pkt->size / (1024.0 * 1024.0));
|
|
|
|
av_buffer_unref(&fd->coeffs_ref);
|
|
av_buffer_unref(&fd->bytestream_ref);
|
|
av_buffer_unref(&fd->compacted_ref);
|
|
av_buffer_unref(&fd->sizes_ref);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vulkan_encode_apv_receive_packet(AVCodecContext *avctx,
|
|
AVPacket *pkt)
|
|
{
|
|
int err;
|
|
VulkanEncodeAPVContext *ev = avctx->priv_data;
|
|
VulkanEncodeAPVFrameData *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;
|
|
fd->flags = frame->flags;
|
|
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);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static av_cold int vulkan_encode_apv_close(AVCodecContext *avctx)
|
|
{
|
|
VulkanEncodeAPVContext *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[0]);
|
|
ff_vk_shader_free(&ev->s, &ev->shd_entropy[1]);
|
|
ff_vk_shader_free(&ev->s, &ev->shd_compact);
|
|
|
|
if (ev->exec_ctx_info) {
|
|
for (int i = 0; i < ev->async_depth; i++) {
|
|
VulkanEncodeAPVFrameData *fd = &ev->exec_ctx_info[i];
|
|
av_buffer_unref(&fd->coeffs_ref);
|
|
av_buffer_unref(&fd->bytestream_ref);
|
|
av_buffer_unref(&fd->compacted_ref);
|
|
av_buffer_unref(&fd->sizes_ref);
|
|
av_buffer_unref(&fd->frame_opaque_ref);
|
|
}
|
|
av_freep(&ev->exec_ctx_info);
|
|
}
|
|
|
|
av_buffer_pool_uninit(&ev->coeffs_pool);
|
|
av_buffer_pool_uninit(&ev->bytestream_pool);
|
|
av_buffer_pool_uninit(&ev->gathered_pool);
|
|
av_buffer_pool_uninit(&ev->compacted_pool);
|
|
av_buffer_pool_uninit(&ev->sizes_pool);
|
|
|
|
ff_cbs_fragment_free(&ev->au);
|
|
ff_cbs_close(&ev->cbc);
|
|
|
|
av_frame_free(&ev->frame);
|
|
ff_vk_uninit(&ev->s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static av_cold int vulkan_encode_apv_init(AVCodecContext *avctx)
|
|
{
|
|
int err;
|
|
VulkanEncodeAPVContext *ev = avctx->priv_data;
|
|
AVHWFramesContext *hwfc;
|
|
|
|
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;
|
|
ev->sw_format = hwfc->sw_format;
|
|
|
|
ev->profile_idc = profile_idc_from_pix_fmt(ev->sw_format);
|
|
ev->chroma_format_idc = chroma_format_from_pix_fmt(ev->sw_format);
|
|
if (ev->profile_idc < 0 || ev->chroma_format_idc < 0) {
|
|
av_log(avctx, AV_LOG_ERROR, "Unsupported sw_format %s for APV.\n",
|
|
av_get_pix_fmt_name(ev->sw_format));
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
/* All four APV chroma formats are supported -- 4:0:0, 4:2:2, 4:4:4 and
|
|
* 4:4:4:4. The profile_idc / chroma_format_idc checks above already
|
|
* reject any pixel format that is not one of them. */
|
|
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(ev->sw_format);
|
|
ev->bit_depth = desc->comp[0].depth;
|
|
ev->num_comp = desc->nb_components;
|
|
ev->blocks_per_mb = 4; /* luma: 16x16 MB -> 4 8x8 blocks */
|
|
ev->chroma_blocks_per_mb = 4 >> (desc->log2_chroma_w + desc->log2_chroma_h);
|
|
ev->level_idc = 33; /* placeholder, real value depends on resolution and bitrate */
|
|
ev->band_idc = 0;
|
|
|
|
/* Frame dimensions in macroblocks */
|
|
ev->frame_mb_x = (avctx->width + APV_MB_WIDTH - 1) / APV_MB_WIDTH;
|
|
ev->frame_mb_y = (avctx->height + APV_MB_HEIGHT - 1) / APV_MB_HEIGHT;
|
|
|
|
/* The 20x20 tile grid cap is structural (fixed-size arrays everywhere);
|
|
* the spec additionally demands tiles of at least 16x8 MBs. Each
|
|
* tile-component maps to one entropy workgroup, one invocation per
|
|
* transform block. */
|
|
int grid_tw = (ev->frame_mb_x + APV_MAX_TILE_COLS - 1) / APV_MAX_TILE_COLS;
|
|
int grid_th = (ev->frame_mb_y + APV_MAX_TILE_ROWS - 1) / APV_MAX_TILE_ROWS;
|
|
int min_tw = FFMAX(APV_MIN_TILE_WIDTH_IN_MBS, grid_tw);
|
|
int min_th = FFMAX(APV_MIN_TILE_HEIGHT_IN_MBS, grid_th);
|
|
|
|
/* tile_w/tile_h pick the tile size in MBs; 0 selects the spec minimum.
|
|
* An explicit request below the spec minimum is honoured down to the
|
|
* grid cap -- non-conformant, but more tiles mean shorter (serial)
|
|
* entropy streams, which is the decode speed lever. */
|
|
ev->tile_mb_w = ev->tile_w_mbs_opt > 0 ? ev->tile_w_mbs_opt : min_tw;
|
|
ev->tile_mb_h = ev->tile_h_mbs_opt > 0 ? ev->tile_h_mbs_opt : min_th;
|
|
ev->tile_mb_w = FFMIN(FFMAX(ev->tile_mb_w, grid_tw), ev->frame_mb_x);
|
|
ev->tile_mb_h = FFMIN(FFMAX(ev->tile_mb_h, grid_th), ev->frame_mb_y);
|
|
if (ev->tile_mb_w < APV_MIN_TILE_WIDTH_IN_MBS ||
|
|
ev->tile_mb_h < APV_MIN_TILE_HEIGHT_IN_MBS)
|
|
av_log(avctx, AV_LOG_WARNING,
|
|
"Tile size %dx%d MBs is below the spec minimum of %dx%d: "
|
|
"NON-CONFORMANT bitstream, most decoders will reject it.\n",
|
|
ev->tile_mb_w, ev->tile_mb_h,
|
|
APV_MIN_TILE_WIDTH_IN_MBS, APV_MIN_TILE_HEIGHT_IN_MBS);
|
|
|
|
/* Left to default, grow the tile toward 1024 transform blocks (the
|
|
* entropy workgroup ceiling) while it still divides the frame. Bigger
|
|
* tiles mean fewer tile-components, which the compaction pass strongly
|
|
* prefers -- it is the dominant win for throughput. */
|
|
if (!ev->tile_w_mbs_opt && !ev->tile_h_mbs_opt) {
|
|
while (ev->tile_mb_w * 2 <= ev->frame_mb_x &&
|
|
ev->frame_mb_x % (ev->tile_mb_w * 2) == 0 &&
|
|
(ev->tile_mb_w * 2) * ev->tile_mb_h * ev->blocks_per_mb <= 1024)
|
|
ev->tile_mb_w *= 2;
|
|
while (ev->tile_mb_h * 2 <= ev->frame_mb_y &&
|
|
ev->frame_mb_y % (ev->tile_mb_h * 2) == 0 &&
|
|
ev->tile_mb_w * (ev->tile_mb_h * 2) * ev->blocks_per_mb <= 1024)
|
|
ev->tile_mb_h *= 2;
|
|
}
|
|
|
|
/* Ceil division: the rightmost column / bottom row of tiles take the
|
|
* remainder MBs (spec-legal; the tile grid is closed at the frame edge,
|
|
* so those tiles may be smaller than the signalled tile size). */
|
|
ev->tile_cols = (ev->frame_mb_x + ev->tile_mb_w - 1) / ev->tile_mb_w;
|
|
ev->tile_rows = (ev->frame_mb_y + ev->tile_mb_h - 1) / ev->tile_mb_h;
|
|
ev->tile_count = ev->tile_cols * ev->tile_rows;
|
|
|
|
if (ev->tile_count > APV_MAX_TILE_COUNT) {
|
|
av_log(avctx, AV_LOG_ERROR, "Too many tiles (%d).\n", ev->tile_count);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
/* The entropy shader runs one invocation per block in a tile-component
|
|
* and its shared buffers are sized for 1024. */
|
|
if (ev->tile_mb_w * ev->tile_mb_h * ev->blocks_per_mb > 1024) {
|
|
av_log(avctx, AV_LOG_ERROR,
|
|
"Tile-component has too many transform blocks (%d > 1024).\n",
|
|
ev->tile_mb_w * ev->tile_mb_h * ev->blocks_per_mb);
|
|
return AVERROR_PATCHWELCOME;
|
|
}
|
|
|
|
/* qp_chroma left at 0 means "use the luma QP". */
|
|
if (ev->qp_c == 0)
|
|
ev->qp_c = ev->qp_y;
|
|
|
|
/* Validate QP range */
|
|
int max_qp = 3 + ev->bit_depth * 6;
|
|
if (ev->qp_y < 0 || ev->qp_y > max_qp || ev->qp_c < 0 || ev->qp_c > max_qp) {
|
|
av_log(avctx, AV_LOG_ERROR,
|
|
"QP out of range [0, %d]: qp_y=%d, qp_c=%d.\n",
|
|
max_qp, ev->qp_y, ev->qp_c);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
/* Buffer sizing */
|
|
size_t blocks_per_tile = (size_t)ev->tile_mb_w * ev->tile_mb_h * ev->blocks_per_mb;
|
|
ev->coeffs_size = (size_t)ev->tile_count * ev->num_comp *
|
|
blocks_per_tile * APV_BLK_COEFFS * sizeof(int16_t);
|
|
|
|
/* Worst-case per-tile-component bytestream: each coefficient at most ~32 bits.
|
|
* Round up generously. */
|
|
ev->slot_size = blocks_per_tile * APV_BLK_COEFFS * 8;
|
|
ev->slot_size = FFALIGN(ev->slot_size, 64);
|
|
ev->bytestream_size = (size_t)ev->tile_count * ev->num_comp * ev->slot_size;
|
|
ev->sizes_size = (size_t)ev->tile_count * ev->num_comp * sizeof(uint32_t);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE,
|
|
"APV Vulkan encoder: %dx%d, %d tiles (%dx%d MBs each), "
|
|
"qp_y=%d qp_c=%d, coeffs=%zu KiB, bytestream=%zu KiB\n",
|
|
avctx->width, avctx->height, ev->tile_count,
|
|
ev->tile_mb_w, ev->tile_mb_h, ev->qp_y, ev->qp_c,
|
|
ev->coeffs_size / 1024, ev->bytestream_size / 1024);
|
|
|
|
ev->headers_only = !!getenv("APV_VULKAN_HEADERS_ONLY");
|
|
ev->skip_entropy = !!getenv("APV_VULKAN_SKIP_ENTROPY");
|
|
if (ev->skip_entropy)
|
|
ev->headers_only = 1; /* the bitstream is never produced */
|
|
if (ev->headers_only)
|
|
av_log(avctx, AV_LOG_WARNING,
|
|
"APV_VULKAN_HEADERS_ONLY set: tiles will not be downloaded "
|
|
"or assembled; output packets contain headers only.\n");
|
|
if (ev->skip_entropy)
|
|
av_log(avctx, AV_LOG_WARNING,
|
|
"APV_VULKAN_SKIP_ENTROPY set: entropy dispatch skipped "
|
|
"(DCT-only benchmark mode).\n");
|
|
|
|
/* Init Vulkan */
|
|
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;
|
|
|
|
/* Init CBS for assembling output */
|
|
err = ff_cbs_init(&ev->cbc, AV_CODEC_ID_APV, avctx);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* Shaders */
|
|
err = init_dct_shader(avctx);
|
|
if (err < 0)
|
|
return err;
|
|
err = init_entropy_shader(avctx, ev->blocks_per_mb, &ev->shd_entropy[0]);
|
|
if (err < 0)
|
|
return err;
|
|
err = init_entropy_shader(avctx, ev->chroma_blocks_per_mb,
|
|
&ev->shd_entropy[1]);
|
|
if (err < 0)
|
|
return err;
|
|
err = init_compact_shader(avctx);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* The DCT/quantize shader's push constants never change frame to frame;
|
|
* build them once. */
|
|
build_dct_push_const(avctx);
|
|
|
|
ev->frame = av_frame_alloc();
|
|
if (!ev->frame)
|
|
return AVERROR(ENOMEM);
|
|
|
|
/* Async data pool */
|
|
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)
|
|
return AVERROR(ENOMEM);
|
|
for (int i = 0; i < ev->async_depth; i++)
|
|
ev->exec_pool.contexts[i].opaque = &ev->exec_ctx_info[i];
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define OFFSET(x) offsetof(VulkanEncodeAPVContext, x)
|
|
#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
|
|
static const AVOption vulkan_encode_apv_options[] = {
|
|
{ "qp", "Quantization parameter (luma)", OFFSET(qp_y),
|
|
AV_OPT_TYPE_INT, { .i64 = 22 }, 0, 255, VE },
|
|
{ "qp_chroma", "Chroma quantization parameter (0 = same as luma qp)", OFFSET(qp_c),
|
|
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 255, VE },
|
|
{ "qmatrix", "Quantization matrix", OFFSET(qmatrix),
|
|
AV_OPT_TYPE_INT, { .i64 = APV_QMATRIX_HEVC }, 0, 1, VE, "qmatrix" },
|
|
{ "flat", "Uniform matrix, all 16 (APV spec default)", 0,
|
|
AV_OPT_TYPE_CONST, { .i64 = APV_QMATRIX_FLAT }, 0, 0, VE, "qmatrix" },
|
|
{ "hevc", "HEVC default intra scaling list (mild perceptual shaping)", 0,
|
|
AV_OPT_TYPE_CONST, { .i64 = APV_QMATRIX_HEVC }, 0, 0, VE, "qmatrix" },
|
|
/* The minimum legal tile is 16x8 MBs; the maxima are this encoder's
|
|
* ceiling of 1024 transform blocks per tile-component (256 MBs): with
|
|
* the other dimension at its minimum, width <= 32 and height <= 16. A
|
|
* value of 0 is the sentinel for the adaptive per-frame default. */
|
|
{ "tile_width", "Tile width in macroblocks (0 = adaptive, auto-sized per frame)", OFFSET(tile_w_mbs_opt),
|
|
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 32, VE },
|
|
{ "tile_height", "Tile height in macroblocks (0 = adaptive, auto-sized per frame)", OFFSET(tile_h_mbs_opt),
|
|
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 16, VE },
|
|
{ "async_depth", "Internal parallelization depth", OFFSET(async_depth),
|
|
AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX, VE },
|
|
{ NULL }
|
|
};
|
|
|
|
static const FFCodecDefault vulkan_encode_apv_defaults[] = {
|
|
{ "g", "1" },
|
|
{ NULL },
|
|
};
|
|
|
|
static const AVClass vulkan_encode_apv_class = {
|
|
.class_name = "apv_vulkan",
|
|
.item_name = av_default_item_name,
|
|
.option = vulkan_encode_apv_options,
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
};
|
|
|
|
static const AVCodecHWConfigInternal *const vulkan_encode_apv_hw_configs[] = {
|
|
HW_CONFIG_ENCODER_FRAMES(VULKAN, VULKAN),
|
|
NULL,
|
|
};
|
|
|
|
const FFCodec ff_apv_vulkan_encoder = {
|
|
.p.name = "apv_vulkan",
|
|
CODEC_LONG_NAME("Advanced Professional Video (Vulkan)"),
|
|
.p.type = AVMEDIA_TYPE_VIDEO,
|
|
.p.id = AV_CODEC_ID_APV,
|
|
.priv_data_size = sizeof(VulkanEncodeAPVContext),
|
|
.init = &vulkan_encode_apv_init,
|
|
FF_CODEC_RECEIVE_PACKET_CB(&vulkan_encode_apv_receive_packet),
|
|
.close = &vulkan_encode_apv_close,
|
|
.p.priv_class = &vulkan_encode_apv_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,
|
|
.defaults = vulkan_encode_apv_defaults,
|
|
CODEC_PIXFMTS(AV_PIX_FMT_VULKAN),
|
|
.hw_configs = vulkan_encode_apv_hw_configs,
|
|
.p.wrapper_name = "vulkan",
|
|
};
|