aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.glsl
blob: e34dd77dd548d5a806cb2152b6f66e6dea36c14e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// Scaling

#version 430 core
layout (local_size_x = 16, local_size_y = 16) in;
layout( rgba8, binding = 0, set = 3) uniform image2D imgOutput;
layout( binding = 1, set = 2) uniform sampler2D Source;
layout( binding = 2 ) uniform dimensions{
 float srcX0;
 float srcX1;
 float srcY0;
 float srcY1;
 float dstX0;
 float dstX1;
 float dstY0;
 float dstY1;
};

/***** Area Sampling *****/

// 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/
// https://github.com/obsproject/obs-studio/pull/1715
// https://legacy.imagemagick.org/Usage/filter/
vec4 AreaSampling(vec2 xy)
{
    // Determine the sizes of the source and target images.
    vec2 source_size = vec2(abs(srcX1 - srcX0), abs(srcY1 - srcY0));
    vec2 target_size = vec2(abs(dstX1 - dstX0), abs(dstY1 - dstY0));
    vec2 inverted_target_size = vec2(1.0) / target_size;

    // Compute the top-left and bottom-right corners of the target pixel box.
    vec2 t_beg = floor(xy - vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1));
    vec2 t_end = t_beg + vec2(1.0, 1.0);

    // Convert the target pixel box to source pixel box.
    vec2 beg = t_beg * inverted_target_size * source_size;
    vec2 end = t_end * inverted_target_size * source_size;

    // Compute the top-left and bottom-right corners of the pixel box.
    ivec2 f_beg = ivec2(beg);
    ivec2 f_end = ivec2(end);

    // Compute how much of the start and end pixels are covered horizontally & vertically.
    float area_w = 1.0 - fract(beg.x);
    float area_n = 1.0 - fract(beg.y);
    float area_e = fract(end.x);
    float area_s = fract(end.y);

    // Compute the areas of the corner pixels in the pixel box.
    float area_nw = area_n * area_w;
    float area_ne = area_n * area_e;
    float area_sw = area_s * area_w;
    float area_se = area_s * area_e;

    // Initialize the color accumulator.
    vec4 avg_color = vec4(0.0, 0.0, 0.0, 0.0);

    // Accumulate corner pixels.
    avg_color += area_nw * texelFetch(Source, ivec2(f_beg.x, f_beg.y), 0);
    avg_color += area_ne * texelFetch(Source, ivec2(f_end.x, f_beg.y), 0);
    avg_color += area_sw * texelFetch(Source, ivec2(f_beg.x, f_end.y), 0);
    avg_color += area_se * texelFetch(Source, ivec2(f_end.x, f_end.y), 0);

    // Determine the size of the pixel box.
    int x_range = int(f_end.x - f_beg.x - 0.5);
    int y_range = int(f_end.y - f_beg.y - 0.5);

    // Accumulate top and bottom edge pixels.
    for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
    {
        avg_color += area_n * texelFetch(Source, ivec2(x, f_beg.y), 0);
        avg_color += area_s * texelFetch(Source, ivec2(x, f_end.y), 0);
    }

    // Accumulate left and right edge pixels and all the pixels in between.
    for (int y = f_beg.y + 1; y <= f_beg.y + y_range; ++y)
    {
        avg_color += area_w * texelFetch(Source, ivec2(f_beg.x, y), 0);
        avg_color += area_e * texelFetch(Source, ivec2(f_end.x, y), 0);

        for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
        {
            avg_color += texelFetch(Source, ivec2(x, y), 0);
        }
    }

    // Compute the area of the pixel box that was sampled.
    float area_corners = area_nw + area_ne + area_sw + area_se;
    float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e);
    float area_center = float(x_range) * float(y_range);

    // Return the normalized average color.
    return avg_color / (area_corners + area_edges + area_center);
}

float insideBox(vec2 v, vec2 bLeft, vec2 tRight) {
    vec2 s = step(bLeft, v) - step(tRight, v);
    return s.x * s.y;
}

vec2 translateDest(vec2 pos) {
    vec2 translatedPos = vec2(pos.x, pos.y);
    translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
    translatedPos.y = dstY0 < dstY1 ? dstY1 + dstY0 - translatedPos.y - 1 : translatedPos.y;
    return translatedPos;
}

void main()
{
    vec2 bLeft = vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1);
    vec2 tRight = vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0);
    ivec2 loc = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
    if (insideBox(loc, bLeft, tRight) == 0) {
        imageStore(imgOutput, loc, vec4(0, 0, 0, 1));
        return;
    }

    vec4 outColor = AreaSampling(loc);
    imageStore(imgOutput, ivec2(translateDest(loc)), vec4(outColor.rgb, 1));
}