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}