1// MMPX Enhanced 2// Original MMPX by Morgan McGuire and Mara Gagiu, MIT license. 3// Adapted for Slang by hunterk. Enhanced by CrashGG. 4// Ported from libretro/slang-shaders mmpx-ex.slang to Geargrafx GLSL preset uniforms. 5 6// Adapted to a compute shader with num workgroups equal to ceil(inputSize / 16); each invocation writes 2x2 output pixels 7 8layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; 9 10layout(rgba8, binding = 0) uniform readonly image2D Source; 11layout(rgba8, binding = 1) uniform writeonly image2D Output; 12 13uint pack_color(vec3 color) 14{ 15 uvec3 value = uvec3(clamp(color, vec3(0.0), vec3(1.0)) * 255.0 + vec3(0.5)); 16 return value.r | (value.g << 8u) | (value.b << 16u) | (255u << 24u); 17} 18 19vec3 unpack_color(uint value) 20{ 21 return vec3(float(value & 255u), float((value >> 8u) & 255u), float((value >> 16u) & 255u)) / 255.0; 22} 23 24float flag(bool value) 25{ 26 return value ? 1.0 : 0.0; 27} 28 29 30float luma(vec3 col){ 31 //Use BT.601 standard from the CRT era 32 return dot(col, vec3(0.299, 0.587, 0.114)); 33} 34 35/* Constant definitions: 360.145898 : Two short golden ratios of 1.0 370.0638587 : Squared short golden ratio (2x) for RGB Euclidean distance 380.4377 : Squared short golden ratio (1x) for RGB Euclidean distance 390.75 : Squared half of RGB Euclidean distance 40*/ 41 42bool simb(vec3 col1, vec3 col2) { 43 44 vec3 diff = col1 - col2; 45 46 float maxdiff = max(diff.r, max(diff.g, diff.b)); 47 float mindiff = min(diff.r, min(diff.g, diff.b)); 48 49 // Luminance weight floor: both colors must be > 0.078. 50 float weight = step(0.234, min(col1.r+col1.g+col1.b, col2.r+col2.g+col2.b)); 51 52 // Find the most opposite channel: take the smallest absolute value if one is positive and the other negative; 0 if same sign 53 // Filter same-sign cases using max(0.0, ...) 54 // Skip team_rebel if either pixel's luminance < 0.078 55 float team_rebel = min(max(0.0, maxdiff), max(0.0, -mindiff)) * weight; 56 float finaldist = (maxdiff - mindiff) + team_rebel; 57 58 float dot_diff = dot(diff, diff); 59 60 // Equivalent to squared final distance scaled by the short golden ratio. 61 float factor = (finaldist * finaldist) * 46.9787; 62 63 return dot_diff < mix(0.0638587, 0.0, factor); 64} 65 66bool sim(vec3 col1, vec3 col2) { 67 68 vec3 diff = col1 - col2; 69 70 // RGB color difference range (max_diff - min_diff) 71 float delta_range = max(diff.r, max(diff.g, diff.b)) - min(diff.r, min(diff.g, diff.b)); 72 73 float dot_diff = dot(diff, diff); 74 75 // Equivalent to squared delta range scaled by the short golden ratio. 76 float factor = (delta_range * delta_range) * 6.8541; 77 78 return dot_diff < mix(0.0638587, 0.0, factor); 79} 80 81bool vi_sim(vec3 col1, uint uC1, uint uC2) { 82 if (uC1==uC2) return true; 83 vec3 col2 = unpack_color(uC2); 84 return sim(col1, col2); 85} 86 87float mixGate(vec3 col1, vec3 col2) { 88 89 vec3 diff = col1 - col2; 90 91 // RGB color difference range (max_diff - min_diff) 92 float delta_range = max(diff.r, max(diff.g, diff.b)) - min(diff.r, min(diff.g, diff.b)); 93 94 float dot_diff = dot(diff, diff); 95 96 // Equivalent to squared delta range scaled by the long golden ratio. 97 float factor = (delta_range * delta_range) * 2.618034; 98 99 return step(dot_diff, mix(0.75, 0.0, factor)); 100} 101 102 103#define eq(a,b) (a==b) 104 105#define neq(a,b) (a!=b) 106 107#define all_eq2(a, b1, b2) \ 108 ( eq(a,b1) && eq(a,b2)) 109 110#define all_eq3(a, b1, b2, b3) \ 111 ( eq(a,b1) && eq(a,b2) && eq(a,b3)) 112 113#define all_eq4(a, b1, b2, b3, b4) \ 114 ( eq(a,b1) && eq(a,b2) && eq(a,b3) && eq(a,b4)) 115 116#define any_eq2(a, b1, b2) (eq(a,b1)||eq(a,b2)) 117#define any_eq3(a, b1, b2, b3) (eq(a,b1)||eq(a,b2)||eq(a,b3)) 118// Better than a!=b1 && a!=b2 119#define none_eq2(a, b1, b2) !any_eq2(a, b1, b2) 120 121 122// Allow total int2 difference across 3 channels 123//#define vec_neq(a,b) (dot(abs(a-b), vec3(1.0)) > 0.01) 124// Allow int2 difference per channel for 3 channels 125#define vec_eq(a,b) all(lessThan(abs(a-b), vec3(0.01))) 126#define vec_neq(a, b) any(greaterThan(abs(a-b), vec3(0.01))) 127 128 129// Pre-define 130//const vec3 testcolor = vec3(1.0, 0.0, 1.0); // Magenta 131//const vec3 testcolor2 = vec3(0.0, 1.0, 1.0); // Cyan 132//const vec3 testcolor3 = vec3(1.0, 1.0, 0.0); // Yellow 133//const vec3 testcolor4 = vec3(1.0, 1.0, 1.0); // White 134const vec3 slopOFF = vec3(2.0); 135const vec3 slopeBAD = vec3(4.0); 136const vec3 theEXIT = vec3(8.0); 137 138#define mixXE mix(vX,vE,mixFactor) 139#define mixXEoff mixXE+slopOFF 140#define Xoff vX+slopOFF 141//#define checkblack(col) ((col).g < 0.078 && (col).r < 0.1 && (col).b < 0.1) 142#define checkblack(col) all(lessThan((col).rgb, vec3(0.1, 0.078, 0.1))) 143#define checkwhite(col) all(greaterThan((col).rgb, vec3(0.92, 0.92, 0.92))) 144 145//pin zz 146// "Concave + Cross" weak blend (weak/no blending) 147vec3 admixC(vec3 vX, vec3 vE) { 148 // Weak blending. Mix if 0.618, else 1.0 149 float mixFactor = mixGate(vX, vE) * (-0.381966) + 1.0; 150 151 return mixXE; 152} 153 154// K-type forced weak blend 155vec3 admixK(vec3 vX, vec3 vE) { 156 vec3 diff = vX - vE; 157 // mixFactor scales from 0.5-1.0 with quadratic falloff (steeper near 1.0) based on squared distance 158 float mixFactor = dot(diff, diff) * 0.16666 + 0.5; 159 // mixFactor scales linearly from 0.5-1.0 based on Euclidean distance 160 //float mixFactor = distance(vX, vE) * 0.28867 + 0.5; 161 return mixXE; 162} 163 164// L-type 2:1 slope - main corner extension 165// Practical rule: this requires 4 pixels on the strict slope to be identical. Artifacts occur otherwise! 166vec3 admixL(vec3 vX, vec3 vE, vec3 vS) { 167 168 // If target X differs from reference S(sample), it's already blended once - copy directly without re-blending 169 if (vec_neq(vX, vS)) return vX; 170 171 float mixFactor = 0.381966 * mixGate(vX,vE); 172 173 return mixXE; 174} 175 176/************************************************************************************************************************************** 177 * Main slope + X cross-processing mechanism * 178 ******************************************************************************************************************************** zz */ 179vec3 admixX( uint A, uint B, uint C, uint D, uint E, uint F, uint G, uint H, uint I 180 , uint P, uint PA, uint PC, uint Q, uint QA, uint QG, uint R, uint RC, uint RI, uint S, uint SG, uint SI, uint AA, uint CC, uint GG 181 , float El, float Bl, float Dl, float Fl, float Hl 182 , vec3 vE, vec3 vB, vec3 vD, vec3 vC, vec3 vG 183 ) { 184 185 186 bool eq_B_C = eq(B,C); 187 bool eq_D_G = eq(D,G); 188 189 // Exit if enclosed by straight walls on both sides 190 if (eq_B_C && eq_D_G) return slopeBAD; 191 192 193 //Pre-declare 194 bool eq_B_P; bool eq_B_PA; bool eq_B_PC; 195 bool eq_D_Q; bool eq_D_QA; bool eq_D_QG; 196 bool eq_E_F; bool eq_E_H; bool eq_A_AA; 197 198 vec3 vX; 199 float mixFactor; 200 201 bool eq_E_C = eq(E,C); 202 bool eq_E_G = eq(E,G); 203 bool eq_A_P = eq(A,P); 204 bool eq_A_Q = eq(A,Q); 205 bool comboE3 = eq_E_C && eq_E_G; 206 bool comboA3 = eq_A_P && eq_A_Q; 207 208/*========================================= 209 B != D 210 ==================================== zz */ 211if (neq(B,D)){ 212 213 // E == A violates logic, exit 214 if (eq(E,A)) return slopeBAD; 215 216 // B-D unconnected? Removed 217 218 // If B and D differ significantly (more than either to center E), exit 219 float diffBD = abs(Bl-Dl); 220 if (diffBD > El-Bl || diffBD > El-Dl) return slopeBAD; 221 222 223 // X is blend of B and D 224 vX = mix(vB, vD, 0.5); 225 226 mixFactor = 0.381966 * mixGate(vX,vE); 227 228 eq_B_PC = eq(B,PC); 229 eq_D_QG = eq(D,QG); 230 231 // Strong pattern set 232 if (none_eq2(A,B,D)){ 233 if (comboA3) return mixXEoff; 234 if ( eq_A_P && eq_B_PC && !eq_B_C ) return mixXEoff; 235 if ( eq_A_Q && eq_D_QG && !eq_D_G ) return mixXEoff; 236 237 // Double slope clamping BD (direction-aware) 238 if ( eq_A_P && eq_E_G ) return mixXEoff; 239 if ( eq_A_Q && eq_E_C ) return mixXEoff; 240 241 // Hollow L inner corner 242 if ( eq_E_C && eq_D_G ) return mixXEoff; 243 if ( eq_E_G && eq_B_C ) return mixXEoff; 244 245} 246 // E-side triple match 247 if ( comboE3 ) return mixXEoff; 248 249 // Original rule with added slope condition 250 if ( eq_E_C && eq_B_PC && neq(B,P)) return mixXEoff; 251 if ( eq_E_G && eq_D_QG && neq(D,Q)) return mixXEoff; 252 253 eq_E_F = eq(E,F); 254 255 // F == H 256 if (eq(F,H)) { 257 258 // Double slope (exclude single-pixel C enclosure, BD are weakly connected) 259 if ( eq_E_C && !eq_D_G && (!eq_E_F||neq(E,P)) ) return mixXEoff; 260 if ( eq_E_G && !eq_B_C && (!eq_E_F||neq(E,Q)) ) return mixXEoff; 261 262 // F+H extension 263 if ( !eq_E_F && eq_B_PC && eq(F,RC) ) return mixXEoff; 264 if ( !eq_E_F && eq_D_QG && eq(H,SG) ) return mixXEoff; 265 } 266 267 return slopeBAD; 268} // B != D 269 270 271 /********* B == D *********/ 272 273 // Prevent font edges from being crushed by black background on 3 sides 274 bool Xisblack = checkblack(vB); 275 if ( Xisblack && El >0.5 && (Fl<0.078 || Hl<0.078) ) return theEXIT; 276 277 vX = vB; 278 279 mixFactor = 0.381966 * mixGate(vX,vE); 280 281 bool B_slope; bool B_tower; bool B_wall; 282 bool D_slope; bool D_tower; bool D_wall; 283 bool En3; 284 #define En4square En3&&eq(E,I) 285 286/*=================================================== 287 E - A intersection 288 ============================================== zz */ 289if (eq(E,A)) { 290 291 // Special pattern: dithering 292 // Goal: force blending 293 294 eq_E_F = eq(E,F); 295 eq_E_H = eq(E,H); 296 297 bool Eisblack = checkblack(vE); 298 299 // 1. Dither center 300 if ( comboE3 && !eq_E_F && !eq_E_H && eq(E,I) ) { 301 302 // Exit if center E is black (KOF '96 power gauge, Punisher belt) - avoid high-contrast blending 303 if (Eisblack) return theEXIT; 304 // Practical 1: skip black B points (handled by normal logic) 305 // Mix if 0.381966, else 0.618034 306 mixFactor = 0.618034 * (1.0 - mixFactor); 307 return mixXEoff; 308 } 309 310 eq_A_AA = eq(A,AA); 311 312 // 2. Dither edge 313 if ( comboA3 && eq_A_AA && none_eq2(A,PA,QA) ) { 314 if (Eisblack) return theEXIT; 315 // Mix if 0.381966, else 0.618034 316 mixFactor = 0.618034 * (1.0 - mixFactor); 317 // Strong blend for layered gradient edges 318 if ( neq(B,PA) && eq(PA,QA) ) return mixXEoff; 319 // Remainder: perfect cross = dither edge, use weak blend 320 // Practical: no special handling needed for health bar borders 321 // Floor weak blend. Mix if 0.618, else 0.854 322 // Note: mixFactor already modified above 323 mixFactor += 0.236068; 324 return mixXEoff; 325 } 326 327 eq_B_PC = eq(B,PC); 328 eq_B_PA = eq(B,PA); 329 eq_D_QG = eq(D,QG); 330 eq_D_QA = eq(D,QA); 331 332 // Subsequent checks skip Eisblack 333 // 3. Half-dither (usually silhouette edge shading), use weak blend 334 335 if ( comboE3 && comboA3 && 336 (eq_B_PC || eq_D_QG) && eq_D_QA && eq_B_PA) { 337 // Floor weak blend. Mix if 0.618, else 0.854 338 mixFactor = mixFactor * (-0.618034) + 0.8541; 339 return mixXEoff; 340 } 341 342 // 4. Quarter-dither (prevents ugly "pinky" artifacts: SF2 Guile plane, Cadillacs and Dinosaurs character select) 343 344 if ( comboE3 && eq_A_P 345 && eq_B_PA && eq_D_QA && eq_D_QG 346 && eq_E_H 347 ) {// Floor weak blend. Mix if 0.618, else 0.854 348 mixFactor = mixFactor * (-0.618034) + 0.8541; 349 return mixXEoff; 350 } 351 352 if ( comboE3 && eq_A_Q 353 && eq_B_PA && eq_D_QA && eq_B_PC 354 && eq_E_F 355 ) {// Floor weak blend. Mix if 0.618, else 0.854 356 mixFactor = mixFactor * (-0.618034) + 0.8541; 357 return mixXEoff; 358 } 359 360 361 // A-side triple match (strong pattern, after dither logic) 362 if (comboA3) return Xoff; 363 364 // E-side triple match (after comboA3) 365 if (comboE3) return mixXEoff; 366 367 eq_B_P = eq(B, P); 368 eq_D_Q = eq(D, Q); 369 370 B_slope = eq_B_PC && !eq_B_P && !eq_B_C && !eq_B_PA; 371 D_slope = eq_D_QG && !eq_D_Q && !eq_D_G && !eq_D_QA; 372 373 B_wall = eq_B_C && !eq_B_PC && !eq_B_P; // Removed one misalignment check 374 D_wall = eq_D_G && !eq_D_QG && !eq_D_Q; // Removed one misalignment check 375 376 B_tower = eq_B_P && !eq_B_PC && !eq_B_C && !eq_B_PA; 377 D_tower = eq_D_Q && !eq_D_QG && !eq_D_G && !eq_D_QA; 378 379 380 if ( B_slope && eq_E_G ) return mixXEoff; 381 if ( D_slope && eq_E_C ) return mixXEoff; 382 383 384// E/B/D zone scoring system 385 386 float scoreE = 0.0; float scoreB = 0.0; float scoreD = 0.0; float scoreZ = 0.0; 387 388// E Zone 389 if (eq_E_C) { 390 scoreE += 1.0 + flag(eq(F,H)) + flag(B_slope); 391 scoreE -= flag(all_eq2(E,P,PC)&&!D_wall); 392 } 393 394 if (eq_E_G) { 395 scoreE += 1.0 + flag(eq(F,H)) + flag(D_slope); 396 scoreE -= flag(all_eq2(E,Q,QG)&&!B_wall); 397 } 398 399 // Higher priority than rectangles 400 scoreE += flag(B_slope && eq_A_Q || D_slope && eq_A_P); 401 402 En3 = eq_E_F && eq_E_H; 403 404 // Clear 4/6-square: early exit, skip final Z long-slope check 405 if ( scoreE<0.1 && mixFactor<0.1 && En4square && eq(E,S)==eq(E,SI) && eq(E,R)==eq(E,RI) ) return theEXIT; 406 407 // No score for En3 408 //if ( scoreE==0 && En3 ) scoreE += 1; 409 410 // Single bar 411 if ( scoreE<0.1 && !En3 && neq(E,I) ) { 412 if ( B_wall && eq_E_F ) return theEXIT; 413 if ( D_wall && eq_E_H ) return theEXIT; 414 } 415 416 // Lower priority than single bar 417 scoreE += flag(B_slope && eq_A_P || D_slope && eq_A_Q); 418 419 if ( !En3 && eq(F,H) ) { 420 if (Eisblack) return slopeBAD; // Black single pixel 421 // slope+eq_F_H combination disabled (avoids bubbles with inner L in BD zone) 422 //scoreE += flag(B_slope&&neq(C,F))+flag(D_slope&&neq(G,H)); 423 424 bool condZ1 = B_wall && (eq(F,R) || eq(F,RC) || eq(G,H) || eq(F,I)); 425 bool condZ2 = D_wall && (eq(C,F) || eq(H,SG) || eq(H,S) || eq(F,I)); 426 scoreZ = flag(condZ1 || condZ2); 427 } 428 429 430// B Zone 431 432 if (eq_B_PA) { 433 scoreB -= 1.0 + flag(eq(P,C)) + flag(eq_A_AA); 434 } 435 436 if (eq(P,C)){ 437 scoreB -= flag(eq_A_AA); 438 // Critical: prevent Z scoring on mirrored shapes when only F==H 439 // Equivalent: if (scoreE==0) scoreZ = 0; 440 scoreZ *= flag(scoreE < 0.1); 441 } 442 443// D Zone 444 445 if (eq_D_QA) { 446 scoreD -= 1.0 + flag(eq(G,Q)) + flag(eq_A_AA); 447 } 448 449 if (eq(G,Q)){ 450 scoreD -= flag(eq_A_AA); 451 // Same logic as B zone 452 scoreZ *= flag(scoreE < 0.1); 453 } 454 455 float scoreFinal = scoreE + scoreB + scoreD + scoreZ ; 456 457 // Long slope: return unblended vX if B/D zones have no penalties and form a gentle slope 458 scoreFinal += flag(min(scoreB,scoreD) > -0.1 && (B_wall && D_tower || B_tower && D_wall)) * 2.0; 459 460 // Set mixFactor to 0 (no blend) if scoreFinal >= 2, return vX 461 mixFactor *= (1.0 - step(1.9, scoreFinal)); 462 // Return mixXE if scoreFinal >=1, else slopeBAD 463 return mixXE + slopeBAD*(1.0 - step(0.9, scoreFinal)); 464 465} // E == A 466 467 468/*=============================================== 469 Main rule: E - C - G 470 ========================================== zz */ 471 472 if (eq_E_C ) { 473 if (comboA3) return vX; 474 if (comboE3) return mixXE; 475 if (all_eq2(B,A,PA) && all_eq3(E,F,P,PC)) return theEXIT; 476 return mixXE; 477 } 478 479 if (eq_E_G) { 480 if (comboA3) return vX; 481 if (comboE3) return mixXE; 482 if (all_eq2(D,A,QA) && all_eq3(E,H,Q,QG)) return theEXIT; 483 return mixXE; 484 } 485 486 487/*========================================================= 488 F - H / B+ D+ extension rules 489 ==================================================== zz */ 490 491 // This section handles leftovers from previous filters; center En4square is naturally walled off from BD logic 492 // B-D unconnected? No longer needed with new "double slope" logic 493 // Rule 1: Flatten inner L corner, not outer 494 // Rule 2: Flatten outer "" edge, not inner 495 496 bool eq_A_B = eq(A,B); 497 bool eq_F_H = eq(F,H); 498 499 eq_B_P = eq(B,P); 500 eq_B_PC = eq(B,PC); 501 eq_B_PA = eq(B,PA); 502 eq_D_Q = eq(D,Q); 503 eq_D_QG = eq(D,QG); 504 eq_D_QA = eq(D,QA); 505 506 B_slope = eq_B_PC && !eq_B_P && !eq_B_C; 507 D_slope = eq_D_QG && !eq_D_Q && !eq_D_G; 508 B_tower = eq_B_P && !eq_B_PC && !eq_B_C && !eq_B_PA; 509 D_tower = eq_D_Q && !eq_D_QG && !eq_D_G && !eq_D_QA; 510 B_wall = eq_B_C && !eq_B_PC && !eq_B_P; 511 D_wall = eq_D_G && !eq_D_QG && !eq_D_Q; 512 513 514// 1. B-D hollow slope 515 if (!eq_A_B) { 516 517 // A-side triple match (high priority) 518 // Note: comboA3 only valid when A!=B in this section 519 if (comboA3) return Xoff; 520 521 if ( (B_slope||B_tower) && (D_slope||D_tower) ) return Xoff; 522 523 if ( B_slope && eq_A_P ) return mixXEoff; 524 if ( D_slope && eq_A_Q ) return mixXEoff; 525 526 if ( (B_slope || D_slope) && eq_F_H ) return mixXEoff; 527 528 if ( B_slope && eq(H,SG) ) return mixXEoff; 529 if ( D_slope && eq(F,RC) ) return mixXEoff; 530 531 if ( B_slope && eq_A_Q && eq(Q,QG) ) return mixXEoff; 532 if ( D_slope && eq_A_P && eq(P,PC) ) return mixXEoff; 533 534 } 535 536 537 538 bool sim_EC = sim(vE, vC); 539 bool sim_EG = sim(vE, vG); 540 541 // Exit if center E is a high-contrast single pixel 542 // Tighten threshold for bright E 543 float E_lumDiff = mix(0.381966, 0.145898, max((El - 0.8541),0.0) * 6.8541); 544 545 // High contrast to neighbors (lower priority than slope detection) 546 if ( mixFactor<0.1 && !sim_EC && !sim_EG && neq(E,I) && abs(El-Fl)>E_lumDiff && abs(El-Hl)>E_lumDiff ) return slopeBAD; 547 548 549 eq_E_F = eq(E,F); 550 eq_E_H = eq(E,H); 551 552 // Long gentle slope (preserve squares for later En4square check) 553 if ( eq_B_C && eq_D_Q ) { 554 if ( eq(P,PC) && eq(A,QA) && !eq_D_QG && eq_E_F && !eq_E_H && eq(H,I)) return theEXIT; 555 if ( eq_A_B ) return slopeBAD; 556 if ( B_wall && D_tower && eq_E_F) return vX; 557 return mixXEoff; 558 } 559 560 if ( eq(D,G) && eq(B,P)) { 561 if ( eq(Q,QG) && eq(A,PA) && !eq_B_PC && eq_E_H && !eq_E_F && eq(F,I)) return theEXIT; 562 if ( eq_A_B ) return slopeBAD; 563 if ( B_tower && D_wall && eq_E_H) return vX; 564 return mixXEoff; 565 } 566 567 568 En3 = eq_E_F && eq_E_H; 569 570 // Wall-enclosed 4-square (En3 && eq(E,I)) 571 if ( En4square ) { // Must come after previous rule 572 // Exit for solid L enclosure (font edges, building corners) 573 // Solid L corner / high-contrast clear 4-square / 6-rectangle (no need for eq(G,H)/eq(C,F) checks) 574 if ( ( eq_B_C || eq_D_G) && eq_A_B) return theEXIT; 575 if ( ( eq_B_C || eq_D_G || mixFactor<0.1) && (eq(E,S) == eq(E, SI) && eq(E,R) == eq(E, RI)) ) return theEXIT; 576 return mixXEoff; 577 } 578 579 // BD non-wall solid shapes 580 if (!eq_B_C && !eq_D_G ) { 581 // B/D semi-solid 1 (requires F-H) 582 if ( comboA3 && eq_F_H ) return Xoff; 583 584 // B/D semi-solid 2 (with "definite rounding" trend) 585 if ( comboA3&&eq_B_PC&&eq(C,CC) ) return Xoff; 586 if ( comboA3&&eq_D_QG&&eq(G,GG) ) return Xoff; 587 588 // Exit if B/D unconnected and not En3 (required for this branch) 589 if ( !eq_B_P && !eq_B_PC && !eq_D_Q && !eq_D_QG && !En3 ) return slopeBAD; 590 591 // 3 diagonal gradients (after above filter) 592 if (eq_A_Q&&sim_EC) return mixXEoff; 593 if (eq_A_P&&sim_EG) return mixXEoff; 594 if (sim_EC&&sim_EG ) return mixXEoff; 595 } 596 597 // Wall-enclosed triangle (remove solid corner, pass to next rule) 598 if ( En3 && eq_A_B) return theEXIT; 599 600 // F - H 601 // Rule: connect inner L corner, not outer 602 if (eq_F_H) { 603 604 // F-H triple pattern (huge quality boost! priority over A==B) 605 if ( eq_B_PC&&eq(F,RC) || eq_D_QG&&eq(H,SG) ) return mixXEoff; 606 607 if (eq_A_B) return slopeBAD; 608 609 if ( eq_B_C || eq_D_G) return mixXEoff; 610 if ( eq_B_PC || eq_D_QG) return mixXEoff; 611 612 } 613 614 return slopeBAD; 615 616} // admixX 617 618 619vec3 admixS( uint A, uint B, uint C, uint D, uint E, uint F, uint G, uint H, uint I 620 , uint R, uint RC, uint RI, uint S, uint SG, uint SI, uint II, uint CC 621 , vec3 vE, vec3 vF, vec3 vC 622 ) { 623 624 // B . 625 // Zone 4 626 // 627 // S 628 629 630 if (any_eq2(F,C,I)) return vE; 631 632 // Skip destructive patterns on opposite side 633 if ( (eq(F,RI) || eq(G,S) || eq(R, RI)) && neq(R,I) ) return vE; 634 635 if (eq(H, S) && none_eq2(H,I,SG)) return vE; 636 637 if ( eq(R, RC) || eq(G,SG) ) return vE; 638 639 // Extend one extra pixel in trend direction if E is white in D==E==C pattern (Street Fighter II Guile's face) 640 if ( checkwhite(vE) && all_eq2(E,C,D) && none_eq2(E,RC,CC)) return vE; 641 642 // Old opposite trend check 643 // if ( none_eq2(I,H,S) && (neq(SI,RI) || eq(I,II)) ) return vE; 644 645 646 #define vX vF 647 float mixFactor = 0.381966 * mixGate(vX,vE); 648 649 if ( eq(E,C) && (eq(E,D)||eq(B,D)) ) return mixXE; 650 651 bool sim_E_C = sim(vE,vC); 652 653 if ( sim_E_C && eq(E,D) && eq(B,C) ) return mixXE; 654 655 if ( (sim_E_C || mixFactor>0.1) && all_eq2(B,C,D) ) return mixXE; 656 657 return vE; 658} 659 660////////////////////////////////////////////////////////////////////////////////////////////////////////// zz 661 662void main() 663{ 664 ivec2 inputPosition = ivec2(gl_GlobalInvocationID.xy); 665 vec2 SourceSize = imageSize(Source); 666 vec2 vTexCoord = vec2(inputPosition) / SourceSize; 667 668#define srcf(c,d) imageLoad(Source, inputPosition + ivec2(c, d)).rgb 669#define src(c,d) pack_color(srcf(c,d)) 670 671 vec3 vE = srcf(0, 0); 672 vec3 vB = srcf(0, -1); 673 vec3 vD = srcf(-1, 0); 674 vec3 vF = srcf(+1, 0); 675 vec3 vH = srcf(0, +1); 676 677 uint E = pack_color(vE); 678 uint B = pack_color(vB); 679 uint D = pack_color(vD); 680 uint F = pack_color(vF); 681 uint H = pack_color(vH); 682 683 684 bool eq_E_D = eq(E,D); 685 bool eq_E_F = eq(E,F); 686 bool eq_E_B = eq(E,B); 687 bool eq_E_H = eq(E,H); 688 bool eq_B_H = eq(B,H); 689 bool eq_D_F = eq(D,F); 690 691// Skip horizontal/vertical 3x1 lines 692bool skiprest = (eq_E_D && eq_E_F) || (eq_E_B && eq_E_H) || (eq_B_H && eq_D_F); 693if (!skiprest) { 694 695 696 697 //Sample 5x5 grid 698 vec3 vA = srcf(-1, -1); 699 vec3 vC = srcf(+1, -1); 700 vec3 vG = srcf(-1, +1); 701 vec3 vI = srcf(+1, +1); 702 703 uint A = pack_color(vA); 704 uint C = pack_color(vC); 705 uint G = pack_color(vG); 706 uint I = pack_color(vI); 707 708 uint P = src( 0, -2); 709 uint Q = src(-2, 0); 710 uint R = src(+2, 0); 711 uint S = src( 0, +2); 712 713 uint PA = src(-1, -2); 714 uint PC = src(+1, -2); 715 uint QA = src(-2, -1); 716 uint QG = src(-2, +1); // AA PA [P] PC CC 717 uint RC = src(+2, -1); // 718 uint RI = src(+2, +1); // QA A B C RC 719 uint SG = src(-1, +2); // 720 uint SI = src(+1, +2); // [Q] D E F [R] 721 uint AA = src(-2, -2); // 722 uint CC = src(+2, -2); // QG G H I RI 723 uint GG = src(-2, +2); // 724 uint II = src(+2, +2); // GG SG [S] SI II 725 726 // Default: nearest-neighbor upscale 727 vec3 J = vE; vec3 K = vE; vec3 L = vE; vec3 M = vE; 728 729 // ------------------------ Boundary check --- Libretro ----------------------- 730 // Define dummy pixel color (impossible value) 731 #define fakeColor vec3(1.234) 732 // 1. Precompute integer pixel coordinates. 733 vec2 pixelPos = vTexCoord * SourceSize.xy; 734 ivec2 currPixel = ivec2(floor(pixelPos)); // Current pixel integer coords (0-based) 735 ivec2 texSize = ivec2(SourceSize.xy); // Actual texture pixel dimensions 736 737 // 3. Check edge overflow - use dummy color to influence luminance logic 738 if (currPixel.y - 1 < 0) vB = fakeColor; // Top pixel overflow (y-1 < 0) 739 if (currPixel.x - 1 < 0) vD = fakeColor; // Left pixel overflow (x-1 < 0) 740 if (currPixel.x + 1 >= texSize.x) vF = fakeColor; // Right pixel overflow (x+1 >= width) 741 if (currPixel.y + 1 >= texSize.y) vH = fakeColor; // Bottom pixel overflow (y+1 >= height) 742 // ---------------------------------------------------------------------- 743 744// Precompute luminance 745 float Bl = luma(vB); 746 float Dl = luma(vD); 747 float El = luma(vE); 748 float Fl = luma(vF); 749 float Hl = luma(vH); 750 751 752// Pre-calcs 753 bool eq_B_D = eq(B,D); 754 bool eq_B_F = eq(B,F); 755 bool eq_D_H = eq(D,H); 756 bool eq_F_H = eq(F,H); 757 758 // Any opposite pair encloses center 759 bool oppoPix = eq_B_H || eq_D_F; 760 // Flag if caught by 1:1 slope rule and entered admixX 761 bool slope1 = false; bool slope2 = false; bool slope3 = false; bool slope4 = false; 762 // Standard pixel returned successfully from 1:1 slope rule 763 bool slope1ok = false; bool slope2ok = false; bool slope3ok = false; bool slope4ok = false; 764 bool slope1end = false; bool slope2end = false; bool slope3end = false; bool slope4end = false; 765 // slopeBAD: entered admixX but returned E (for at least one JKLM) 766 // slopOFF: returned with OFF flag - skip long slope calculations 767 768 769// B - D 770 if ( 771 (!eq_E_B && !eq_E_D && !oppoPix) && (!eq_D_H && !eq_B_F) 772 && (eq(E,A) || El>=Dl&&El>=Bl) && ( (El<Dl&&El<Bl) || none_eq2(A,B,D) || neq(E,P) || neq(E,Q) ) 773 && ( eq_B_D &&(eq(E,A)||eq(B,PC)||eq(D,QG)||sim(vE,vC)||sim(vE,vG)) || simb(vB,vD)&&(eq_F_H||eq(E,C)||eq(E,G)) ) 774 ) { 775 J=admixX(A,B,C,D,E,F,G,H,I 776 ,P,PA,PC,Q,QA,QG,R,RC,RI,S,SG,SI,AA,CC,GG 777 ,El, Bl, Dl, Fl, Hl 778 ,vE, vB, vD, vC, vG 779 ); 780 slope1 = true; // Mark on entry 781 slope1ok = (J.b < 1.1); // Valid pixel 782 slope1end = (J.b < 3.1); // Modified, skip long slope (mostly simBD) 783 skiprest = (J.b > 7.1); // theEXIT 784 J = (J.b > 3.1) ? vE : // Restore vE for slopeBAD/theEXIT 785 (J.b > 1.1) ? (J - 2.0) :// slopeoff 786 J; // Normal pixel [0-1.0] 787 } 788// B - F 789 if ( !slope1 790 && (!eq_E_B && !eq_E_F && !oppoPix) && (!eq_B_D && !eq_F_H) 791 && (eq(E,C) || El>=Bl&&El>=Fl) && ( (El<Bl&&El<Fl) || none_eq2(C,B,F) || neq(E,P) || neq(E,R) ) 792 && ( eq_B_F &&(eq(E,C)||eq(B,PA)||eq(F,RI)||sim(vE,vA)||sim(vE,vI)) || simb(vB,vF)&&(eq_D_H||eq(E,A)||eq(E,I)) ) 793 ) { 794 K=admixX(C,F,I,B,E,H,A,D,G 795 ,R,RC,RI,P,PC,PA,S,SI,SG,Q,QA,QG,CC,II,AA 796 ,El,Fl,Bl,Hl,Dl 797 ,vE,vF,vB,vI,vA 798 ); 799 slope2 = true; 800 slope2ok = (K.b < 1.1); 801 slope2end = (K.b < 3.1); 802 skiprest = (K.b > 7.1); 803 K = (K.b > 3.1) ? vE : 804 (K.b > 1.1) ? (K - 2.0) : 805 K; 806 } 807// D - H 808 if ( !slope1 && !skiprest 809 && (!eq_E_D && !eq_E_H && !oppoPix) && (!eq_F_H && !eq_B_D) 810 && (eq(E,G) || El>=Hl&&El>=Dl) && ((El<Hl&&El<Dl) || none_eq2(G,D,H) || neq(E,S) || neq(E,Q)) 811 && ( eq_D_H &&(eq(E,G)||eq(D,QA)||eq(H,SI)||sim(vE,vA)||sim(vE,vI)) || simb(vD,vH)&&(eq_B_F||eq(E,A)||eq(E,I)) ) 812 ) { 813 L=admixX(G,D,A,H,E,B,I,F,C 814 ,Q,QG,QA,S,SG,SI,P,PA,PC,R,RI,RC,GG,AA,II 815 ,El,Dl,Hl,Bl,Fl 816 ,vE,vD,vH,vA,vI 817 ); 818 slope3 = true; 819 slope3ok = (L.b < 1.1); 820 slope3end = (L.b < 3.1); 821 skiprest = (L.b > 7.1); 822 L = (L.b > 3.1) ? vE : 823 (L.b > 1.1) ? (L - 2.0) : 824 L; 825 } 826// F - H 827 if ( !slope2 && !slope3 && !skiprest 828 && (!eq_E_F && !eq_E_H && !oppoPix) && (!eq_B_F && !eq_D_H) 829 && (eq(E,I) || El>=Fl&&El>=Hl) && ((El<Fl&&El<Hl) || none_eq2(I,F,H) || neq(E,R) || neq(E,S)) 830 && ( eq_F_H &&(eq(E,I)||eq(F,RC)||eq(H,SG)||sim(vE,vC)||sim(vE,vG)) || simb(vF,vH)&&(eq_B_D||eq(E,C)||eq(E,G)) ) 831 ) { 832 M=admixX(I,H,G,F,E,D,C,B,A 833 ,S,SI,SG,R,RI,RC,Q,QG,QA,P,PC,PA,II,GG,CC 834 ,El,Hl,Fl,Dl,Bl 835 ,vE,vH,vF,vG,vC 836 ); 837 slope4 = true; 838 slope4ok = (M.b < 1.1); 839 slope4end = (M.b < 3.1); 840 skiprest = (M.b > 7.1); 841 M = (M.b > 3.1) ? vE : 842 (M.b > 1.1) ? (M - 2.0) : 843 M; 844 } 845 846 847// Long gentle 2:1 slope (P100) 848 849 if (slope4ok) { //zone4 long slope 850 // Original ext 1: pass neighbor to admixL to prevent double-blending 851 // Original ext 2: prevent L-shape reoccurrence within opposite pixels unless walled 852 if (all_eq2(R,F,G) && neq(R, RC) && (neq(Q,G)||eq(Q, QA))) {L=admixL(M,L,vH); skiprest = true;} 853 // vertical 854 if (all_eq2(S,H,C) && neq(S, SG) && (neq(P,C)||eq(P, PA))) {K=admixL(M,K,vF); skiprest = true;} 855 } 856 857 if (slope3ok) { //zone3 long slope 858 // horizontal 859 if (all_eq2(Q,D,I) && neq(Q, QA) && (neq(R,I)||eq(R, RC))) {M=admixL(L,M,vH); skiprest = true;} 860 // vertical 861 if (all_eq2(S,H,A) && neq(S, SI) && (neq(A,P)||eq(P, PC))) {J=admixL(L,J,vD); skiprest = true;} 862 } 863 864 if (slope2ok) { //zone2 long slope 865 // horizontal 866 if (all_eq2(R,F,A) && neq(R, RI) && (neq(A,Q)||eq(Q, QG))) {J=admixL(K,J,vB); skiprest = true;} 867 // vertical 868 if (all_eq2(P,B,I) && neq(P, PA) && (neq(I,S)||eq(S, SG))) {M=admixL(K,M,vF); skiprest = true;} 869 } 870 871 if (slope1ok) { //zone1 long slope 872 // horizontal 873 if (all_eq2(Q,D,C) && neq(Q, QG) && (neq(C,R)||eq(R, RI))) {K=admixL(J,K,vB); skiprest = true;} 874 // vertical 875 if (all_eq2(P,B,G) && neq(P, PC) && (neq(G,S)||eq(S, SI))) {L=admixL(J,L,vD); skiprest = true;} 876 } 877 878// Longslope complete - exit early; diagonal sawslope unlikely 879// Note: sawslope entry cannot exclude diagonal slopes (including slopeok) 880if (!skiprest && !oppoPix) { 881 882 883 // horizontal bottom 884 if (!eq_E_H && none_eq2(H,A,C)) { 885 886 // A B 887 // Q D Zone 4 888 // I 889 // 890 // (!slope3 && D!=H) required to fully exclude trend 891 if ( (!slope2 && !eq_B_F) && (!slope3 && !eq_D_H) && (!slope4end && !eq_F_H) && 892 !eq_E_F && eq(R,H) && eq(F,G) ) { 893 M = admixS( A, B, C, D, E, F, G, H, I 894 , R, RC, RI, S, SG, SI, II, CC 895 , vE, vF, vC 896 ); 897 skiprest = true;} 898 899 // A C 900 // R Zone 3 901 // G 902 // 903 if ( !skiprest && (!slope1 && !eq_B_D) && (!slope4 && !eq_F_H) && (!slope3end && !eq_D_H) && 904 !eq_E_D && eq(Q,H) && eq(D,I) ) { 905 L = admixS( C, B, A, F, E, D, I, H, G 906 , Q, QA, QG, S, SI, SG, GG, AA 907 , vE, vD, vA 908 ); 909 skiprest = true;} 910 } 911 912 // horizontal up 913 if ( !skiprest && !eq_E_B && none_eq2(B,G,I)) { 914 915 // 916 // 917 // Zone 2 918 // H I . 919 if ( (!slope1 && !eq_B_D) && (!slope4 && !eq_F_H) && (!slope2end && !eq_B_F) && 920 !eq_E_F && eq(B,R) && eq(A,F) ) { 921 K = admixS( G, H, I, D, E, F, A, B, C 922 , R, RI, RC, P, PA, PC, CC, II 923 , vE, vF, vI 924 ); 925 skiprest = true;} 926 927 // 928 // A 929 // R Zone 1 930 // . G I 931 if ( !skiprest && (!slope2 && !eq_B_F) && (!slope3 && !eq_D_H) && (!slope1end && !eq_B_D) && 932 !eq_E_D && eq(B,Q) && eq(C,D) ) { 933 J = admixS( I, H, G, F, E, D, C, B, A 934 , Q, QG, QA, P, PC, PA, AA, GG 935 , vE, vD, vG 936 ); 937 skiprest = true;} 938 939 } 940 941 // vertical left 942 if ( !skiprest && !eq_E_D && none_eq2(D,C,I) ) { 943 944 // B 945 // Q R 946 // I Zone 3 947 // 948 if ( (!slope1 && !eq_B_D) && (!slope4 && !eq_F_H) && (!slope3end && !eq_D_H) && 949 !eq_E_H && eq(D,S) && eq(A,H) ) { 950 L = admixS( C, F, I, B, E, H, A, D, G 951 , S, SI, SG, Q, QA, QG, GG, II 952 , vE, vH, vI 953 ); 954 skiprest = true;} 955 956 // 957 // A C 958 // Q F R Zone 1 959 // 960 if ( !skiprest && (!slope3 && !eq_D_H) && (!slope2 && !eq_B_F) && (!slope1end && !eq_B_D) && 961 !eq_E_B && eq(P,D) && eq(B,G) ) { 962 J = admixS( I, F, C, H, E, B, G, D, A 963 , P, PC, PA, Q, QG, QA, AA, CC 964 , vE, vB, vC 965 ); 966 skiprest = true;} 967 968 } 969 970 // vertical right 971 if ( !skiprest && !eq_E_F && none_eq2(F,A,G) ) { // right 972 973 // A B 974 // Q D R 975 // G I Zone 4 976 // . 977 if ( (!slope2 && !eq_B_F) && (!slope3 && !eq_D_H) && (!slope4end && !eq_F_H) && 978 !eq_E_H && eq(S,F) && eq(H,C) ) { 979 M = admixS( A, D, G, B, E, H, C, F 980 , I, S, SG, SI, R, RC, RI, II, GG 981 , vE, vH, vG 982 ); 983 skiprest = true;} 984 985 // 986 // A C 987 // Q D R Zone 2 988 // G H 989 if ( !skiprest && (!slope1 && !eq_B_D) && (!slope4 && !eq_F_H) && (!slope2end && !eq_B_F) && 990 !eq_E_B && eq(P,F) && eq(B,I) ) { 991 K = admixS( G, D, A, H, E, B, I, F, C 992 , P, PA, PC, R, RI, RC, CC, AA 993 , vE, vB, vA 994 ); 995 skiprest = true;} 996 997 } // vertical right 998} // sawslope 999 1000// Exit after sawslope; old logic: skiprest||slopeBAD (still uses slopeOFF (weak) and slopeok (strong) with minor effect) 1001skiprest = skiprest||slope1||slope2||slope3||slope4; 1002 1003/************************************************** 1004 "Concave + Cross" blending (P100) 1005 *************************************************/ 1006// Use approximate pixels for cross distant edges - improves horizontal lines + aliasing and layered gradients 1007// Example: glowing text in SFIIIn2 intro, Japanese houses in SFZ3mix, Wolf Fang intro 1008 1009vec3 vT; // Temp T 1010 1011if (!skiprest && 1012 Bl<El && !eq_E_D && !eq_E_F && eq_E_H && none_eq2(E,A,C) && all_eq2(G,H,I) && vi_sim(vE,E,S) ) { // TOP 1013 1014 if (eq_B_D||eq_B_F) { J=admixC(vB,J); K=J; 1015 if (eq_D_F) { L=mix(J,L, 0.61804); M=L; } 1016 } else { vT = El-Bl < abs(El-Dl) ? vB : vD; J=admixC(vT,J); 1017 if (eq_D_F) { K=J; L=mix(J,L, 0.61804); M=L; } 1018 else {vT = El-Bl < abs(El-Fl) ? vB : vF; K=admixC(vT,K); } 1019 } 1020 1021 skiprest = true; 1022} 1023 1024if (!skiprest && 1025 Hl<El && !eq_E_D && !eq_E_F && eq_E_B && none_eq2(E,G,I) && all_eq2(A,B,C) && vi_sim(vE,E,P) ) { // BOTTOM 1026 1027 if (eq_D_H||eq_F_H) { L=admixC(vH,L); M=L; 1028 if (eq_D_F) { J=mix(L,J, 0.61804); K=J; } 1029 } else { vT = El-Hl < abs(El-Dl) ? vH : vD; L=admixC(vT,L); 1030 if (eq_D_F) { M=L; J=mix(L,J, 0.61804); K=J; } 1031 else { vT = El-Hl < abs(El-Fl) ? vH : vF; M=admixC(vT,M); } 1032 } 1033 1034 skiprest = true; 1035} 1036 1037if (!skiprest && 1038 Fl<El && !eq_E_B && !eq_E_H && eq_E_D && none_eq2(E,C,I) && all_eq2(A,D,G) && vi_sim(vE,E,Q) ) { // RIGHT 1039 1040 if (eq_B_F||eq_F_H) { K=admixC(vF,K); M=K; 1041 if (eq_B_H) { J=mix(K,J, 0.61804); L=J; } 1042 } else { vT = El-Fl < abs(El-Bl) ? vF : vB; K=admixC(vT,K); 1043 if (eq_B_H) { M=K; J=mix(K,J, 0.61804); L=J; } 1044 else { vT = El-Fl < abs(El-Hl) ? vF : vH; M=admixC(vT,M); } 1045 } 1046 1047 skiprest = true; 1048} 1049 1050if (!skiprest && 1051 Dl<El && !eq_E_B && !eq_E_H && eq_E_F && none_eq2(E,A,G) && all_eq2(C,F,I) && vi_sim(vE,E,R) ) { // LEFT 1052 1053 if (eq_B_D||eq_D_H) { J=admixC(vD,J); L=J; 1054 if (eq_B_H) { K=mix(J,K, 0.61804); M=K; } 1055 } else { vT = El-Dl < abs(El-Bl) ? vD : vB; J=admixC(vT,J); 1056 if (eq_B_H) { L=J; K=mix(J,K, 0.61804); M=K; } 1057 else { vT = El-Dl < abs(El-Hl) ? vD : vH; L=admixC(vT,L); } 1058 } 1059 1060 skiprest = true; 1061} 1062 1063/* 1064 1065 1066 Scorpion pattern (P99). Resembles Matrix sentinels. Smooths regular staggered pixels. 1067*/ 1068// Practical rules: 1069// 1. Do NOT use approximate pixels (causes artifacts) 1070// 2. Shorten scorpion tail by 1 pixel for bright centers to catch more patterns 1071// Scorpion is exclusive - won't trigger if any earlier rule matched (entered) 1072 1073if (!skiprest && !eq_E_F&&eq_E_D&&eq_B_F&&eq_F_H && all_eq2(E,C,I) && (eq(E,Q)||El>Fl) && neq(F,src(+3, 0)) ) {K=admixK(vF,K); M=K;skiprest=true;} // RIGHT 1074if (!skiprest && !eq_E_D&&eq_E_F&&eq_B_D&&eq_D_H && all_eq2(E,A,G) && (eq(E,R)||El>Dl) && neq(D,src(-3, 0)) ) {J=admixK(vD,J); L=J;skiprest=true;} // LEFT 1075if (!skiprest && !eq_E_H&&eq_E_B&&eq_D_H&&eq_F_H && all_eq2(E,G,I) && (eq(E,P)||El>Hl) && neq(H,src(0, +3)) ) {L=admixK(vH,L); M=L;skiprest=true;} // BOTTOM 1076if (!skiprest && !eq_E_B&&eq_E_H&&eq_B_D&&eq_B_F && all_eq2(E,A,C) && (eq(E,S)||El>Bl) && neq(B,src(0, -3)) ) {J=admixK(vB,J); K=J;} // TOP 1077 1078 ivec2 outputPosition = 2 * ivec2(gl_GlobalInvocationID.xy); 1079 imageStore(Output, outputPosition + ivec2(0, 0), vec4(J, 1)); 1080 imageStore(Output, outputPosition + ivec2(1, 0), vec4(K, 1)); 1081 imageStore(Output, outputPosition + ivec2(0, 1), vec4(L, 1)); 1082 imageStore(Output, outputPosition + ivec2(1, 1), vec4(M, 1)); 1083 1084 //float fx = step(0.5, a.x); 1085 //float fy = step(0.5, a.y); 1086 1087 //FragColor.rgb = mix(mix(J, L, fy), mix(K, M, fy), fx); 1088 1089} else { 1090 ivec2 outputPosition = 2 * ivec2(gl_GlobalInvocationID.xy); 1091 imageStore(Output, outputPosition + ivec2(0, 0), vec4(vE, 1)); 1092 imageStore(Output, outputPosition + ivec2(1, 0), vec4(vE, 1)); 1093 imageStore(Output, outputPosition + ivec2(0, 1), vec4(vE, 1)); 1094 imageStore(Output, outputPosition + ivec2(1, 1), vec4(vE, 1)); 1095} 1096}