hblur.wgsl207 lines · 6.6 KB · raw
1@group(0) @binding(0)
2var texture_in: texture_2d<f32>;
3@group(0) @binding(1)
4var<uniform> texture_width: u32;
5
6// If horizontal resolution is 1280 or higher, use a wider range of pixels when applying horizontal blur.
7//
8// This is a hack to make blur work correctly for 32X when the Genesis VDP is in H32 mode, which changes the internal
9// rendering resolution to 1280x224 in order to correctly handle priority between Genesis H32 pixels and 32X H40 pixels
10const S32X_H32_HACK_WIDTH = 1280u;
11
12fn to_texture_position(fragment_position: vec4f) -> vec2u {
13    let texture_position = round(fragment_position.xy - vec2f(0.5));
14    return vec2u(u32(texture_position.x), u32(texture_position.y));
15}
16
17@fragment
18fn hblur_2px(@builtin(position) position: vec4f) -> @location(0) vec4f {
19    let t_position = to_texture_position(position);
20
21    if texture_width >= S32X_H32_HACK_WIDTH {
22        // Blur 9 pixels at H1280px
23        let color = compute_hblur_variable(t_position, 4);
24        return vec4f(color, 1.0);
25    }
26
27    let left = textureLoad(texture_in, t_position, 0).rgb;
28    let right = select(
29        left,
30        textureLoad(texture_in, t_position + vec2u(1u, 0u), 0).rgb,
31        t_position.x != texture_width - 1u,
32    );
33
34    let color = (left + right) / 2.0;
35    return vec4f(color, 1.0);
36}
37
38@fragment
39fn hblur_3px(@builtin(position) position: vec4f) -> @location(0) vec4f {
40    let t_position = to_texture_position(position);
41
42    if texture_width >= S32X_H32_HACK_WIDTH {
43        // Blur 13 pixels at H1280px
44        let color = compute_hblur_variable(t_position, 6);
45        return vec4f(color, 1.0);
46    }
47
48    let color = compute_hblur_3px(t_position);
49    return vec4f(color, 1.0);
50}
51
52fn compute_hblur_3px(t_position: vec2u) -> vec3f {
53    var color_sum = 2.0 * textureLoad(texture_in, t_position, 0).rgb;
54    var weight_sum = 2.0;
55
56    if t_position.x != 0u {
57        color_sum += textureLoad(texture_in, t_position - vec2u(1u, 0u), 0).rgb;
58        weight_sum += 1.0;
59    }
60
61    if t_position.x != texture_width - 1u {
62        color_sum += textureLoad(texture_in, t_position + vec2u(1u, 0u), 0).rgb;
63        weight_sum += 1.0;
64    }
65
66    return color_sum / weight_sum;
67}
68
69fn compute_hblur_variable(t_position: vec2u, distance: i32) -> vec3f {
70    var color_sum = vec3f(0.0, 0.0, 0.0);
71    var weight_sum = 0.0;
72
73    for (var dx = -distance; dx <= distance; dx++) {
74        let x = i32(t_position.x) + dx;
75        if x < 0 || x >= i32(texture_width) {
76            continue;
77        }
78
79        let color = textureLoad(texture_in, vec2u(u32(x), t_position.y), 0).rgb;
80        let weight = f32(distance + 1 - abs(dx));
81        color_sum += weight * color;
82        weight_sum += weight;
83    }
84
85    return color_sum / weight_sum;
86}
87
88@fragment
89fn hblur_snes(@builtin(position) position: vec4f) -> @location(0) vec4f {
90    let t_position = to_texture_position(position);
91
92    if texture_width >= S32X_H32_HACK_WIDTH {
93        // Blur 5 pixels at H1280px
94        let color = compute_hblur_variable(t_position, 2);
95        return vec4f(color, 1.0);
96    }
97
98    if texture_width >= 512u {
99        let color = compute_hblur_3px(t_position);
100        return vec4f(color, 1.0);
101    }
102
103    let color = hblur_snes_256px(t_position);
104    return vec4f(color, 1.0);
105}
106
107fn hblur_snes_256px(out_t_position: vec2u) -> vec3f {
108    let in_t_position = out_t_position / vec2u(2u, 1u);
109
110    let center = textureLoad(texture_in, in_t_position, 0).rgb;
111
112    let left_x = select(
113        in_t_position.x,
114        in_t_position.x - 1u,
115        out_t_position.x % 2u == 0u && in_t_position.x != 0u,
116    );
117
118    let right_x = select(
119        in_t_position.x,
120        in_t_position.x + 1u,
121        out_t_position.x % 2u == 1u && in_t_position.x != texture_width - 1u,
122    );
123
124    let left = textureLoad(texture_in, vec2u(left_x, in_t_position.y), 0).rgb;
125    let right = textureLoad(texture_in, vec2u(right_x, in_t_position.y), 0).rgb;
126
127    return (2.0 * center + left + right) / 4.0;
128}
129
130// Returns a value in the range [0.0, 3.0]
131fn diff(a: vec3f, b: vec3f) -> f32 {
132    return dot(abs(a - b), vec3f(1.0));
133}
134
135fn shift_left(position: vec2u, shift: u32) -> vec2u {
136    return select(
137        position + vec2u(shift, 0u),
138        position - vec2u(shift, 0u),
139        position.x > shift - 1u,
140    );
141}
142
143fn shift_right(position: vec2u, shift: u32) -> vec2u {
144    return select(
145        position - vec2u(shift, 0u),
146        position + vec2u(shift, 0u),
147        position.x < texture_width - shift,
148    );
149}
150
151fn should_apply_strong_anti_dither(left2: vec3f, left: vec3f, center: vec3f, right: vec3f, right2: vec3f) -> bool {
152    return diff(left, right) < 0.001 && diff(left, center) < 2.5 && diff(left2, left) >= 0.001 && diff(right2, right) >= 0.001;
153}
154
155@fragment
156fn anti_dither_weak(@builtin(position) position: vec4f) -> @location(0) vec4f {
157    let t_position = to_texture_position(position);
158
159    let center = textureLoad(texture_in, t_position, 0).rgb;
160
161    let left = textureLoad(texture_in, shift_left(t_position, 1u), 0).rgb;
162    let right = textureLoad(texture_in, shift_right(t_position, 1u), 0).rgb;
163
164    let left2 = textureLoad(texture_in, shift_left(t_position, 2u), 0).rgb;
165    let right2 = textureLoad(texture_in, shift_right(t_position, 2u), 0).rgb;
166
167    let color = select(
168        center,
169        (2.0 * center + left + right) / 4.0,
170        diff(left, right) < 0.001
171            && diff(center, left) < 2.0
172            && diff(left2, left) >= 0.001
173            && diff(right2, right) >= 0.001,
174    );
175    return vec4f(color, 1.0);
176}
177
178@fragment
179fn anti_dither_strong(@builtin(position) position: vec4f) -> @location(0) vec4f {
180    let t_position = to_texture_position(position);
181
182    let center = textureLoad(texture_in, t_position, 0).rgb;
183
184    let left = textureLoad(texture_in, shift_left(t_position, 1u), 0).rgb;
185    let right = textureLoad(texture_in, shift_right(t_position, 1u), 0).rgb;
186
187    let left2 = textureLoad(texture_in, shift_left(t_position, 2u), 0).rgb;
188    let right2 = textureLoad(texture_in, shift_right(t_position, 2u), 0).rgb;
189
190    let left3 = textureLoad(texture_in, shift_left(t_position, 3u), 0).rgb;
191    let right3 = textureLoad(texture_in, shift_right(t_position, 3u), 0).rgb;
192
193    let color = select(
194        select(
195            select(
196                center,
197                (2.0 * left + center + left2) / 4.0,
198                should_apply_strong_anti_dither(left3, left2, left, center, right),
199            ),
200            (2.0 * right + center + right2) / 4.0,
201            should_apply_strong_anti_dither(left, center, right, right2, right3),
202        ),
203        (2.0 * center + left + right) / 4.0,
204        should_apply_strong_anti_dither(left2, left, center, right, right2),
205    );
206    return vec4f(color, 1.0);
207}