Commit 00e49750e8 for ffmpeg
commit 00e49750e8c95cdd6efc729e4c52ee8726f5466d
Author: Kacper Michajłow <kasper93@gmail.com>
Date: Wed Sep 30 16:26:23 2026 +0200
avfilter/vf_scale_vulkan: convert to the negotiated chroma location
The output link accepts any location and the frames are converted to the
negotiated one. Format conversions tagged the output as top-left while the
chroma of each 2x2 block came from whichever of its four invocations
stored it last, and scaling without a format change scaled each plane on
its own grid, which only keeps center sited chroma in place. The chroma of
each block is now sampled once at its location, and the chroma planes are
resampled from the input location to the output one. Without a negotiated
location, subsampled input keeps its location and other input still gets
top-left, and input without a location is assumed to be sited like the
output.
Signed-off-by: Kacper Michajłow <kasper93@gmail.com>
diff --git a/libavfilter/vf_scale_vulkan.c b/libavfilter/vf_scale_vulkan.c
index c7a2aa7495..c60f26acf5 100644
--- a/libavfilter/vf_scale_vulkan.c
+++ b/libavfilter/vf_scale_vulkan.c
@@ -22,6 +22,7 @@
#include "vulkan_filter.h"
#include "scale_eval.h"
#include "filters.h"
+#include "formats.h"
#include "colorspace.h"
#include "video.h"
#include "libswscale/swscale.h"
@@ -72,6 +73,8 @@ typedef struct ScaleVulkanContext {
int crop_w;
int crop_h;
float in_dims[2];
+ float in_chroma_loc[2];
+ float out_chroma_loc[2];
} opts;
char *out_format_string;
@@ -91,6 +94,8 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
ScaleVulkanContext *s = ctx->priv;
FFVulkanContext *vkctx = &s->vkctx;
FFVulkanShader *shd = &s->shd;
+ const AVPixFmtDescriptor *in_desc = av_pix_fmt_desc_get(s->vkctx.input_format);
+ int chroma_planes = 0;
int in_planes = av_pix_fmt_count_planes(s->vkctx.input_format);
int out_planes = av_pix_fmt_count_planes(s->vkctx.output_format);
@@ -107,6 +112,8 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
if (s->vkctx.output_format == s->vkctx.input_format) {
mode = MODE_COPY;
+ if (in_desc->log2_chroma_w || in_desc->log2_chroma_h)
+ chroma_planes = (1 << in_desc->comp[1].plane) | (1 << in_desc->comp[2].plane);
} else {
switch (s->vkctx.output_format) {
case AV_PIX_FMT_NV12: mode = MODE_NV12; break;
@@ -120,10 +127,13 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
RET(ff_vk_init_sampler(vkctx, &s->sampler, 0, sampler_mode));
- SPEC_LIST_CREATE(sl, 3, 3*sizeof(int32_t))
+ SPEC_LIST_CREATE(sl, 6, 6*sizeof(int32_t))
SPEC_LIST_ADD(sl, 0, 32, out_planes);
SPEC_LIST_ADD(sl, 1, 32, mode);
SPEC_LIST_ADD(sl, 2, 32, s->out_range == AVCOL_RANGE_JPEG);
+ SPEC_LIST_ADD(sl, 3, 32, chroma_planes);
+ SPEC_LIST_ADD(sl, 4, 32, in_desc->log2_chroma_w);
+ SPEC_LIST_ADD(sl, 5, 32, in_desc->log2_chroma_h);
ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
(uint32_t []) { 32, 32, 1 }, 0);
@@ -230,12 +240,26 @@ fail:
return err;
}
+static void chroma_loc_offset(float offset[2], enum AVChromaLocation loc,
+ const AVPixFmtDescriptor *desc)
+{
+ int x, y;
+
+ if (loc == AVCHROMA_LOC_UNSPECIFIED)
+ loc = AVCHROMA_LOC_CENTER;
+ av_chroma_location_enum_to_pos(&x, &y, loc);
+ offset[0] = x * ((1 << desc->log2_chroma_w) - 1) / 256.0f;
+ offset[1] = y * ((1 << desc->log2_chroma_h) - 1) / 256.0f;
+}
+
static int scale_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
{
int err;
AVFilterContext *ctx = link->dst;
ScaleVulkanContext *s = ctx->priv;
AVFilterLink *outlink = ctx->outputs[0];
+ const AVPixFmtDescriptor *in_desc = av_pix_fmt_desc_get(s->vkctx.input_format);
+ const AVPixFmtDescriptor *out_desc = av_pix_fmt_desc_get(s->vkctx.output_format);
AVFrame *out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
if (!out) {
@@ -259,7 +283,10 @@ static int scale_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
if (s->out_range != AVCOL_RANGE_UNSPECIFIED)
out->color_range = s->out_range;
- if (s->vkctx.output_format != s->vkctx.input_format)
+ if (outlink->chroma_location != AVCHROMA_LOC_UNSPECIFIED)
+ out->chroma_location = outlink->chroma_location;
+ else if (s->vkctx.output_format != s->vkctx.input_format &&
+ !in_desc->log2_chroma_w && !in_desc->log2_chroma_h)
out->chroma_location = AVCHROMA_LOC_TOPLEFT;
if (!s->sws) {
@@ -277,6 +304,12 @@ static int scale_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
RET(init_filter(ctx, in));
}
+ /* Input without a chroma location is assumed to be sited like the output. */
+ chroma_loc_offset(s->opts.in_chroma_loc,
+ in->chroma_location != AVCHROMA_LOC_UNSPECIFIED ?
+ in->chroma_location : out->chroma_location, in_desc);
+ chroma_loc_offset(s->opts.out_chroma_loc, out->chroma_location, out_desc);
+
RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, out, in,
s->sampler, 1, &s->opts, sizeof(s->opts)));
} else {
@@ -365,6 +398,24 @@ static int scale_vulkan_config_output(AVFilterLink *outlink)
return ff_vk_filter_config_output(outlink);
}
+static int scale_vulkan_query_formats(const AVFilterContext *avctx,
+ AVFilterFormatsConfig **cfg_in,
+ AVFilterFormatsConfig **cfg_out)
+{
+ int err;
+
+ err = ff_set_common_formats2(avctx, cfg_in, cfg_out,
+ ff_make_formats_list_singleton(AV_PIX_FMT_VULKAN));
+ if (err < 0)
+ return err;
+
+ err = ff_formats_ref(ff_all_chroma_locations(), &cfg_in[0]->chroma_locations);
+ if (err < 0)
+ return err;
+
+ return ff_formats_ref(ff_all_chroma_locations(), &cfg_out[0]->chroma_locations);
+}
+
static void scale_vulkan_uninit(AVFilterContext *avctx)
{
ScaleVulkanContext *s = avctx->priv;
@@ -435,6 +486,6 @@ const FFFilter ff_vf_scale_vulkan = {
.uninit = &scale_vulkan_uninit,
FILTER_INPUTS(scale_vulkan_inputs),
FILTER_OUTPUTS(scale_vulkan_outputs),
- FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
+ FILTER_QUERY_FUNC2(&scale_vulkan_query_formats),
.flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
};
diff --git a/libavfilter/vulkan/scale.comp.glsl b/libavfilter/vulkan/scale.comp.glsl
index b439344106..62b11448ec 100644
--- a/libavfilter/vulkan/scale.comp.glsl
+++ b/libavfilter/vulkan/scale.comp.glsl
@@ -35,6 +35,9 @@ layout (local_size_x_id = 253, local_size_y_id = 254, local_size_z_id = 255) in;
layout (constant_id = 0) const int nb_planes = 0;
layout (constant_id = 1) const int mode = MODE_COPY;
layout (constant_id = 2) const int fullrange = 0;
+layout (constant_id = 3) const int chroma_planes = 0;
+layout (constant_id = 4) const int log2_chroma_w = 0;
+layout (constant_id = 5) const int log2_chroma_h = 0;
layout (set = 0, binding = 0) uniform sampler2D input_img[];
layout (set = 0, binding = 1) uniform writeonly image2D output_img[];
@@ -46,16 +49,27 @@ layout (push_constant, std430) uniform pushConstants {
int crop_w;
int crop_h;
vec2 in_dims;
+ vec2 in_chroma_loc;
+ vec2 out_chroma_loc;
};
-vec4 scale_bilinear(int idx, ivec2 pos, vec2 crop_range, vec2 crop_off)
+vec4 scale_bilinear(int idx, vec2 pos, vec2 crop_range, vec2 crop_off)
{
- vec2 npos = (vec2(pos) + 0.5f) / imageSize(output_img[idx]);
+ vec2 npos = pos / imageSize(output_img[idx]);
npos *= crop_range; /* Reduce the range */
npos += crop_off; /* Offset the start */
return texture(input_img[idx], npos);
}
+vec4 scale_chroma(int idx, ivec2 pos, vec2 crop_range, vec2 crop_off)
+{
+ vec2 sub = vec2(1 << log2_chroma_w, 1 << log2_chroma_h);
+ vec2 npos = (vec2(pos) * sub + 0.5f + out_chroma_loc) / imageSize(output_img[0]);
+ npos = npos * crop_range + crop_off;
+ npos = (npos * in_dims - 0.5f - in_chroma_loc) / sub + 0.5f;
+ return texture(input_img[idx], npos / textureSize(input_img[idx], 0));
+}
+
vec4 rgb2yuv(vec4 src)
{
src *= yuv_matrix;
@@ -70,15 +84,11 @@ vec4 rgb2yuv(vec4 src)
void write_nv12(vec4 src, ivec2 pos)
{
- imageStore(output_img[0], pos, vec4(src.r, 0.0, 0.0, 0.0));
- pos /= ivec2(2);
imageStore(output_img[1], pos, vec4(src.g, src.b, 0.0, 0.0));
}
void write_420(vec4 src, ivec2 pos)
{
- imageStore(output_img[0], pos, vec4(src.r, 0.0, 0.0, 0.0));
- pos /= ivec2(2);
imageStore(output_img[1], pos, vec4(src.g, 0.0, 0.0, 0.0));
imageStore(output_img[2], pos, vec4(src.b, 0.0, 0.0, 0.0));
}
@@ -102,7 +112,11 @@ void main()
if (any(greaterThanEqual(pos, size)))
continue;
- vec4 res = scale_bilinear(i, pos, c_r, c_o);
+ vec4 res;
+ if ((chroma_planes & (1 << i)) != 0)
+ res = scale_chroma(i, pos, c_r, c_o);
+ else
+ res = scale_bilinear(i, vec2(pos) + 0.5f, c_r, c_o);
imageStore(output_img[i], pos, res);
}
} else {
@@ -110,11 +124,20 @@ void main()
if (any(greaterThanEqual(pos, size)))
return;
- vec4 res = rgb2yuv(scale_bilinear(0, pos, c_r, c_o));
+ vec4 res = rgb2yuv(scale_bilinear(0, vec2(pos) + 0.5f, c_r, c_o));
+ if (mode == MODE_YUV444) {
+ write_444(res, pos);
+ return;
+ }
+
+ imageStore(output_img[0], pos, vec4(res.r, 0.0, 0.0, 0.0));
+ if (((pos.x | pos.y) & 1) != 0)
+ return;
+
+ res = rgb2yuv(scale_bilinear(0, vec2(pos) + 0.5f + out_chroma_loc, c_r, c_o));
switch (mode) {
- case MODE_NV12: write_nv12(res, pos); break;
- case MODE_YUV420: write_420(res, pos); break;
- case MODE_YUV444: write_444(res, pos); break;
+ case MODE_NV12: write_nv12(res, pos >> 1); break;
+ case MODE_YUV420: write_420(res, pos >> 1); break;
}
}
}