Video: Fix area/box resampling shifting the output by about one pixel when upscaling and downscaling

This commit is contained in:
Filoppi 2023-08-04 14:40:55 +03:00
parent 39d96a21a8
commit fc3f7866f8

View File

@ -43,6 +43,17 @@ float3 LinearTosRGBGamma(float3 color)
// Non filtered gamma corrected sample (nearest neighbor)
float4 QuickSample(float3 uvw, float gamma)
{
#if 0 // Test sampling range
const float threshold = 0.00000001;
float2 xy = uvw.xy * GetResolution();
// Sampling outside the valid range, draw in yellow
if (xy.x < (0.0 - threshold) || xy.x > (GetResolution().x + threshold) || xy.y < (0.0 - threshold) || xy.y > (GetResolution().y + threshold))
return float4(1.0, 1.0, 0.0, 1);
// Sampling at the edges, draw in purple
if (xy.x < 1.0 || xy.x > (GetResolution().x - 1.0) || xy.y < 1.0 || xy.y > (GetResolution().y - 1.0))
return float4(0.5, 0, 0.5, 1);
#endif
float4 color = texture(samp1, uvw);
color.rgb = pow(color.rgb, float3(gamma));
return color;
@ -178,7 +189,7 @@ float4 SharpBilinearSample(float3 uvw, float gamma)
/***** Area Sampling *****/
// By Sam Belliveau. Public Domain license.
// By Sam Belliveau and Filippo Tarpini. Public Domain license.
// Effectively a more accurate sharp bilinear filter when upscaling,
// that also works as a mathematically perfect downscale filter.
// https://entropymine.com/imageworsener/pixelmixing/
@ -191,9 +202,11 @@ float4 AreaSampling(float3 uvw, float gamma)
float2 inverted_target_size = GetInvWindowResolution();
// Determine the range of the source image that the target pixel will cover.
// We shift by one output pixel because that's a prerequisite of the algorithm.
float2 range = source_size * inverted_target_size;
float2 beg = (uvw.xy - inverted_target_size) * source_size;
// Workaround: shift the resolution by 1/4 pixel to align the results with other sampling algorithms,
// otherwise the results would be offsetted, and we'd be sampling from coordinates outside the valid range.
float2 adjusted_source_size = source_size - 0.25;
float2 range = adjusted_source_size * inverted_target_size;
float2 beg = (uvw.xy * adjusted_source_size) - (range * 0.5);
float2 end = beg + range;
// Compute the top-left and bottom-right corners of the pixel box.
@ -215,14 +228,14 @@ float4 AreaSampling(float3 uvw, float gamma)
// Initialize the color accumulator.
float4 avg_color = float4(0.0, 0.0, 0.0, 0.0);
// Presents rounding errors
const float offset = 0.5;
// Prevents rounding errors due to the coordinates flooring above
const float2 offset = float2(0.5, 0.5);
// Accumulate corner pixels.
avg_color += area_nw * QuickSampleByPixel(float2(f_beg.x + offset, f_beg.y + offset), uvw.z, gamma);
avg_color += area_ne * QuickSampleByPixel(float2(f_end.x + offset, f_beg.y + offset), uvw.z, gamma);
avg_color += area_sw * QuickSampleByPixel(float2(f_beg.x + offset, f_end.y + offset), uvw.z, gamma);
avg_color += area_se * QuickSampleByPixel(float2(f_end.x + offset, f_end.y + offset), uvw.z, gamma);
avg_color += area_nw * QuickSampleByPixel(float2(f_beg.x, f_beg.y) + offset, uvw.z, gamma);
avg_color += area_ne * QuickSampleByPixel(float2(f_end.x, f_beg.y) + offset, uvw.z, gamma);
avg_color += area_sw * QuickSampleByPixel(float2(f_beg.x, f_end.y) + offset, uvw.z, gamma);
avg_color += area_se * QuickSampleByPixel(float2(f_end.x, f_end.y) + offset, uvw.z, gamma);
// Determine the size of the pixel box.
int x_range = int(f_end.x - f_beg.x + 0.5);
@ -231,6 +244,7 @@ float4 AreaSampling(float3 uvw, float gamma)
// Workaround to compile the shader with DX11/12.
// If this isn't done, it will complain that the loop could have too many iterations.
// This number should be enough to guarantee downscaling from very high to very small resolutions.
// Note that this number might be referenced in the UI.
const int max_iterations = 16;
// Fix up the average calculations in case we reached the upper limit
@ -243,8 +257,8 @@ float4 AreaSampling(float3 uvw, float gamma)
if (ix < x_range)
{
float x = f_beg.x + 1.0 + float(ix);
avg_color += area_n * QuickSampleByPixel(float2(x + offset, f_beg.y + offset), uvw.z, gamma);
avg_color += area_s * QuickSampleByPixel(float2(x + offset, f_end.y + offset), uvw.z, gamma);
avg_color += area_n * QuickSampleByPixel(float2(x, f_beg.y) + offset, uvw.z, gamma);
avg_color += area_s * QuickSampleByPixel(float2(x, f_end.y) + offset, uvw.z, gamma);
}
}
@ -255,15 +269,15 @@ float4 AreaSampling(float3 uvw, float gamma)
{
float y = f_beg.y + 1.0 + float(iy);
avg_color += area_w * QuickSampleByPixel(float2(f_beg.x + offset, y + offset), uvw.z, gamma);
avg_color += area_e * QuickSampleByPixel(float2(f_end.x + offset, y + offset), uvw.z, gamma);
avg_color += area_w * QuickSampleByPixel(float2(f_beg.x, y) + offset, uvw.z, gamma);
avg_color += area_e * QuickSampleByPixel(float2(f_end.x, y) + offset, uvw.z, gamma);
for (int ix = 0; ix < max_iterations; ++ix)
{
if (ix < x_range)
{
float x = f_beg.x + 1.0 + float(ix);
avg_color += QuickSampleByPixel(float2(x + offset, y + offset), uvw.z, gamma);
avg_color += QuickSampleByPixel(float2(x, y) + offset, uvw.z, gamma);
}
}
}