This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit 00e49750e8c95cdd6efc729e4c52ee8726f5466d Author: Kacper Michajłow <[email protected]> AuthorDate: Wed Sep 30 16:26:23 2026 +0200 Commit: Kacper Michajłow <[email protected]> CommitDate: Thu Oct 1 14:31:15 2026 +0000 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 <[email protected]> --- libavfilter/vf_scale_vulkan.c | 57 ++++++++++++++++++++++++++++++++++++-- libavfilter/vulkan/scale.comp.glsl | 45 ++++++++++++++++++++++-------- 2 files changed, 88 insertions(+), 14 deletions(-) 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; } } } -- To stop receiving notification emails like this one, please contact [email protected]. _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
