Files
FFmpeg/libswscale/vulkan/ops.c
T
Niklas HaasandClaude Opus 5.5 bcf6712d01 swscale/uops: factor out ff_sws_uop_list_print()
The same loop was duplicated in three backends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Niklas Haas <git@haasn.dev>
2026-10-29 15:47:35 +00:00

1337 lines
48 KiB
C

/**
* Copyright (C) 2026 Lynne
*
* 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/refstruct.h"
#include "../graph.h"
#include "../ops_internal.h"
#include "../swscale_internal.h"
#include "../uops.h"
#include "ops.h"
#include "spvasm.h"
static void vk_ops_ctx_uninit(AVRefStructOpaque opaque, void *obj)
{
FFVulkanOpsCtx *s = obj;
ff_vk_uninit(&s->vkctx);
}
int ff_sws_vk_init(SwsContext *sws, AVBufferRef *dev_ref)
{
int err;
SwsInternal *c = sws_internal(sws);
if (!c->hw_priv) {
c->hw_priv = av_refstruct_alloc_ext(sizeof(FFVulkanOpsCtx), 0, NULL,
vk_ops_ctx_uninit);
if (!c->hw_priv)
return AVERROR(ENOMEM);
}
FFVulkanOpsCtx *s = c->hw_priv;
if (s->vkctx.device_ref) {
if (s->vkctx.device_ref->data == dev_ref->data)
return 0;
/* Reinitialize with new context */
ff_vk_uninit(&s->vkctx);
}
err = ff_vk_init(&s->vkctx, sws, dev_ref, NULL);
if (err < 0)
return err;
s->qf = ff_vk_qf_find(&s->vkctx, VK_QUEUE_COMPUTE_BIT, 0);
if (!s->qf) {
av_log(sws, AV_LOG_ERROR, "Device has no compute queues\n");
return AVERROR(ENOTSUP);
}
return 0;
}
AVBufferRef *ff_sws_vk_device_ref(SwsContext *sws)
{
SwsInternal *c = sws_internal(sws);
FFVulkanOpsCtx *s = c->hw_priv;
return s ? s->vkctx.device_ref : NULL;
}
#define MAX_DITHER_BUFS 4
#define MAX_FILT_BUFS 4
#define MAX_DATA_BUFS (MAX_DITHER_BUFS + MAX_FILT_BUFS*4)
typedef struct VulkanPriv {
FFVulkanOpsCtx *s;
FFVkExecPool e;
FFVulkanShader shd;
FFVkBuffer data_bufs[MAX_DATA_BUFS];
int nb_data_bufs;
VkFormat src_view_fmt[AV_NUM_DATA_POINTERS];
VkFormat dst_view_fmt[AV_NUM_DATA_POINTERS];
int interlaced;
} VulkanPriv;
static void spirv_process(const SwsFrame *dst, const SwsFrame *src,
int y, int h, const SwsPass *pass)
{
VulkanPriv *p = (VulkanPriv *) pass->priv;
FFVkExecContext *ec = ff_vk_exec_get(&p->s->vkctx, &p->e);
FFVulkanFunctions *vk = &p->s->vkctx.vkfn;
int err = ff_vk_exec_start(&p->s->vkctx, ec);
if (err < 0)
goto error;
AVFrame *src_f = (AVFrame *) src->avframe;
AVFrame *dst_f = (AVFrame *) dst->avframe;
err = ff_vk_exec_add_dep_frame(&p->s->vkctx, ec, src_f,
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT);
if (err < 0)
goto fail;
err = ff_vk_exec_add_dep_frame(&p->s->vkctx, ec, dst_f,
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT);
if (err < 0)
goto fail;
VkImageView src_views[AV_NUM_DATA_POINTERS];
VkImageView dst_views[AV_NUM_DATA_POINTERS];
err = ff_vk_create_imageviews_fmt(&p->s->vkctx, ec, src_views, src_f,
p->src_view_fmt);
if (err < 0)
goto fail;
err = ff_vk_create_imageviews_fmt(&p->s->vkctx, ec, dst_views, dst_f,
p->dst_view_fmt);
if (err < 0)
goto fail;
ff_vk_shader_update_img_array(&p->s->vkctx, ec, &p->shd, src_f, src_views,
0, 0, VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE);
ff_vk_shader_update_img_array(&p->s->vkctx, ec, &p->shd, dst_f, dst_views,
0, 1, VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE);
int nb_img_bar = 0;
VkImageMemoryBarrier2 img_bar[8];
ff_vk_frame_barrier(&p->s->vkctx, ec, src_f, 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);
ff_vk_frame_barrier(&p->s->vkctx, ec, dst_f, img_bar, &nb_img_bar,
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
VK_ACCESS_SHADER_WRITE_BIT,
VK_IMAGE_LAYOUT_GENERAL,
VK_QUEUE_FAMILY_IGNORED);
vk->CmdPipelineBarrier2(ec->buf, &(VkDependencyInfo) {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.pImageMemoryBarriers = img_bar,
.imageMemoryBarrierCount = nb_img_bar,
});
if (p->interlaced) {
uint32_t field = pass->graph ? pass->graph->dst.field : 0;
ff_vk_shader_update_push_const(&p->s->vkctx, ec, &p->shd,
VK_SHADER_STAGE_COMPUTE_BIT,
0, sizeof(field), &field);
}
ff_vk_exec_bind_shader(&p->s->vkctx, ec, &p->shd);
vk->CmdDispatch(ec->buf,
FFALIGN(dst->width, p->shd.lg_size[0])/p->shd.lg_size[0],
FFALIGN(dst->height, p->shd.lg_size[1])/p->shd.lg_size[1],
1);
err = ff_vk_exec_submit(&p->s->vkctx, ec);
if (err < 0)
goto error;
ff_vk_exec_wait(&p->s->vkctx, ec);
return;
fail:
ff_vk_exec_discard(&p->s->vkctx, ec);
error:
/* SwsPassFunc has no way to return errors */
av_log(&p->s->vkctx, AV_LOG_ERROR, "Vulkan pass failed: %s\n",
av_err2str(err));
}
static void spirv_free(void *priv)
{
VulkanPriv *p = priv;
ff_vk_exec_pool_free(&p->s->vkctx, &p->e);
ff_vk_shader_free(&p->s->vkctx, &p->shd);
for (int i = 0; i < p->nb_data_bufs; i++)
ff_vk_free_buf(&p->s->vkctx, &p->data_bufs[i]);
av_refstruct_unref(&p->s);
av_free(priv);
}
static int create_filter_buf(FFVulkanOpsCtx *s, const SwsFilterWeights *wd,
FFVkBuffer *buf)
{
int err;
/* Weights */
err = ff_vk_create_buf(&s->vkctx, buf,
wd->num_weights*sizeof(float) +
wd->dst_size*sizeof(int32_t), NULL, NULL,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
if (err < 0)
goto fail;
float *weights_data;
err = ff_vk_map_buffer(&s->vkctx, buf,
(uint8_t **)&weights_data, 0);
if (err < 0)
goto fail;
for (int i = 0; i < wd->num_weights; i++)
weights_data[i] = (float) wd->weights[i] / SWS_FILTER_SCALE;
memcpy(weights_data + wd->num_weights,
wd->offsets, wd->dst_size*sizeof(int32_t));
ff_vk_unmap_buffer(&s->vkctx, buf, 1);
return 0;
fail:
ff_vk_free_buf(&s->vkctx, buf);
return err;
}
static int create_dither_buf(FFVulkanOpsCtx *s, const SwsUOp *uop,
FFVkBuffer *buf)
{
int err;
const int size = 1 << uop->par.dither.size_log2;
const int rows = ff_sws_dither_height(&uop->par.dither);
err = ff_vk_create_buf(&s->vkctx, buf,
rows*size*sizeof(float), NULL, NULL,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
if (err < 0)
return err;
float *dither_data;
err = ff_vk_map_buffer(&s->vkctx, buf, (uint8_t **)&dither_data, 0);
if (err < 0)
goto fail;
for (int i = 0; i < rows*size; i++)
dither_data[i] = uop->data.ptr[i].f32;
ff_vk_unmap_buffer(&s->vkctx, buf, 1);
return 0;
fail:
ff_vk_free_buf(&s->vkctx, buf);
return err;
}
typedef struct SPIRVIDs {
/* Entrypoint interface variables */
int iface[3 + MAX_DATA_BUFS + 1];
int nb_iface;
int gid_var;
int in_img_var;
int out_img_var;
int glfn;
int ep;
/* Types */
int void_type;
int b_type;
int u32_type;
int i32_type;
int f32_type;
int void_fn_type;
/* Define vector types */
int bvec2_type;
int u32vec2_type;
int i32vec2_type;
int u32vec3_type;
int u32vec4_type;
int f32vec4_type;
int f32mat4_type;
/* Constants */
int f32_0;
int u32_cid[5];
int dither_ptr_elem_id;
int out_img_type;
int out_img_array_id;
int in_img_type;
int in_img_array_id;
/* Pointer types for images */
int u32vec3_tptr;
int out_img_tptr;
int out_img_sptr;
int in_img_tptr;
int in_img_sptr;
/* Interlaced handling */
int interlaced;
int push_const_struct_id;
int push_const_ptr_id;
int push_const_elem_ptr_id;
int push_const_var_id;
int field_i32;
VulkanPriv *priv;
FFVulkanDescriptorSetBinding buf_desc[MAX_DATA_BUFS]; /* set 1 */
const SwsOpList *ops;
int bitexact;
/* Per-invocation state of the main function */
int in_img[4], out_img[4]; /* image handles, indexed by plane */
int gid, gi2; /* invocation position, as uvec2 and ivec2 */
int src_gid, dst_gid; /* image position to read from / write to */
} SPIRVIDs;
/* All integer pixel types are represented as 32-bit unsigned integers */
static int type_scalar(const SPIRVIDs *id, SwsPixelType type)
{
av_assert1(type != SWS_PIXEL_NONE);
return ff_sws_pixel_type_is_int(type) ? id->u32_type : id->f32_type;
}
static int type_vec4(const SPIRVIDs *id, SwsPixelType type)
{
av_assert1(type != SWS_PIXEL_NONE);
return ff_sws_pixel_type_is_int(type) ? id->u32vec4_type : id->f32vec4_type;
}
static int const_zero(const SPIRVIDs *id, SwsPixelType type)
{
av_assert1(type != SWS_PIXEL_NONE);
return ff_sws_pixel_type_is_int(type) ? id->u32_cid[0] : id->f32_0;
}
static int const_pixel(SPICtx *spi, const SPIRVIDs *id, SwsPixelType type,
SwsPixel px)
{
switch (type) {
case SWS_PIXEL_U8: return spi_OpConstantUInt(spi, id->u32_type, px.u8);
case SWS_PIXEL_U16: return spi_OpConstantUInt(spi, id->u32_type, px.u16);
case SWS_PIXEL_U32: return spi_OpConstantUInt(spi, id->u32_type, px.u32);
case SWS_PIXEL_F32: return spi_OpConstantFloat(spi, id->f32_type, px.f32);
default: break;
}
av_unreachable("Invalid pixel type!");
return 0;
}
static int const_splat(SPICtx *spi, const SPIRVIDs *id, SwsPixelType type,
SwsPixel px)
{
const int c = const_pixel(spi, id, type, px);
return spi_OpConstantComposite(spi, type_vec4(id, type), c, c, c, c);
}
/* Shift and mask vectors for (un)packing each component */
static void define_pack_consts(SPICtx *spi, const SPIRVIDs *id, const SwsUOp *uop,
int *shift_id, int *mask_id)
{
uint64_t mask[4];
int shift[4], tmp[4];
ff_sws_pack_decode(uop->par.pack.pattern, mask, shift);
for (int i = 0; i < 4; i++)
tmp[i] = spi_OpConstantUInt(spi, id->u32_type, shift[i]);
*shift_id = spi_OpConstantComposite(spi, id->u32vec4_type,
tmp[0], tmp[1], tmp[2], tmp[3]);
for (int i = 0; i < 4; i++)
tmp[i] = spi_OpConstantUInt(spi, id->u32_type, mask[i]);
*mask_id = spi_OpConstantComposite(spi, id->u32vec4_type,
tmp[0], tmp[1], tmp[2], tmp[3]);
}
/* Arithmetic on operands of the given pixel type */
static int emit_add(SPICtx *spi, SwsPixelType type, int res_type, int a, int b)
{
return ff_sws_pixel_type_is_int(type) ? spi_OpIAdd(spi, res_type, a, b)
: spi_OpFAdd(spi, res_type, a, b);
}
static int emit_mul(SPICtx *spi, SwsPixelType type, int res_type, int a, int b)
{
return ff_sws_pixel_type_is_int(type) ? spi_OpIMul(spi, res_type, a, b)
: spi_OpFMul(spi, res_type, a, b);
}
static int emit_min(SPICtx *spi, const SPIRVIDs *id, SwsPixelType type,
int res_type, int a, int b)
{
const int op = ff_sws_pixel_type_is_int(type) ? GLSLstd450UMin : GLSLstd450FMin;
return spi_OpExtInst(spi, res_type, id->glfn, op, a, b);
}
static int emit_max(SPICtx *spi, const SPIRVIDs *id, SwsPixelType type,
int res_type, int a, int b)
{
const int op = ff_sws_pixel_type_is_int(type) ? GLSLstd450UMax : GLSLstd450FMax;
return spi_OpExtInst(spi, res_type, id->glfn, op, a, b);
}
/* Converts between float and integer values, if needed */
static int emit_convert(SPICtx *spi, SwsPixelType from, SwsPixelType to,
int res_type, int a)
{
if (ff_sws_pixel_type_is_int(from) == ff_sws_pixel_type_is_int(to))
return a;
return ff_sws_pixel_type_is_int(to) ? spi_OpConvertFToU(spi, res_type, a)
: spi_OpConvertUToF(spi, res_type, a);
}
/* Section 1: Function to define all shader header data, and decorations */
static void define_shader_header(SPICtx *spi, SPIRVIDs *id)
{
spi_OpCapability(spi, SpvCapabilityShader); /* Shader type */
/* Declare required capabilities */
spi_OpCapability(spi, SpvCapabilityImageQuery);
spi_OpCapability(spi, SpvCapabilityStorageImageReadWithoutFormat);
spi_OpCapability(spi, SpvCapabilityStorageImageWriteWithoutFormat);
/* Import the GLSL set of functions (used for min/max) */
id->glfn = spi_OpExtInstImport(spi, "GLSL.std.450");
/* Next section starts here */
spi_OpMemoryModel(spi, SpvAddressingModelLogical, SpvMemoryModelGLSL450);
}
/* Section 2: Define all types and constants */
static void define_shader_consts(SPICtx *spi, SPIRVIDs *id)
{
/* Define scalar types */
id->void_type = spi_OpTypeVoid(spi);
id->b_type = spi_OpTypeBool(spi);
int u32_type =
id->u32_type = spi_OpTypeInt(spi, 32, 0);
id->i32_type = spi_OpTypeInt(spi, 32, 1);
int f32_type =
id->f32_type = spi_OpTypeFloat(spi, 32);
id->void_fn_type = spi_OpTypeFunction(spi, id->void_type, NULL, 0);
/* Define vector types */
id->bvec2_type = spi_OpTypeVector(spi, id->b_type, 2);
id->u32vec2_type = spi_OpTypeVector(spi, u32_type, 2);
id->i32vec2_type = spi_OpTypeVector(spi, id->i32_type, 2);
id->u32vec3_type = spi_OpTypeVector(spi, u32_type, 3);
id->u32vec4_type = spi_OpTypeVector(spi, u32_type, 4);
id->f32vec4_type = spi_OpTypeVector(spi, f32_type, 4);
id->f32mat4_type = spi_OpTypeMatrix(spi, id->f32vec4_type, 4);
/* Constants */
id->f32_0 = spi_OpConstantFloat(spi, f32_type, 0);
for (int i = 0; i < 5; i++)
id->u32_cid[i] = spi_OpConstantUInt(spi, u32_type, i);
}
/* Section 3: Define bindings */
static void define_shader_bindings(const SwsOpList *ops, SPICtx *spi, SPIRVIDs *id,
int in_img_count, int out_img_count)
{
id->dither_ptr_elem_id = spi_OpTypePointer(spi, SpvStorageClassUniform,
id->f32_type);
const SwsOp *op_w = ff_sws_op_list_output(ops);
const SwsOp *op_r = ff_sws_op_list_input(ops);
/* Define image types for descriptors */
id->out_img_type = spi_OpTypeImage(spi, type_scalar(id, op_w->rw.type),
2, 0, 0, 0, 2, SpvImageFormatUnknown);
id->out_img_array_id = spi_OpTypeArray(spi, id->out_img_type, spi_get_id(spi),
id->u32_cid[out_img_count]);
id->in_img_type = 0;
id->in_img_array_id = 0;
if (op_r) {
/* If the formats match, we have to reuse the types due to SPIR-V not
* allowing redundant type defines */
int match = type_scalar(id, op_w->rw.type) ==
type_scalar(id, op_r->rw.type);
id->in_img_type = match ? id->out_img_type :
spi_OpTypeImage(spi, type_scalar(id, op_r->rw.type),
2, 0, 0, 0, 2, SpvImageFormatUnknown);
id->in_img_array_id = spi_OpTypeArray(spi, id->in_img_type, spi_get_id(spi),
id->u32_cid[in_img_count]);
}
/* Pointer types for images */
id->u32vec3_tptr = spi_OpTypePointer(spi, SpvStorageClassInput,
id->u32vec3_type);
id->out_img_tptr = spi_OpTypePointer(spi, SpvStorageClassUniformConstant,
id->out_img_array_id);
id->out_img_sptr = spi_OpTypePointer(spi, SpvStorageClassUniformConstant,
id->out_img_type);
id->in_img_tptr = 0;
id->in_img_sptr = 0;
if (op_r) {
id->in_img_tptr = spi_OpTypePointer(spi, SpvStorageClassUniformConstant,
id->in_img_array_id);
id->in_img_sptr = spi_OpTypePointer(spi, SpvStorageClassUniformConstant,
id->in_img_type);
}
/* Define inputs */
id->gid_var = spi_OpVariable(spi, spi_get_id(spi), id->u32vec3_tptr,
SpvStorageClassInput, 0);
id->iface[id->nb_iface++] = id->gid_var;
spi_OpDecorate(spi, id->gid_var, SpvDecorationBuiltIn,
SpvBuiltInGlobalInvocationId);
if (op_r) {
id->in_img_var = spi_OpVariable(spi, spi_get_id(spi), id->in_img_tptr,
SpvStorageClassUniformConstant, 0);
id->iface[id->nb_iface++] = id->in_img_var;
spi_OpDecorate(spi, id->in_img_var, SpvDecorationNonWritable);
spi_OpDecorate(spi, id->in_img_var, SpvDecorationDescriptorSet, 0);
spi_OpDecorate(spi, id->in_img_var, SpvDecorationBinding, 0);
}
id->out_img_var = spi_OpVariable(spi, spi_get_id(spi), id->out_img_tptr,
SpvStorageClassUniformConstant, 0);
id->iface[id->nb_iface++] = id->out_img_var;
spi_OpDecorate(spi, id->out_img_var, SpvDecorationNonReadable);
spi_OpDecorate(spi, id->out_img_var, SpvDecorationDescriptorSet, 0);
spi_OpDecorate(spi, id->out_img_var, SpvDecorationBinding, 1);
if (id->interlaced) {
id->push_const_struct_id = spi_get_id(spi);
spi_OpTypeStruct(spi, id->push_const_struct_id, id->u32_type);
spi_OpDecorate(spi, id->push_const_struct_id, SpvDecorationBlock);
spi_OpMemberDecorate(spi, id->push_const_struct_id, 0,
SpvDecorationOffset, 0);
id->push_const_ptr_id = spi_OpTypePointer(spi, SpvStorageClassPushConstant,
id->push_const_struct_id);
id->push_const_elem_ptr_id = spi_OpTypePointer(spi, SpvStorageClassPushConstant,
id->u32_type);
id->push_const_var_id = spi_OpVariable(spi, spi_get_id(spi),
id->push_const_ptr_id,
SpvStorageClassPushConstant, 0);
id->iface[id->nb_iface++] = id->push_const_var_id;
}
}
/* Must be called after all interface variables have been defined */
static void define_entry_point(FFVulkanShader *shd, SPICtx *spi, SPIRVIDs *id)
{
spi_OpEntryPoint(spi, SpvExecutionModelGLCompute, id->ep, "main",
id->iface, id->nb_iface);
spi_OpExecutionMode(spi, id->ep, SpvExecutionModeLocalSize,
shd->lg_size, 3);
}
static int insert_vmat_linear(const SwsUOp *uop, SPICtx *spi, SPIRVIDs *id,
int data)
{
const SwsPixel (*m)[5] = uop->data.mat4x5;
int col[4], tmp[4];
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 4; i++)
tmp[i] = const_pixel(spi, id, SWS_PIXEL_F32, m[i][j]);
col[j] = spi_OpConstantComposite(spi, id->f32vec4_type,
tmp[0], tmp[1], tmp[2], tmp[3]);
}
int mat = spi_OpConstantComposite(spi, id->f32mat4_type,
col[0], col[1], col[2], col[3]);
for (int i = 0; i < 4; i++)
tmp[i] = const_pixel(spi, id, SWS_PIXEL_F32, m[i][4]);
int off = spi_OpConstantComposite(spi, id->f32vec4_type,
tmp[0], tmp[1], tmp[2], tmp[3]);
data = spi_OpMatrixTimesVector(spi, id->f32vec4_type, mat, data);
return spi_OpFAdd(spi, id->f32vec4_type, off, data);
}
static int insert_bitexact_linear(const SwsUOp *uop, SPICtx *spi, SPIRVIDs *id,
int data)
{
const SwsPixelType op_type = uop->type;
const SwsPixel (*m)[5] = uop->data.mat4x5;
const uint32_t zero = uop->par.lin.zero;
int type_s = type_scalar(id, op_type);
int type_v = type_vec4(id, op_type);
int tmp[4];
tmp[0] = spi_OpCompositeExtract(spi, type_s, data, 0);
tmp[1] = spi_OpCompositeExtract(spi, type_s, data, 1);
tmp[2] = spi_OpCompositeExtract(spi, type_s, data, 2);
tmp[3] = spi_OpCompositeExtract(spi, type_s, data, 3);
/* Same evaluation order as the C backend: offset first, then each column
* from left to right, skipping zero coefficients */
int res[4];
for (int j = 0; j < 4; j++) {
if (!SWS_COMP_TEST(uop->mask, j)) {
res[j] = tmp[j];
continue;
}
bool acc = !(zero & SWS_MASK_OFF(j));
res[j] = acc ? const_pixel(spi, id, op_type, m[j][4])
: const_zero(id, op_type);
for (int i = 0; i < 4; i++) {
if (zero & SWS_MASK(j, i))
continue;
int k = const_pixel(spi, id, op_type, m[j][i]);
int v = emit_mul(spi, op_type, type_s, tmp[i], k);
spi_OpDecorate(spi, v, SpvDecorationNoContraction);
if (acc) {
v = emit_add(spi, op_type, type_s, res[j], v);
spi_OpDecorate(spi, v, SpvDecorationNoContraction);
}
res[j] = v;
acc = true;
}
}
int result = spi_OpCompositeConstruct(spi, type_v,
res[0], res[1], res[2], res[3]);
/* Integer results are defined modulo the bit depth of the pixel type */
if (op_type == SWS_PIXEL_U8 || op_type == SWS_PIXEL_U16) {
const unsigned mask = op_type == SWS_PIXEL_U8 ? UINT8_MAX : UINT16_MAX;
const int mask_v = const_splat(spi, id, SWS_PIXEL_U32, (SwsPixel) { .u32 = mask });
result = spi_OpBitwiseAnd(spi, type_v, result, mask_v);
}
return result;
}
/* Registers the last created buffer in descriptor set 1, returns its binding */
static int add_buf_binding(SPIRVIDs *id)
{
VulkanPriv *p = id->priv;
id->buf_desc[p->nb_data_bufs] = (FFVulkanDescriptorSetBinding) {
.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
};
return p->nb_data_bufs++;
}
static int emit_read_filtered(SPICtx *spi, SPIRVIDs *id, const SwsUOp *uop,
int *data)
{
const SwsOpList *ops = id->ops;
const SwsFilterWeights *wd = uop->data.kernel;
VulkanPriv *p = id->priv;
if (p->nb_data_bufs >= MAX_DATA_BUFS)
return AVERROR(ENOTSUP);
int err = create_filter_buf(p->s, wd, &p->data_bufs[p->nb_data_bufs]);
if (err < 0)
return err;
const int binding = add_buf_binding(id);
/* Uniform buffer in descriptor set 1:
* struct { float w[dst_size][filter_size]; int o[dst_size]; } */
const int fs_id = spi_OpConstantUInt(spi, id->u32_type, wd->filter_size);
const int ds_id = spi_OpConstantUInt(spi, id->u32_type, wd->dst_size);
int arr_w_in = spi_OpTypeArray(spi, id->f32_type, spi_get_id(spi), fs_id);
spi_OpDecorate(spi, arr_w_in, SpvDecorationArrayStride, sizeof(float));
int arr_w_out = spi_OpTypeArray(spi, arr_w_in, spi_get_id(spi), ds_id);
spi_OpDecorate(spi, arr_w_out, SpvDecorationArrayStride,
wd->filter_size*sizeof(float));
int arr_o = spi_OpTypeArray(spi, id->i32_type, spi_get_id(spi), ds_id);
spi_OpDecorate(spi, arr_o, SpvDecorationArrayStride, sizeof(int32_t));
int struct_id = spi_get_id(spi);
spi_OpTypeStruct(spi, struct_id, arr_w_out, arr_o);
spi_OpDecorate(spi, struct_id, SpvDecorationBlock);
spi_OpMemberDecorate(spi, struct_id, 0, SpvDecorationOffset, 0);
spi_OpMemberDecorate(spi, struct_id, 1, SpvDecorationOffset,
wd->num_weights*sizeof(float));
int ptr = spi_OpTypePointer(spi, SpvStorageClassUniform, struct_id);
int buf = spi_OpVariable(spi, spi_get_id(spi), ptr, SpvStorageClassUniform, 0);
spi_OpDecorate(spi, buf, SpvDecorationDescriptorSet, 1);
spi_OpDecorate(spi, buf, SpvDecorationBinding, binding);
id->iface[id->nb_iface++] = buf;
const int o_ptr_type = spi_OpTypePointer(spi, SpvStorageClassUniform,
id->i32_type);
const int is_h = uop->uop == SWS_UOP_READ_PLANAR_FH;
const int src_interlaced = ops->src.interlaced;
const int read_vtype = type_vec4(id, uop->type);
/* Buffer array index along the filtered axis: pos.x (H) or pos.y (V) */
int axis = spi_OpCompositeExtract(spi, id->u32_type, id->gid, is_h ? 0 : 1);
/* int o = filter_o[axis]; */
int o_ptr = spi_OpAccessChain(spi, o_ptr_type, buf, id->u32_cid[1], axis);
int o = spi_OpLoad(spi, id->i32_type, o_ptr, SpvMemoryAccessMaskNone, 0);
/* Signed pixel position, for the non-filtered coordinate axis */
int pos_x = spi_OpCompositeExtract(spi, id->i32_type, id->gi2, 0);
int pos_y = spi_OpCompositeExtract(spi, id->i32_type, id->gi2, 1);
/* For interlaced horizontal filtering, the y coordinate of every tap is
* the (constant) destination y mapped into the source image. */
if (src_interlaced && is_h) {
pos_y = spi_OpShiftLeftLogical(spi, id->i32_type, pos_y, id->u32_cid[1]);
pos_y = spi_OpIAdd(spi, id->i32_type, pos_y, id->field_i32);
}
/* Accumulators, initialized to zero */
int acc[4] = { id->f32_0, id->f32_0, id->f32_0, id->f32_0 };
for (int t = 0; t < wd->filter_size; t++) {
const int tap = spi_OpConstantInt(spi, id->i32_type, t);
/* float w = filter_w[axis][t]; */
int w_ptr = spi_OpAccessChain(spi, id->dither_ptr_elem_id, buf,
id->u32_cid[0], axis, tap);
int w = spi_OpLoad(spi, id->f32_type, w_ptr,
SpvMemoryAccessMaskNone, 0);
/* Source coordinate, filtered axis offset by the tap index */
int c = t ? spi_OpIAdd(spi, id->i32_type, o, tap) : o;
/* For interlaced vertical filtering, the per-tap source row is
* field-local; map it to the actual image row. */
if (src_interlaced && !is_h) {
c = spi_OpShiftLeftLogical(spi, id->i32_type, c, id->u32_cid[1]);
c = spi_OpIAdd(spi, id->i32_type, c, id->field_i32);
}
int coord = is_h ?
spi_OpCompositeConstruct(spi, id->i32vec2_type, c, pos_y) :
spi_OpCompositeConstruct(spi, id->i32vec2_type, pos_x, c);
for (int e = 0; e < 4; e++) {
if (!SWS_COMP_TEST(uop->mask, e))
continue;
int px = spi_OpImageRead(spi, read_vtype,
id->in_img[ops->plane_src[e]], coord,
SpvImageOperandsMaskNone);
px = spi_OpCompositeExtract(spi, type_scalar(id, uop->type), px, 0);
px = emit_convert(spi, uop->type, SWS_PIXEL_F32, id->f32_type, px);
px = spi_OpFMul(spi, id->f32_type, w, px);
acc[e] = spi_OpFAdd(spi, id->f32_type, acc[e], px);
}
}
*data = spi_OpCompositeConstruct(spi, id->f32vec4_type,
acc[0], acc[1], acc[2], acc[3]);
return 0;
}
/* Sub-packed formats are accessed as a single raw word per pixel, which is
* then (un)packed manually. */
static VkFormat packed_word_fmt(const SwsOp *op)
{
if (!op || op->rw.mode != SWS_RW_PACKED || op->rw.elems != 1)
return VK_FORMAT_UNDEFINED;
switch (op->rw.type) {
case SWS_PIXEL_U8: return VK_FORMAT_R8_UINT;
case SWS_PIXEL_U16: return VK_FORMAT_R16_UINT;
case SWS_PIXEL_U32: return VK_FORMAT_R32_UINT;
default: return VK_FORMAT_UNDEFINED;
}
}
static int get_view_fmts(VkFormat view_fmts[AV_NUM_DATA_POINTERS],
enum AVPixelFormat fmt, const SwsOp *op)
{
const VkFormat *vk_fmts = av_vkfmt_from_pixfmt(fmt);
const VkFormat word_fmt = packed_word_fmt(op);
enum FFVkShaderRepFormat rep = FF_VK_REP_NATIVE;
if (op)
rep = ff_sws_pixel_type_is_int(op->rw.type) ? FF_VK_REP_UINT
: FF_VK_REP_FLOAT;
if (!vk_fmts)
return AVERROR(ENOTSUP);
const int nb_planes = av_pix_fmt_count_planes(fmt);
for (int i = 0; i < nb_planes; i++) {
view_fmts[i] = word_fmt ? word_fmt : ff_vk_map_fmt_to_rep(vk_fmts[i], rep);
if (view_fmts[i] == VK_FORMAT_UNDEFINED)
return AVERROR(ENOTSUP);
}
return 0;
}
/* Plane indices refer to actual frame planes, so the image handle arrays
* have to cover the highest plane referenced, not just the plane count. */
static int rw_op_img_count(const SwsOp *op, const uint8_t *planes)
{
const int nb_planes = ff_sws_rw_op_planes(op);
int count = 0;
for (int i = 0; i < nb_planes; i++)
count = FFMAX(count, planes[i] + 1);
return count;
}
static int emit_read(SPICtx *spi, SPIRVIDs *id, const SwsUOp *uop, int *data)
{
const SwsOpList *ops = id->ops;
const int type_v = type_vec4(id, uop->type);
const int type_s = type_scalar(id, uop->type);
const int zero = const_zero(id, uop->type);
switch (uop->uop) {
case SWS_UOP_READ_PLANAR_FH:
case SWS_UOP_READ_PLANAR_FV:
if (uop->par.filter.type != SWS_PIXEL_F32)
return AVERROR(ENOTSUP);
return emit_read_filtered(spi, id, uop, data);
case SWS_UOP_READ_PACKED:
*data = spi_OpImageRead(spi, type_v, id->in_img[ops->plane_src[0]],
id->src_gid, SpvImageOperandsMaskNone);
break;
case SWS_UOP_READ_PLANAR: {
int tmp[4] = { zero, zero, zero, zero };
for (int i = 0; i < 4; i++) {
if (!SWS_COMP_TEST(uop->mask, i))
continue;
tmp[i] = spi_OpImageRead(spi, type_v,
id->in_img[ops->plane_src[i]], id->src_gid,
SpvImageOperandsMaskNone);
tmp[i] = spi_OpCompositeExtract(spi, type_s, tmp[i], 0);
}
*data = spi_OpCompositeConstruct(spi, type_v,
tmp[0], tmp[1], tmp[2], tmp[3]);
break;
}
default:
return AVERROR(ENOTSUP);
}
return 0;
}
static int emit_write(SPICtx *spi, SPIRVIDs *id, const SwsUOp *uop, int *data)
{
const SwsOpList *ops = id->ops;
const int type_v = type_vec4(id, uop->type);
const int type_s = type_scalar(id, uop->type);
switch (uop->uop) {
case SWS_UOP_WRITE_PACKED:
spi_OpImageWrite(spi, id->out_img[ops->plane_dst[0]], id->dst_gid, *data,
SpvImageOperandsMaskNone);
break;
case SWS_UOP_WRITE_PLANAR:
for (int i = 0; i < 4; i++) {
if (!SWS_COMP_TEST(uop->mask, i))
continue;
int tmp = spi_OpCompositeExtract(spi, type_s, *data, i);
tmp = spi_OpCompositeConstruct(spi, type_v, tmp, tmp, tmp, tmp);
spi_OpImageWrite(spi, id->out_img[ops->plane_dst[i]], id->dst_gid, tmp,
SpvImageOperandsMaskNone);
}
break;
default:
return AVERROR(ENOTSUP);
}
return 0;
}
static int emit_clamp(SPICtx *spi, SPIRVIDs *id, const SwsUOp *uop, int *data)
{
const SwsPixelType type = uop->type;
const int type_v = type_vec4(id, type);
const int type_s = type_scalar(id, type);
for (int i = 0; i < 4; i++) {
if (!SWS_COMP_TEST(uop->mask, i))
continue;
int lim = const_pixel(spi, id, type, uop->data.vec4[i]);
int tmp = spi_OpCompositeExtract(spi, type_s, *data, i);
if (uop->uop == SWS_UOP_MIN)
tmp = emit_min(spi, id, type, type_s, tmp, lim);
else
tmp = emit_max(spi, id, type, type_s, tmp, lim);
*data = spi_OpCompositeInsert(spi, type_v, tmp, *data, i);
}
return 0;
}
static int emit_dither(SPICtx *spi, SPIRVIDs *id, const SwsUOp *uop, int *data)
{
if (uop->type != SWS_PIXEL_F32)
return AVERROR(ENOTSUP);
VulkanPriv *p = id->priv;
if (p->nb_data_bufs >= MAX_DATA_BUFS)
return AVERROR(ENOTSUP);
int err = create_dither_buf(p->s, uop, &p->data_bufs[p->nb_data_bufs]);
if (err < 0)
return err;
const int binding = add_buf_binding(id);
/* Uniform buffer in descriptor set 1: struct { float m[rows][size]; } */
const int size = 1 << uop->par.dither.size_log2;
const int rows = ff_sws_dither_height(&uop->par.dither);
const int size_id = spi_OpConstantUInt(spi, id->u32_type, size);
const int rows_id = spi_OpConstantUInt(spi, id->u32_type, rows);
const int mask_id = spi_OpConstantUInt(spi, id->u32_type, size - 1);
int arr_1d = spi_OpTypeArray(spi, id->f32_type, spi_get_id(spi), size_id);
spi_OpDecorate(spi, arr_1d, SpvDecorationArrayStride, sizeof(float));
int arr_2d = spi_OpTypeArray(spi, arr_1d, spi_get_id(spi), rows_id);
spi_OpDecorate(spi, arr_2d, SpvDecorationArrayStride, size*sizeof(float));
int struct_id = spi_get_id(spi);
spi_OpTypeStruct(spi, struct_id, arr_2d);
spi_OpDecorate(spi, struct_id, SpvDecorationBlock);
spi_OpMemberDecorate(spi, struct_id, 0, SpvDecorationOffset, 0);
int ptr = spi_OpTypePointer(spi, SpvStorageClassUniform, struct_id);
int buf = spi_OpVariable(spi, spi_get_id(spi), ptr, SpvStorageClassUniform, 0);
spi_OpDecorate(spi, buf, SpvDecorationDescriptorSet, 1);
spi_OpDecorate(spi, buf, SpvDecorationBinding, binding);
id->iface[id->nb_iface++] = buf;
/* The matrix is padded to allow offsetting the wrapped row */
int x_id = spi_OpCompositeExtract(spi, id->u32_type, id->gid, 0);
int y_id = spi_OpCompositeExtract(spi, id->u32_type, id->gid, 1);
x_id = spi_OpBitwiseAnd(spi, id->u32_type, x_id, mask_id);
y_id = spi_OpBitwiseAnd(spi, id->u32_type, y_id, mask_id);
for (int i = 0; i < 4; i++) {
if (!SWS_COMP_TEST(uop->mask, i))
continue;
int y_off = spi_OpConstantUInt(spi, id->u32_type,
uop->par.dither.y_offset[i]);
int row = spi_OpIAdd(spi, id->u32_type, y_id, y_off);
int val_ptr = spi_OpAccessChain(spi, id->dither_ptr_elem_id, buf,
id->u32_cid[0], row, x_id);
int val = spi_OpLoad(spi, id->f32_type, val_ptr,
SpvMemoryAccessMaskNone, 0);
int tmp = spi_OpCompositeExtract(spi, id->f32_type, *data, i);
tmp = spi_OpFAdd(spi, id->f32_type, tmp, val);
*data = spi_OpCompositeInsert(spi, id->f32vec4_type, tmp, *data, i);
}
return 0;
}
static int emit_linear(SPICtx *spi, SPIRVIDs *id, const SwsUOp *uop, int *data)
{
/* Matrix operations are only defined for floats */
if (id->bitexact || ff_sws_pixel_type_is_int(uop->type))
*data = insert_bitexact_linear(uop, spi, id, *data);
else
*data = insert_vmat_linear(uop, spi, id, *data);
return 0;
}
static int emit_pack(SPICtx *spi, SPIRVIDs *id, const SwsUOp *uop, int *data)
{
const int type_v = type_vec4(id, uop->type);
const int type_s = type_scalar(id, uop->type);
int shift, mask;
define_pack_consts(spi, id, uop, &shift, &mask);
*data = spi_OpBitwiseAnd(spi, type_v, *data, mask);
*data = spi_OpShiftLeftLogical(spi, type_v, *data, shift);
int val = spi_OpCompositeExtract(spi, type_s, *data, 0);
for (int i = 1; i < 4; i++) {
int tmp = spi_OpCompositeExtract(spi, type_s, *data, i);
val = spi_OpBitwiseOr(spi, type_s, val, tmp);
}
*data = spi_OpCompositeConstruct(spi, type_v, val, val, val, val);
return 0;
}
static int emit_shader(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
const SwsOpList *ops, const SwsUOpList *uops,
FFVulkanShader *shd, SPICtx *spi)
{
SPIRVIDs spid_data = { 0 }, *id = &spid_data;
id->priv = p;
id->ops = ops;
id->bitexact = !!(sws->flags & SWS_BITEXACT);
id->interlaced = ops->src.interlaced || ops->dst.interlaced;
p->interlaced = id->interlaced;
ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
(uint32_t []) { 32, 32, 1 }, 0);
shd->precompiled = 0;
if (id->interlaced)
ff_vk_shader_add_push_const(shd, 0, sizeof(uint32_t),
VK_SHADER_STAGE_COMPUTE_BIT);
/* Image ops, to determine types */
const SwsOp *op_w = ff_sws_op_list_output(ops);
int out_img_count = rw_op_img_count(op_w, ops->plane_dst);
const SwsOp *op_r = ff_sws_op_list_input(ops);
int in_img_count = op_r ? rw_op_img_count(op_r, ops->plane_src) : 0;
int err = get_view_fmts(p->dst_view_fmt, ops->dst.format, op_w);
if (err < 0)
return err;
err = get_view_fmts(p->src_view_fmt, ops->src.format, op_r);
if (err < 0)
return err;
FFVulkanDescriptorSetBinding img_desc[2] = {
{
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
.elems = 4,
},
{
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
.elems = 4,
},
};
ff_vk_shader_add_descriptor_set(&s->vkctx, shd, img_desc, 2, 0);
/* Define shader header sections */
define_shader_header(spi, id);
define_shader_consts(spi, id);
define_shader_bindings(ops, spi, id, in_img_count, out_img_count);
/* Main function starts here */
id->ep = spi_get_id(spi);
spi_OpFunction(spi, id->ep, id->void_type, 0, id->void_fn_type);
spi_OpLabel(spi, spi_get_id(spi));
/* Load input image handles */
for (int i = 0; i < in_img_count; i++) {
/* Deref array and then the pointer */
int img = spi_OpAccessChain(spi, id->in_img_sptr,
id->in_img_var, id->u32_cid[i]);
id->in_img[i] = spi_OpLoad(spi, id->in_img_type, img,
SpvMemoryAccessMaskNone, 0);
}
/* Load output image handles */
for (int i = 0; i < out_img_count; i++) {
int img = spi_OpAccessChain(spi, id->out_img_sptr,
id->out_img_var, id->u32_cid[i]);
id->out_img[i] = spi_OpLoad(spi, id->out_img_type, img,
SpvMemoryAccessMaskNone, 0);
}
/* Load gl_GlobalInvocationID */
id->gid = spi_OpLoad(spi, id->u32vec3_type, id->gid_var,
SpvMemoryAccessMaskNone, 0);
/* ivec2(gl_GlobalInvocationID.xy) */
id->gid = spi_OpVectorShuffle(spi, id->u32vec2_type, id->gid, id->gid, 0, 1);
id->gi2 = spi_OpBitcast(spi, id->i32vec2_type, id->gid);
/* For interlaced sources/destinations the shader operates on field-local
* coordinates, while images contain the full frame. Map the y axis to the
* actual image row: image_y = field_y * 2 + field. */
int dst_gi2 = id->gi2;
id->dst_gid = id->src_gid = id->gid;
if (id->interlaced) {
int field_u32_ptr = spi_OpAccessChain(spi, id->push_const_elem_ptr_id,
id->push_const_var_id,
id->u32_cid[0]);
int field_u32 = spi_OpLoad(spi, id->u32_type, field_u32_ptr,
SpvMemoryAccessMaskNone, 0);
id->field_i32 = spi_OpBitcast(spi, id->i32_type, field_u32);
int img_y_i32 = spi_OpShiftLeftLogical(spi, id->i32_type,
spi_OpCompositeExtract(spi, id->i32_type, id->gi2, 1),
id->u32_cid[1]);
img_y_i32 = spi_OpIAdd(spi, id->i32_type, img_y_i32, id->field_i32);
int gi2_x = spi_OpCompositeExtract(spi, id->i32_type, id->gi2, 0);
int mapped_gi2 = spi_OpCompositeConstruct(spi, id->i32vec2_type,
gi2_x, img_y_i32);
int mapped_gid = spi_OpBitcast(spi, id->u32vec2_type, mapped_gi2);
if (ops->src.interlaced)
id->src_gid = mapped_gid;
if (ops->dst.interlaced) {
id->dst_gid = mapped_gid;
dst_gi2 = mapped_gi2;
}
}
/* imageSize(id->out_img[0]); */
int img1_s = spi_OpImageQuerySize(spi, id->i32vec2_type, id->out_img[0]);
int scmp = spi_OpSGreaterThanEqual(spi, id->bvec2_type, dst_gi2, img1_s);
scmp = spi_OpAny(spi, id->b_type, scmp);
/* if (out of bounds) return */
int quit_label = spi_get_id(spi), merge_label = spi_get_id(spi);
spi_OpSelectionMerge(spi, merge_label, SpvSelectionControlMaskNone);
spi_OpBranchConditional(spi, scmp, quit_label, merge_label, 0);
spi_OpLabel(spi, quit_label);
spi_OpReturn(spi); /* Quit if out of bounds here */
spi_OpLabel(spi, merge_label);
/* Initialize main data state */
const SwsPixelType type0 = uops->ops[0].type;
const int zero0 = const_zero(id, type0);
int data = spi_OpCompositeConstruct(spi, type_vec4(id, type0),
zero0, zero0, zero0, zero0);
/* Operations */
for (int n = 0; n < uops->num_ops; n++) {
const SwsUOp *uop = &uops->ops[n];
const int type_v = type_vec4(id, uop->type);
switch (uop->uop) {
case SWS_UOP_READ_PLANAR:
case SWS_UOP_READ_PLANAR_FH:
case SWS_UOP_READ_PLANAR_FV:
case SWS_UOP_READ_PACKED:
err = emit_read(spi, id, uop, &data);
break;
case SWS_UOP_WRITE_PLANAR:
case SWS_UOP_WRITE_PACKED:
err = emit_write(spi, id, uop, &data);
break;
case SWS_UOP_CLEAR:
for (int i = 0; i < 4; i++) {
if (!SWS_COMP_TEST(uop->mask, i))
continue;
int val = const_pixel(spi, id, uop->type, uop->data.vec4[i]);
data = spi_OpCompositeInsert(spi, type_v, val, data, i);
}
break;
case SWS_UOP_PERMUTE:
case SWS_UOP_COPY: {
const SwsMoveUOp *move = &uop->par.move;
int idx[5] = { -1, 0, 1, 2, 3 }; /* tmp, x, y, z, w */
for (int i = 0; i < move->num_moves; i++)
idx[move->dst[i] + 1] = idx[move->src[i] + 1];
data = spi_OpVectorShuffle(spi, type_v, data, data,
idx[1], idx[2], idx[3], idx[4]);
break;
}
case SWS_UOP_TO_U8:
case SWS_UOP_TO_U16:
case SWS_UOP_TO_U32:
data = emit_convert(spi, uop->type, SWS_PIXEL_U32,
id->u32vec4_type, data);
break;
case SWS_UOP_TO_F32:
data = emit_convert(spi, uop->type, SWS_PIXEL_F32,
id->f32vec4_type, data);
break;
case SWS_UOP_EXPAND_PAIR:
case SWS_UOP_EXPAND_QUAD: {
/* The SPIR-V compiler should optimize this multiplication into
* a shift and or, if faster */
const uint32_t k = uop->uop == SWS_UOP_EXPAND_PAIR ? 0x101 : 0x1010101;
const int k_v = const_splat(spi, id, SWS_PIXEL_U32, (SwsPixel) { .u32 = k });
data = spi_OpIMul(spi, type_v, data, k_v);
break;
}
case SWS_UOP_LSHIFT:
case SWS_UOP_RSHIFT: {
const SwsPixel amount = { .u32 = uop->par.shift.amount };
const int shift_v = const_splat(spi, id, SWS_PIXEL_U32, amount);
if (uop->uop == SWS_UOP_LSHIFT)
data = spi_OpShiftLeftLogical(spi, type_v, data, shift_v);
else
data = spi_OpShiftRightLogical(spi, type_v, data, shift_v);
break;
}
case SWS_UOP_SCALE:
data = emit_mul(spi, uop->type, type_v, data,
const_splat(spi, id, uop->type, uop->data.scalar));
break;
case SWS_UOP_ADD: {
const int zero = const_zero(id, uop->type);
int arg[4] = { zero, zero, zero, zero };
for (int i = 0; i < 4; i++) {
if (!SWS_COMP_TEST(uop->mask, i))
continue;
arg[i] = const_pixel(spi, id, uop->type, uop->data.vec4[i]);
}
int arg_v = spi_OpConstantComposite(spi, type_v, arg[0], arg[1], arg[2], arg[3]);
data = emit_add(spi, uop->type, type_v, data, arg_v);
break;
}
case SWS_UOP_MIN:
case SWS_UOP_MAX:
err = emit_clamp(spi, id, uop, &data);
break;
case SWS_UOP_DITHER:
err = emit_dither(spi, id, uop, &data);
break;
case SWS_UOP_LINEAR:
err = emit_linear(spi, id, uop, &data);
break;
case SWS_UOP_UNPACK: {
int shift, mask;
define_pack_consts(spi, id, uop, &shift, &mask);
data = spi_OpVectorShuffle(spi, type_v, data, data, 0, 0, 0, 0);
data = spi_OpShiftRightLogical(spi, type_v, data, shift);
data = spi_OpBitwiseAnd(spi, type_v, data, mask);
break;
}
case SWS_UOP_PACK:
err = emit_pack(spi, id, uop, &data);
break;
default:
return AVERROR(ENOTSUP);
}
if (err < 0)
return err;
}
/* Return and finalize */
spi_OpReturn(spi);
spi_OpFunctionEnd(spi);
if (p->nb_data_bufs)
ff_vk_shader_add_descriptor_set(&s->vkctx, shd, id->buf_desc,
p->nb_data_bufs, 1);
define_entry_point(shd, spi, id);
return 0;
}
static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
const SwsOpList *ops, const SwsUOpList *uops,
FFVulkanShader *shd)
{
uint8_t *spvbuf = NULL;
SPICtx spi_context, *spi = &spi_context;
spi_init(spi);
int err = emit_shader(sws, p, s, ops, uops, shd, spi);
if (err < 0)
goto fail;
int len = spi_end(spi, &spvbuf);
if (len < 0) {
err = len;
goto fail;
}
err = ff_vk_shader_link(&s->vkctx, shd, spvbuf, len, "main");
fail:
av_free(spvbuf);
spi_uninit(spi);
return err;
}
static int compile_ops_spirv(SwsContext *sws, const SwsOpList *ops,
SwsCompiledOp *out)
{
SwsUOpList *uops = NULL;
int err;
SwsInternal *c = sws_internal(sws);
FFVulkanOpsCtx *s = c->hw_priv;
if (!s)
return AVERROR(ENOTSUP);
VulkanPriv *p = av_mallocz(sizeof(*p));
if (!p)
return AVERROR(ENOMEM);
p->s = av_refstruct_ref(c->hw_priv);
err = ff_vk_exec_pool_init(&s->vkctx, s->qf, &p->e, 1,
0, 0, 0, NULL);
if (err < 0)
goto fail;
uops = ff_sws_uop_list_alloc();
if (!uops) {
err = AVERROR(ENOMEM);
goto fail;
}
err = ff_sws_ops_translate(sws, ops, SWS_UOP_FLAG_ADD, uops);
if (err < 0)
goto fail;
err = add_ops_spirv(sws, p, s, ops, uops, &p->shd);
if (err < 0)
goto fail;
err = ff_vk_shader_register_exec(&s->vkctx, &p->e, &p->shd);
if (err < 0)
goto fail;
for (int i = 0; i < p->nb_data_bufs; i++)
ff_vk_shader_update_desc_buffer(&s->vkctx, &p->e.contexts[0], &p->shd,
1, i, 0, &p->data_bufs[i],
0, VK_WHOLE_SIZE, VK_FORMAT_UNDEFINED);
*out = (SwsCompiledOp) {
.opaque = true,
.func_opaque = spirv_process,
.priv = p,
.free = spirv_free,
};
ff_sws_uop_list_print(sws, AV_LOG_DEBUG, uops);
ff_sws_uop_list_free(&uops);
return 0;
fail:
ff_sws_uop_list_free(&uops);
spirv_free(p);
return err;
}
const SwsOpBackend backend_spirv = {
.name = "spirv",
.flags = SWS_BACKEND_SPIRV,
.compile = compile_ops_spirv,
.hw_format = AV_PIX_FMT_VULKAN,
};