gfx/scaler: point scaling takes the sample nearest each pixel's centre

The point scaler started from the position the bilinear and sinc
filters weigh from - an output pixel's centre less half an input
sample - and took the sample that position fell in, so every pick was
half a sample short: 64 columns brought down to 40 took columns 0, 1,
3, 5, 6 where the nearest are 0, 2, 4, 5, 7, and an upscale by 3 drew
the first column twice and its neighbours four times. It now starts
half a sample on, which is the nearest sample to the centre - what
FFmpeg's swscale and a GPU's nearest filtering pick - in both the
filter path and the ARGB8888 special path. Powers of two up are as
they were; other integer upscales now repeat each pixel exactly.

parallel-ps3 needs this to move the RSX blit engine's scaled copies
from swscale to the scaler with the same picks.

scaler_point_test checks the picks against floor((x + 1/2) in / out)
for sizes up and down through both paths, and its reference and
image_scale_transfer_test's upscale expectations take the centre; both
fail on the old scaler.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-11 18:00:51 +01:00
1 parent bfcc408883
commit ab6a34d345
4 files changed
+58 -17

No files matched your search

+5 -2
View File
@@ -297,8 +297,11 @@ bool scaler_gen_filter(struct scaler_ctx *ctx)
switch (ctx->scaler_type)
{
case SCALER_TYPE_POINT:
gen_filter_point_sub(&ctx->horiz, ctx->out_width, x_pos, x_step);
gen_filter_point_sub(&ctx->vert, ctx->out_height, y_pos, y_step);
/* the sample nearest each output pixel's centre: x_pos is that
* centre less half a sample, which the filters above weigh
* from, so the nearest is half a sample on */
gen_filter_point_sub(&ctx->horiz, ctx->out_width, x_pos + (1 << 15), x_step);
gen_filter_point_sub(&ctx->vert, ctx->out_height, y_pos + (1 << 15), y_step);
ctx->scaler_special = scaler_argb8888_point_special;
break;
+3 -7
View File
@@ -455,18 +455,14 @@ void scaler_argb8888_point_special(const struct scaler_ctx *ctx,
int out_stride, int in_stride)
{
int h, w;
int x_pos = (1 << 15) * in_width / out_width - (1 << 15);
/* the sample nearest each output pixel's centre */
int x_pos = (1 << 15) * in_width / out_width;
int x_step = (1 << 16) * in_width / out_width;
int y_pos = (1 << 15) * in_height / out_height - (1 << 15);
int y_pos = (1 << 15) * in_height / out_height;
int y_step = (1 << 16) * in_height / out_height;
const uint32_t *input = (const uint32_t*)input_;
uint32_t *output = (uint32_t*)output_;
if (x_pos < 0)
x_pos = 0;
if (y_pos < 0)
y_pos = 0;
for (h = 0; h < out_height; h++, y_pos += y_step, output += out_stride >> 2)
{
int x = x_pos;
@@ -179,14 +179,13 @@ static void upscale(unsigned factor, int pix10)
struct texture_image img;
uint32_t *expected;
unsigned x, y, dw = 7 * factor, dh = 3 * factor;
unsigned step = 65536 / factor;
init(&img, 7, 3, pix10);
expected = (uint32_t*)malloc((size_t)dw * dh * 4);
assert(expected);
/* every pixel repeated factor times each way */
for (y = 0; y < dh; y++)
for (x = 0; x < dw; x++)
expected[(size_t)y * dw + x] = img.pixels[
(size_t)((y * step) >> 16) * 7 + ((x * step) >> 16)];
expected[(size_t)y * dw + x] = img.pixels[(size_t)(y / factor) * 7 + x / factor];
assert(image_texture_scale(&img, 3 * factor, 0));
assert(img.width == dw && img.height == dh);
assert(img.pix10 == (pix10 != 0) && img.supports_rgba);
@@ -85,17 +85,16 @@ static void pixels(int iw, int ih, int ow, int oh, unsigned flip,
if (flip & 2) op = -op;
for (y = 0; y < oh; y++)
{
int64_t yp = ((int64_t)ih << 15) / oh - 32768;
/* the input sample nearest the output pixel's centre */
int64_t yp = ((int64_t)ih << 15) / oh;
int64_t ys = ((int64_t)ih << 16) / oh;
if (yp < 0) yp = 0;
for (x = 0; x < ow; x++)
{
int64_t xp = ((int64_t)iw << 15) / ow - 32768;
int64_t xp = ((int64_t)iw << 15) / ow;
int64_t xs = ((int64_t)iw << 16) / ow;
const void *p;
void *q = ref + (ptrdiff_t)y * op + (size_t)x * bpp_out;
uint32_t pixel;
if (xp < 0) xp = 0;
p = src + (ptrdiff_t)((yp + y * ys) >> 16) * ip
+ (size_t)((xp + x * xs) >> 16) * bpp_in;
if (in_fmt == SCALER_FMT_RGB565)
@@ -241,6 +240,49 @@ static void all_rgb565_values(enum scaler_pix_fmt out_fmt)
free(expected);
}
/* Each output pixel takes the input sample nearest its centre:
* floor((x + 1/2) * in / out). An odd input size never puts a centre on
* a boundary between samples, so the pick is never a tie. Both paths:
* ARGB8888 to itself, and RGB565 to ARGB8888 through the filter. */
static void centres(void)
{
static const int sizes[][2] = {
{63, 40}, {63, 100}, {7, 3}, {3, 8}, {5, 2}, {9, 4}, {1, 5}, {65, 64}
};
unsigned k;
for (k = 0; k < sizeof(sizes) / sizeof(sizes[0]); k++)
{
int iw = sizes[k][0], ow = sizes[k][1], x, y, path;
for (path = 0; path < 2; path++)
{
struct scaler_ctx ctx;
uint32_t in32[65 * 65], out[100 * 100];
uint16_t in16[65 * 65];
for (y = 0; y < iw; y++)
for (x = 0; x < iw; x++)
{
/* the sample's own column and row in its channels */
in32[y * iw + x] = 0xff000000u | ((uint32_t)(x * 2) << 16) | ((uint32_t)(y * 2) << 8);
in16[y * iw + x] = (uint16_t)((x << 11) | (y << 5));
}
init(&ctx, SCALER_TYPE_POINT, path ? SCALER_FMT_RGB565 : SCALER_FMT_ARGB8888,
SCALER_FMT_ARGB8888, iw, iw, ow, ow, iw * (path ? 2 : 4), ow * 4);
assert(scaler_ctx_gen_filter(&ctx));
scaler_ctx_scale(&ctx, out, path ? (const void*)in16 : (const void*)in32);
for (y = 0; y < ow; y++)
for (x = 0; x < ow; x++)
{
int sx = (2 * x + 1) * iw / (2 * ow), sy = (2 * y + 1) * iw / (2 * ow);
uint32_t want = path
? 0xff000000u | pixconv_rgb565_to_xrgb8888((uint16_t)((sx << 11) | (sy << 5)))
: in32[sy * iw + sx];
assert(out[y * ow + x] == want);
}
scaler_ctx_gen_reset(&ctx);
}
}
}
int main(int argc, char **argv)
{
static const enum scaler_pix_fmt fmts[][2] = {
@@ -278,9 +320,10 @@ int main(int argc, char **argv)
for (fmt = 0; fmt < sizeof(fmts) / sizeof(fmts[0]); fmt++)
pixels(sizes[size][0], sizes[size][1], sizes[size][2], sizes[size][3], flip,
fmts[fmt][0], fmts[fmt][1], 0);
centres();
modes_and_failures();
all_rgb565_values(SCALER_FMT_ARGB8888);
all_rgb565_values(SCALER_FMT_BGR24);
puts("PASS: point pixels/strides, native and converted formats, mode changes, allocation failure and bounds");
puts("PASS: point pixels at the nearest centres, pixels/strides, native and converted formats, mode changes, allocation failure and bounds");
return 0;
}