1/* 2 Copyright 2020 Morgan McGuire & Mara Gagiu. 3 Provided under the Open Source MIT license https://opensource.org/licenses/MIT 4*/ 5 6// WGSL port of MMPX GLSL 7// Copyright (C) 2026 James Groth 8 9// MMPX pixel art magnification algorithm: https://casual-effects.com/research/McGuire2021PixelArt/index.html 10 11@group(0) @binding(0) var input_frame: texture_2d<f32>; 12@group(0) @binding(1) var output_frame: texture_storage_2d<rgba8unorm, write>; 13 14var<private> input_size: vec2i; 15 16fn load_input(position: vec2i) -> vec3f { 17 return textureLoad( 18 input_frame, 19 clamp(position, vec2i(0), input_size - 1), 20 0, 21 ).rgb; 22} 23 24// Coefficients used for Y in RGB-to-YUV conversion 25const RGB_TO_Y: vec3f = vec3f(0.299, 0.587, 0.114); 26 27fn luma(c: vec3f) -> f32 { 28 return dot(c, RGB_TO_Y); 29} 30 31fn eq(a: vec3f, b: vec3f) -> bool { 32 return all(a == b); 33} 34 35fn ne(a: vec3f, b: vec3f) -> bool { 36 return any(a != b); 37} 38 39fn all_eq2(B: vec3f, A0: vec3f, A1: vec3f) -> bool { 40 return all((B == A0) & (B == A1)); 41} 42 43fn all_eq3(B: vec3f, A0: vec3f, A1: vec3f, A2: vec3f) -> bool { 44 return all((B == A0) & (B == A1) & (B == A2)); 45} 46 47fn all_eq4(B: vec3f, A0: vec3f, A1: vec3f, A2: vec3f, A3: vec3f) -> bool { 48 return all((B == A0) & (B == A1) & (B == A2) & (B == A3)); 49} 50 51fn any_eq3(B: vec3f, A0: vec3f, A1: vec3f, A2: vec3f) -> bool { 52 return eq(B, A0) || eq(B, A1) || eq(B, A2); 53} 54 55fn none_eq2(B: vec3f, A0: vec3f, A1: vec3f) -> bool { 56 return ne(B, A0) && ne(B, A1); 57} 58 59fn none_eq4(B: vec3f, A0: vec3f, A1: vec3f, A2: vec3f, A3: vec3f) -> bool { 60 return ne(B, A0) && ne(B, A1) && ne(B, A2) && ne(B, A3); 61} 62 63@compute @workgroup_size(16, 16, 1) 64fn mmpx(@builtin(global_invocation_id) invocation: vec3u) { 65 let in_pos = vec2i(invocation.xy); 66 input_size = vec2i(textureDimensions(input_frame)); 67 if any(in_pos >= input_size) { 68 return; 69 } 70 71 // Input pixels (5x5, E is pixel at current position): 72 // - - P - - 73 // - A B C - 74 // Q D E F R 75 // - G H I - 76 // - - S - - 77 // Certain branches load additional pixels beyond these 78 79 let A = load_input(in_pos + vec2i(-1, -1)); 80 let B = load_input(in_pos + vec2i( 0, -1)); 81 let C = load_input(in_pos + vec2i( 1, -1)); 82 let D = load_input(in_pos + vec2i(-1, 0)); 83 let E = load_input(in_pos + vec2i( 0, 0)); 84 let F = load_input(in_pos + vec2i( 1, 0)); 85 let G = load_input(in_pos + vec2i(-1, 1)); 86 let H = load_input(in_pos + vec2i( 0, 1)); 87 let I = load_input(in_pos + vec2i( 1, 1)); 88 89 // Output pixels (2x2): 90 // J K 91 // L M 92 93 var J = E; 94 var K = E; 95 var L = E; 96 var M = E; 97 98 if any((A != E) | (B != E) | (C != E) | (D != E) | (F != E) | (G != E) | (H != E) | (I != E)) { 99 let P = load_input(in_pos + vec2i( 0, -2)); 100 let S = load_input(in_pos + vec2i( 0, 2)); 101 let Q = load_input(in_pos + vec2i(-2, 0)); 102 let R = load_input(in_pos + vec2i( 2, 0)); 103 104 let Bl = luma(B); 105 let Dl = luma(D); 106 let El = luma(E); 107 let Fl = luma(F); 108 let Hl = luma(H); 109 110 // 1:1 slope rules 111 if (eq(D, B) && ne(D, H) && ne(D, F)) 112 && (El >= Dl || eq(E, A)) 113 && any_eq3(E, A, C, G) 114 && ((El < Dl) || ne(A, D) || ne(E, P) || ne(E, Q)) 115 { 116 J = D; 117 } 118 if (eq(B, F) && ne(B, D) && ne(B, H)) 119 && (El >= Bl || eq(E, C)) 120 && any_eq3(E, A, C, I) 121 && ((El < Bl) || ne(C, B) || ne(E, P) || ne(E, R)) 122 { 123 K = B; 124 } 125 if (eq(H, D) && ne(H, F) && ne(H, B)) 126 && (El >= Hl || eq(E, G)) 127 && any_eq3(E, A, G, I) 128 && ((El < Hl) || ne(G, H) || ne(E, S) || ne(E, Q)) 129 { 130 L = H; 131 } 132 if (eq(F, H) && ne(F, B) && ne(F, D)) 133 && (El >= Fl || eq(E, I)) 134 && any_eq3(E, C, G, I) 135 && ((El < Fl) || ne(I, H) || ne(E, R) || ne(E, S)) 136 { 137 M = F; 138 } 139 140 // Intersection rules 141 if ne(E, F) && all_eq4(E, C, I, D, Q) && all_eq2(F, B, H) && ne(F, load_input(in_pos + vec2i(3, 0))) { 142 M = F; 143 K = F; 144 } 145 if ne(E, D) && all_eq4(E, A, G, F, R) && all_eq2(D, B, H) && ne(D, load_input(in_pos + vec2i(-3, 0))) { 146 L = D; 147 J = D; 148 } 149 if ne(E, H) && all_eq4(E, G, I, B, P) && all_eq2(H, D, F) && ne(H, load_input(in_pos + vec2i(0, 3))) { 150 M = H; 151 L = H; 152 } 153 if ne(E, B) && all_eq4(E, A, C, H, S) && all_eq2(B, D, F) && ne(B, load_input(in_pos + vec2i(0, -3))) { 154 K = B; 155 J = B; 156 } 157 if Bl < El && all_eq4(E, G, H, I, S) && none_eq4(E, A, D, C, F) { 158 K = B; 159 J = B; 160 } 161 if Hl < El && all_eq4(E, A, B, C, P) && none_eq4(E, D, G, I, F) { 162 M = H; 163 L = H; 164 } 165 if Fl < El && all_eq4(E, A, D, G, Q) && none_eq4(E, B, C, I, H) { 166 M = F; 167 K = F; 168 } 169 if Dl < El && all_eq4(E, C, F, I, R) && none_eq4(E, B, A, G, H) { 170 L = D; 171 J = D; 172 } 173 174 // 2:1 slope rules 175 if ne(H, B) { 176 if ne(H, A) && ne(H, E) && ne(H, C) { 177 if all_eq3(H, G, F, R) && none_eq2(H, D, load_input(in_pos + vec2i(2, -1))) { 178 L = M; 179 } 180 if all_eq3(H, I, D, Q) && none_eq2(H, F, load_input(in_pos + vec2i(-2, -1))) { 181 M = L; 182 } 183 } 184 185 if ne(B, I) && ne(B, G) && ne(B, E) { 186 if all_eq3(B, A, F, R) && none_eq2(B, D, load_input(in_pos + vec2i(2, 1))) { 187 J = K; 188 } 189 if all_eq3(B, C, D, Q) && none_eq2(B, F, load_input(in_pos + vec2i(-2, 1))) { 190 K = J; 191 } 192 } 193 } // H !== B 194 195 if ne(F, D) { 196 if ne(D, I) && ne(D, E) && ne(D, C) { 197 if all_eq3(D, A, H, S) && none_eq2(D, B, load_input(in_pos + vec2i(1, 2))) { 198 J = L; 199 } 200 if all_eq3(D, G, B, P) && none_eq2(D, H, load_input(in_pos + vec2i(1, -2))) { 201 L = J; 202 } 203 } 204 205 if ne(F, E) && ne(F, A) && ne(F, G) { 206 if all_eq3(F, C, H, S) && none_eq2(F, B, load_input(in_pos + vec2i(-1, 2))) { 207 K = M; 208 } 209 if all_eq3(F, I, B, P) && none_eq2(F, H, load_input(in_pos + vec2i(-1, -2))) { 210 M = K; 211 } 212 } 213 } // F !== D 214 } // not constant 215 216 // Write four pixels at once 217 let out_pos_tl = 2 * in_pos; 218 textureStore(output_frame, out_pos_tl + vec2i(0, 0), vec4f(J, 1.0)); 219 textureStore(output_frame, out_pos_tl + vec2i(1, 0), vec4f(K, 1.0)); 220 textureStore(output_frame, out_pos_tl + vec2i(0, 1), vec4f(L, 1.0)); 221 textureStore(output_frame, out_pos_tl + vec2i(1, 1), vec4f(M, 1.0)); 222}