@pixagram/renderart 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/xbrz-gpu.js CHANGED
@@ -7,28 +7,21 @@
7
7
  * Copyright (C) 2014-2016 DeSmuME team (shader algorithms)
8
8
  * Copyright (C) 2011/2016 Hyllian - sergiogdb@gmail.com
9
9
  */
10
- // Vertex shader
11
10
  const VERTEX_SHADER = `#version 300 es
12
11
  layout(location = 0) in vec2 position;
13
12
  out vec2 vUv;
14
-
15
13
  void main() {
16
14
  vUv = position * 0.5 + 0.5;
17
15
  gl_Position = vec4(position, 0.0, 1.0);
18
16
  }`;
19
- // Fragment shader with xBRZ algorithm
20
- const FRAGMENT_SHADER = `#version 300 es
17
+ const COMMON_HEADER = `#version 300 es
21
18
  precision highp float;
22
-
23
19
  uniform sampler2D uTex;
24
20
  uniform vec2 uInputRes;
25
- uniform vec2 uOutputRes;
26
- uniform int uScale;
27
21
  uniform float uLuminanceWeight;
28
22
  uniform float uEqualColorTolerance;
29
23
  uniform float uSteepDirectionThreshold;
30
24
  uniform float uDominantDirectionThreshold;
31
-
32
25
  in vec2 vUv;
33
26
  out vec4 outColor;
34
27
 
@@ -36,61 +29,24 @@ out vec4 outColor;
36
29
  #define BLEND_NORMAL 1
37
30
  #define BLEND_DOMINANT 2
38
31
 
39
- // Sample source texture with clamping
40
- vec4 tex(vec2 coord) {
41
- return texture(uTex, clamp(coord, vec2(0.0), vec2(1.0)));
42
- }
43
-
44
- // Reduce color to single value for equality comparison
45
- float reduce(vec3 color) {
46
- return dot(color, vec3(65536.0, 256.0, 1.0));
47
- }
32
+ vec4 tex(vec2 coord) { return texture(uTex, clamp(coord, vec2(0.0), vec2(1.0))); }
33
+ float reduce(vec3 c) { return dot(c, vec3(65536.0, 256.0, 1.0)); }
48
34
 
49
- // YCbCr color distance (perceptually weighted)
50
- float distYCbCr(vec3 pixA, vec3 pixB) {
35
+ float DistYCbCr(vec3 pixA, vec3 pixB) {
51
36
  const vec3 w = vec3(0.2627, 0.6780, 0.0593);
52
37
  const float scaleB = 0.5 / (1.0 - w.b);
53
38
  const float scaleR = 0.5 / (1.0 - w.r);
54
-
55
39
  vec3 diff = pixA - pixB;
56
40
  float Y = dot(diff, w);
57
41
  float Cb = scaleB * (diff.b - Y);
58
42
  float Cr = scaleR * (diff.r - Y);
59
-
60
43
  return sqrt((uLuminanceWeight * Y) * (uLuminanceWeight * Y) + Cb * Cb + Cr * Cr);
61
44
  }
62
45
 
63
- // Check if two pixels are equal within tolerance
64
- bool isPixEqual(vec3 pixA, vec3 pixB) {
65
- return distYCbCr(pixA, pixB) < uEqualColorTolerance;
66
- }
67
-
68
- // Check if any blending is needed
69
- bool isBlendingNeeded(ivec4 blend) {
70
- return blend.x != BLEND_NONE || blend.y != BLEND_NONE ||
71
- blend.z != BLEND_NONE || blend.w != BLEND_NONE;
72
- }
46
+ bool IsPixEqual(vec3 pixA, vec3 pixB) { return DistYCbCr(pixA, pixB) < uEqualColorTolerance; }
47
+ bool IsBlendingNeeded(ivec4 blend) { return blend.x != 0 || blend.y != 0 || blend.z != 0 || blend.w != 0; }
73
48
 
74
- void main() {
75
- vec2 texelSize = 1.0 / uInputRes;
76
-
77
- // Find which source pixel and sub-pixel position
78
- vec2 srcPos = vUv * uInputRes;
79
- vec2 srcPixel = floor(srcPos);
80
- vec2 f = fract(srcPos);
81
-
82
- // Sample 5x5 neighborhood
83
- // Pixel Mapping: 20|21|22|23|24
84
- // 19|06|07|08|09
85
- // 18|05|00|01|10
86
- // 17|04|03|02|11
87
- // 16|15|14|13|12
88
-
89
- vec2 tc = (srcPixel + 0.5) * texelSize;
90
- float dx = texelSize.x;
91
- float dy = texelSize.y;
92
-
93
- vec3 src[25];
49
+ void sampleNeighborhood(vec2 tc, float dx, float dy, out vec3 src[25], out float v[9]) {
94
50
  src[21] = tex(tc + vec2(-dx, -2.0*dy)).rgb;
95
51
  src[22] = tex(tc + vec2( 0, -2.0*dy)).rgb;
96
52
  src[23] = tex(tc + vec2( dx, -2.0*dy)).rgb;
@@ -112,423 +68,285 @@ void main() {
112
68
  src[ 9] = tex(tc + vec2( 2.0*dx, -dy)).rgb;
113
69
  src[10] = tex(tc + vec2( 2.0*dx, 0)).rgb;
114
70
  src[11] = tex(tc + vec2( 2.0*dx, dy)).rgb;
115
-
116
- // Reduce colors for comparison
117
- float v[9];
118
- v[0] = reduce(src[0]);
119
- v[1] = reduce(src[1]);
120
- v[2] = reduce(src[2]);
121
- v[3] = reduce(src[3]);
122
- v[4] = reduce(src[4]);
123
- v[5] = reduce(src[5]);
124
- v[6] = reduce(src[6]);
125
- v[7] = reduce(src[7]);
126
- v[8] = reduce(src[8]);
127
-
71
+ for (int i = 0; i < 9; i++) v[i] = reduce(src[i]);
72
+ }
73
+
74
+ ivec4 detectCorners(vec3 src[25], float v[9]) {
128
75
  ivec4 blendResult = ivec4(BLEND_NONE);
129
-
130
- // Preprocess corners - determine blend type for each
131
-
132
- // Corner (1, 1) - bottom-right
76
+ // Corner (1, 1)
133
77
  if (!((v[0] == v[1] && v[3] == v[2]) || (v[0] == v[3] && v[1] == v[2]))) {
134
- float dist_03_01 = distYCbCr(src[4], src[0]) + distYCbCr(src[0], src[8]) +
135
- distYCbCr(src[14], src[2]) + distYCbCr(src[2], src[10]) +
136
- 4.0 * distYCbCr(src[3], src[1]);
137
- float dist_00_02 = distYCbCr(src[5], src[3]) + distYCbCr(src[3], src[13]) +
138
- distYCbCr(src[7], src[1]) + distYCbCr(src[1], src[11]) +
139
- 4.0 * distYCbCr(src[0], src[2]);
140
- bool dominantGradient = (uDominantDirectionThreshold * dist_03_01) < dist_00_02;
141
- blendResult[2] = ((dist_03_01 < dist_00_02) && (v[0] != v[1]) && (v[0] != v[3]))
142
- ? (dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
78
+ float dist_03_01 = DistYCbCr(src[4], src[0]) + DistYCbCr(src[0], src[8]) + DistYCbCr(src[14], src[2]) + DistYCbCr(src[2], src[10]) + 4.0 * DistYCbCr(src[3], src[1]);
79
+ float dist_00_02 = DistYCbCr(src[5], src[3]) + DistYCbCr(src[3], src[13]) + DistYCbCr(src[7], src[1]) + DistYCbCr(src[1], src[11]) + 4.0 * DistYCbCr(src[0], src[2]);
80
+ bool dominant = (uDominantDirectionThreshold * dist_03_01) < dist_00_02;
81
+ blendResult[2] = ((dist_03_01 < dist_00_02) && (v[0] != v[1]) && (v[0] != v[3])) ? (dominant ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
143
82
  }
144
-
145
- // Corner (0, 1) - bottom-left
83
+ // Corner (0, 1)
146
84
  if (!((v[5] == v[0] && v[4] == v[3]) || (v[5] == v[4] && v[0] == v[3]))) {
147
- float dist_04_00 = distYCbCr(src[17], src[5]) + distYCbCr(src[5], src[7]) +
148
- distYCbCr(src[15], src[3]) + distYCbCr(src[3], src[1]) +
149
- 4.0 * distYCbCr(src[4], src[0]);
150
- float dist_05_03 = distYCbCr(src[18], src[4]) + distYCbCr(src[4], src[14]) +
151
- distYCbCr(src[6], src[0]) + distYCbCr(src[0], src[2]) +
152
- 4.0 * distYCbCr(src[5], src[3]);
153
- bool dominantGradient = (uDominantDirectionThreshold * dist_05_03) < dist_04_00;
154
- blendResult[3] = ((dist_04_00 > dist_05_03) && (v[0] != v[5]) && (v[0] != v[3]))
155
- ? (dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
85
+ float dist_04_00 = DistYCbCr(src[17], src[5]) + DistYCbCr(src[5], src[7]) + DistYCbCr(src[15], src[3]) + DistYCbCr(src[3], src[1]) + 4.0 * DistYCbCr(src[4], src[0]);
86
+ float dist_05_03 = DistYCbCr(src[18], src[4]) + DistYCbCr(src[4], src[14]) + DistYCbCr(src[6], src[0]) + DistYCbCr(src[0], src[2]) + 4.0 * DistYCbCr(src[5], src[3]);
87
+ bool dominant = (uDominantDirectionThreshold * dist_05_03) < dist_04_00;
88
+ blendResult[3] = ((dist_04_00 > dist_05_03) && (v[0] != v[5]) && (v[0] != v[3])) ? (dominant ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
156
89
  }
157
-
158
- // Corner (1, 0) - top-right
90
+ // Corner (1, 0)
159
91
  if (!((v[7] == v[8] && v[0] == v[1]) || (v[7] == v[0] && v[8] == v[1]))) {
160
- float dist_00_08 = distYCbCr(src[5], src[7]) + distYCbCr(src[7], src[23]) +
161
- distYCbCr(src[3], src[1]) + distYCbCr(src[1], src[9]) +
162
- 4.0 * distYCbCr(src[0], src[8]);
163
- float dist_07_01 = distYCbCr(src[6], src[0]) + distYCbCr(src[0], src[2]) +
164
- distYCbCr(src[22], src[8]) + distYCbCr(src[8], src[10]) +
165
- 4.0 * distYCbCr(src[7], src[1]);
166
- bool dominantGradient = (uDominantDirectionThreshold * dist_07_01) < dist_00_08;
167
- blendResult[1] = ((dist_00_08 > dist_07_01) && (v[0] != v[7]) && (v[0] != v[1]))
168
- ? (dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
92
+ float dist_00_08 = DistYCbCr(src[5], src[7]) + DistYCbCr(src[7], src[23]) + DistYCbCr(src[3], src[1]) + DistYCbCr(src[1], src[9]) + 4.0 * DistYCbCr(src[0], src[8]);
93
+ float dist_07_01 = DistYCbCr(src[6], src[0]) + DistYCbCr(src[0], src[2]) + DistYCbCr(src[22], src[8]) + DistYCbCr(src[8], src[10]) + 4.0 * DistYCbCr(src[7], src[1]);
94
+ bool dominant = (uDominantDirectionThreshold * dist_07_01) < dist_00_08;
95
+ blendResult[1] = ((dist_00_08 > dist_07_01) && (v[0] != v[7]) && (v[0] != v[1])) ? (dominant ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
169
96
  }
170
-
171
- // Corner (0, 0) - top-left
97
+ // Corner (0, 0)
172
98
  if (!((v[6] == v[7] && v[5] == v[0]) || (v[6] == v[5] && v[7] == v[0]))) {
173
- float dist_05_07 = distYCbCr(src[18], src[6]) + distYCbCr(src[6], src[22]) +
174
- distYCbCr(src[4], src[0]) + distYCbCr(src[0], src[8]) +
175
- 4.0 * distYCbCr(src[5], src[7]);
176
- float dist_06_00 = distYCbCr(src[19], src[5]) + distYCbCr(src[5], src[3]) +
177
- distYCbCr(src[21], src[7]) + distYCbCr(src[7], src[1]) +
178
- 4.0 * distYCbCr(src[6], src[0]);
179
- bool dominantGradient = (uDominantDirectionThreshold * dist_05_07) < dist_06_00;
180
- blendResult[0] = ((dist_05_07 < dist_06_00) && (v[0] != v[5]) && (v[0] != v[7]))
181
- ? (dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
99
+ float dist_05_07 = DistYCbCr(src[18], src[6]) + DistYCbCr(src[6], src[22]) + DistYCbCr(src[4], src[0]) + DistYCbCr(src[0], src[8]) + 4.0 * DistYCbCr(src[5], src[7]);
100
+ float dist_06_00 = DistYCbCr(src[19], src[5]) + DistYCbCr(src[5], src[3]) + DistYCbCr(src[21], src[7]) + DistYCbCr(src[7], src[1]) + 4.0 * DistYCbCr(src[6], src[0]);
101
+ bool dominant = (uDominantDirectionThreshold * dist_05_07) < dist_06_00;
102
+ blendResult[0] = ((dist_05_07 < dist_06_00) && (v[0] != v[5]) && (v[0] != v[7])) ? (dominant ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE;
182
103
  }
104
+ return blendResult;
105
+ }
106
+ `;
107
+ // 2x xBRZ fragment shader
108
+ const FRAG_2X = COMMON_HEADER + `
109
+ const float M_PI = 3.1415926535897932384626433832795;
110
+
111
+ void main() {
112
+ vec2 texelSize = 1.0 / uInputRes;
113
+ vec2 srcPos = vUv * uInputRes;
114
+ vec2 srcPixel = floor(srcPos);
115
+ vec2 f = fract(srcPos);
116
+ vec2 tc = (srcPixel + 0.5) * texelSize;
183
117
 
184
- // Start with center pixel for all output pixels
185
- vec3 result = src[0];
118
+ vec3 src[25]; float v[9];
119
+ sampleNeighborhood(tc, texelSize.x, texelSize.y, src, v);
120
+ ivec4 blendResult = detectCorners(src, v);
186
121
 
187
- if (isBlendingNeeded(blendResult)) {
188
- // Scale-dependent blending
189
- if (uScale == 2) {
190
- // 2x output: 4 pixels per source pixel
191
- // Output Mapping: 00|01
192
- // 03|02
193
-
194
- vec3 dst[4];
195
- dst[0] = src[0]; dst[1] = src[0];
196
- dst[2] = src[0]; dst[3] = src[0];
197
-
198
- // Process each corner with rotation
199
- for (int rot = 0; rot < 4; rot++) {
200
- // Rotated kernel indices
201
- int k1, k2, k3, k4, k5, k6, k7, k8;
202
- ivec4 blend;
203
-
204
- if (rot == 0) {
205
- k1 = 1; k2 = 2; k3 = 3; k4 = 4; k5 = 5; k6 = 6; k7 = 7; k8 = 8;
206
- blend = blendResult;
207
- } else if (rot == 1) {
208
- k1 = 7; k2 = 8; k3 = 1; k4 = 2; k5 = 3; k6 = 4; k7 = 5; k8 = 6;
209
- blend = blendResult.wxyz;
210
- } else if (rot == 2) {
211
- k1 = 5; k2 = 6; k3 = 7; k4 = 8; k5 = 1; k6 = 2; k7 = 3; k8 = 4;
212
- blend = blendResult.zwxy;
213
- } else {
214
- k1 = 3; k2 = 4; k3 = 5; k4 = 6; k5 = 7; k6 = 8; k7 = 1; k8 = 2;
215
- blend = blendResult.yzwx;
216
- }
217
-
218
- vec3 k[9];
219
- k[0] = src[0];
220
- k[1] = (rot == 0) ? src[1] : (rot == 1) ? src[7] : (rot == 2) ? src[5] : src[3];
221
- k[2] = (rot == 0) ? src[2] : (rot == 1) ? src[8] : (rot == 2) ? src[6] : src[4];
222
- k[3] = (rot == 0) ? src[3] : (rot == 1) ? src[1] : (rot == 2) ? src[7] : src[5];
223
- k[4] = (rot == 0) ? src[4] : (rot == 1) ? src[2] : (rot == 2) ? src[8] : src[6];
224
- k[5] = (rot == 0) ? src[5] : (rot == 1) ? src[3] : (rot == 2) ? src[1] : src[7];
225
- k[6] = (rot == 0) ? src[6] : (rot == 1) ? src[4] : (rot == 2) ? src[2] : src[8];
226
- k[7] = (rot == 0) ? src[7] : (rot == 1) ? src[5] : (rot == 2) ? src[3] : src[1];
227
- k[8] = (rot == 0) ? src[8] : (rot == 1) ? src[6] : (rot == 2) ? src[4] : src[2];
228
-
229
- float vk0 = reduce(k[0]);
230
- float vk4 = reduce(k[4]);
231
- float vk5 = reduce(k[5]);
232
- float vk7 = reduce(k[7]);
233
- float vk8 = reduce(k[8]);
234
-
235
- float dist_01_04 = distYCbCr(k[1], k[4]);
236
- float dist_03_08 = distYCbCr(k[3], k[8]);
237
- bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
238
- bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
239
- bool needBlend = (blend[2] != BLEND_NONE);
240
- bool doLineBlend = blend[2] >= BLEND_DOMINANT ||
241
- !((blend[1] != BLEND_NONE && !isPixEqual(k[0], k[4])) ||
242
- (blend[3] != BLEND_NONE && !isPixEqual(k[0], k[8])) ||
243
- (isPixEqual(k[4], k[3]) && isPixEqual(k[3], k[2]) &&
244
- isPixEqual(k[2], k[1]) && isPixEqual(k[1], k[8]) && !isPixEqual(k[0], k[2])));
245
-
246
- vec3 blendPix = (distYCbCr(k[0], k[1]) <= distYCbCr(k[0], k[3])) ? k[1] : k[3];
247
-
248
- const float PI = 3.14159265359;
249
-
250
- // Apply blends to rotated destination
251
- int d1 = (rot == 0) ? 1 : (rot == 1) ? 0 : (rot == 2) ? 3 : 2;
252
- int d2 = (rot == 0) ? 2 : (rot == 1) ? 1 : (rot == 2) ? 0 : 3;
253
- int d3 = (rot == 0) ? 3 : (rot == 1) ? 2 : (rot == 2) ? 1 : 0;
254
-
255
- float blend1 = (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0;
256
- float blend2 = needBlend ? (doLineBlend ?
257
- (haveShallowLine ? (haveSteepLine ? 5.0/6.0 : 0.75) : (haveSteepLine ? 0.75 : 0.5))
258
- : (1.0 - PI/4.0)) : 0.0;
259
- float blend3 = (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0;
260
-
261
- dst[d1] = mix(dst[d1], blendPix, blend1);
262
- dst[d2] = mix(dst[d2], blendPix, blend2);
263
- dst[d3] = mix(dst[d3], blendPix, blend3);
264
- }
265
-
266
- // Select output pixel based on sub-pixel position
267
- result = mix(mix(dst[0], dst[1], step(0.5, f.x)),
268
- mix(dst[3], dst[2], step(0.5, f.x)), step(0.5, f.y));
269
-
270
- } else if (uScale == 3) {
271
- // 3x output: 9 pixels per source pixel
272
- vec3 dst[9];
273
- for (int i = 0; i < 9; i++) dst[i] = src[0];
122
+ vec3 dst[4];
123
+ dst[0] = src[0]; dst[1] = src[0]; dst[2] = src[0]; dst[3] = src[0];
124
+
125
+ if (IsBlendingNeeded(blendResult)) {
126
+ // Process all 4 corners
127
+ for (int corner = 0; corner < 4; corner++) {
128
+ vec3 k1, k3, k4, k5, k7, k8;
129
+ int blendIdx;
130
+ if (corner == 0) { k1=src[1]; k3=src[3]; k4=src[4]; k5=src[5]; k7=src[7]; k8=src[8]; blendIdx=2; }
131
+ else if (corner == 1) { k1=src[7]; k3=src[1]; k4=src[2]; k5=src[3]; k7=src[5]; k8=src[6]; blendIdx=1; }
132
+ else if (corner == 2) { k1=src[5]; k3=src[7]; k4=src[8]; k5=src[1]; k7=src[3]; k8=src[4]; blendIdx=0; }
133
+ else { k1=src[3]; k3=src[5]; k4=src[6]; k5=src[7]; k7=src[1]; k8=src[2]; blendIdx=3; }
274
134
 
275
- // Process all 4 corners with rotation
276
- for (int rot = 0; rot < 4; rot++) {
277
- ivec4 blend = (rot == 0) ? blendResult :
278
- (rot == 1) ? blendResult.wxyz :
279
- (rot == 2) ? blendResult.zwxy : blendResult.yzwx;
280
-
281
- vec3 k[9];
282
- k[0] = src[0];
283
- k[1] = (rot == 0) ? src[1] : (rot == 1) ? src[7] : (rot == 2) ? src[5] : src[3];
284
- k[2] = (rot == 0) ? src[2] : (rot == 1) ? src[8] : (rot == 2) ? src[6] : src[4];
285
- k[3] = (rot == 0) ? src[3] : (rot == 1) ? src[1] : (rot == 2) ? src[7] : src[5];
286
- k[4] = (rot == 0) ? src[4] : (rot == 1) ? src[2] : (rot == 2) ? src[8] : src[6];
287
- k[5] = (rot == 0) ? src[5] : (rot == 1) ? src[3] : (rot == 2) ? src[1] : src[7];
288
- k[6] = (rot == 0) ? src[6] : (rot == 1) ? src[4] : (rot == 2) ? src[2] : src[8];
289
- k[7] = (rot == 0) ? src[7] : (rot == 1) ? src[5] : (rot == 2) ? src[3] : src[1];
290
- k[8] = (rot == 0) ? src[8] : (rot == 1) ? src[6] : (rot == 2) ? src[4] : src[2];
291
-
292
- float vk0 = reduce(k[0]);
293
- float vk4 = reduce(k[4]);
294
- float vk5 = reduce(k[5]);
295
- float vk7 = reduce(k[7]);
296
- float vk8 = reduce(k[8]);
297
-
298
- float dist_01_04 = distYCbCr(k[1], k[4]);
299
- float dist_03_08 = distYCbCr(k[3], k[8]);
300
- bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
301
- bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
302
- bool needBlend = (blend[2] != BLEND_NONE);
303
- bool doLineBlend = blend[2] >= BLEND_DOMINANT ||
304
- !((blend[1] != BLEND_NONE && !isPixEqual(k[0], k[4])) ||
305
- (blend[3] != BLEND_NONE && !isPixEqual(k[0], k[8])) ||
306
- (isPixEqual(k[4], k[3]) && isPixEqual(k[3], k[2]) &&
307
- isPixEqual(k[2], k[1]) && isPixEqual(k[1], k[8]) && !isPixEqual(k[0], k[2])));
308
-
309
- vec3 blendPix = (distYCbCr(k[0], k[1]) <= distYCbCr(k[0], k[3])) ? k[1] : k[3];
310
-
311
- // 3x specific blend weights
312
- // Output mapping for rot=0: 0|1|2
313
- // 3|4|5
314
- // 6|7|8
315
- // Corner is at position 8
316
-
317
- int idx[4];
318
- if (rot == 0) { idx[0] = 5; idx[1] = 8; idx[2] = 7; idx[3] = 2; }
319
- else if (rot == 1) { idx[0] = 1; idx[1] = 2; idx[2] = 5; idx[3] = 0; }
320
- else if (rot == 2) { idx[0] = 3; idx[1] = 0; idx[2] = 1; idx[3] = 6; }
321
- else { idx[0] = 7; idx[1] = 6; idx[2] = 3; idx[3] = 8; }
322
-
323
- dst[idx[0]] = mix(dst[idx[0]], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0);
324
- dst[idx[1]] = mix(dst[idx[1]], blendPix, needBlend ? (doLineBlend ?
325
- (haveShallowLine ? (haveSteepLine ? 2.0/3.0 : 0.75) : (haveSteepLine ? 0.75 : 0.5)) : 0.3) : 0.0);
326
- dst[idx[2]] = mix(dst[idx[2]], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0);
327
- }
135
+ float vk0 = v[0], vk4 = reduce(k4), vk5 = reduce(k5), vk7 = reduce(k7), vk8 = reduce(k8);
136
+ float dist_01_04 = DistYCbCr(k1, k4);
137
+ float dist_03_08 = DistYCbCr(k3, k8);
138
+ bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
139
+ bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
140
+ bool needBlend = (blendResult[blendIdx] != BLEND_NONE);
141
+ bool doLineBlend = blendResult[blendIdx] >= BLEND_DOMINANT ||
142
+ !((blendResult[(blendIdx+3)%4] != BLEND_NONE && !IsPixEqual(src[0], k4)) ||
143
+ (blendResult[(blendIdx+1)%4] != BLEND_NONE && !IsPixEqual(src[0], k8)) ||
144
+ (IsPixEqual(k4, k3) && IsPixEqual(k3, k8) && !IsPixEqual(src[0], k3)));
328
145
 
329
- // Select output based on 3x3 grid position
330
- float fx = f.x * 3.0;
331
- float fy = f.y * 3.0;
332
- int xi = int(fx);
333
- int yi = int(fy);
334
- int idx = yi * 3 + xi;
146
+ vec3 blendPix = (DistYCbCr(src[0], k1) <= DistYCbCr(src[0], k3)) ? k1 : k3;
147
+ float b1 = (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0;
148
+ float b2 = needBlend ? (doLineBlend ? (haveShallowLine ? (haveSteepLine ? 5.0/6.0 : 0.75) : (haveSteepLine ? 0.75 : 0.5)) : (1.0 - M_PI/4.0)) : 0.0;
149
+ float b3 = (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0;
335
150
 
336
- result = dst[clamp(idx, 0, 8)];
151
+ if (corner == 0) { dst[1] = mix(dst[1], blendPix, b1); dst[2] = mix(dst[2], blendPix, b2); dst[3] = mix(dst[3], blendPix, b3); }
152
+ else if (corner == 1) { dst[0] = mix(dst[0], blendPix, b1); dst[1] = mix(dst[1], blendPix, b2); dst[2] = mix(dst[2], blendPix, b3); }
153
+ else if (corner == 2) { dst[3] = mix(dst[3], blendPix, b1); dst[0] = mix(dst[0], blendPix, b2); dst[1] = mix(dst[1], blendPix, b3); }
154
+ else { dst[2] = mix(dst[2], blendPix, b1); dst[3] = mix(dst[3], blendPix, b2); dst[0] = mix(dst[0], blendPix, b3); }
155
+ }
156
+ }
157
+
158
+ vec2 ff = step(0.5, f);
159
+ vec3 res = mix(mix(dst[0], dst[1], ff.x), mix(dst[3], dst[2], ff.x), ff.y);
160
+ outColor = vec4(res, tex(tc).a);
161
+ }`;
162
+ // 3x xBRZ fragment shader
163
+ const FRAG_3X = COMMON_HEADER + `
164
+ const float one_third = 1.0 / 3.0;
165
+ const float two_third = 2.0 / 3.0;
166
+
167
+ void main() {
168
+ vec2 texelSize = 1.0 / uInputRes;
169
+ vec2 srcPos = vUv * uInputRes;
170
+ vec2 srcPixel = floor(srcPos);
171
+ vec2 f = fract(srcPos);
172
+ vec2 tc = (srcPixel + 0.5) * texelSize;
173
+
174
+ vec3 src[25]; float v[9];
175
+ sampleNeighborhood(tc, texelSize.x, texelSize.y, src, v);
176
+ ivec4 blendResult = detectCorners(src, v);
177
+
178
+ // Output: 06|07|08 / 05|00|01 / 04|03|02
179
+ vec3 dst[9];
180
+ for (int i = 0; i < 9; i++) dst[i] = src[0];
181
+
182
+ if (IsBlendingNeeded(blendResult)) {
183
+ for (int corner = 0; corner < 4; corner++) {
184
+ vec3 k1, k3, k4, k5, k7, k8;
185
+ int blendIdx;
186
+ if (corner == 0) { k1=src[1]; k3=src[3]; k4=src[4]; k5=src[5]; k7=src[7]; k8=src[8]; blendIdx=2; }
187
+ else if (corner == 1) { k1=src[7]; k3=src[1]; k4=src[2]; k5=src[3]; k7=src[5]; k8=src[6]; blendIdx=1; }
188
+ else if (corner == 2) { k1=src[5]; k3=src[7]; k4=src[8]; k5=src[1]; k7=src[3]; k8=src[4]; blendIdx=0; }
189
+ else { k1=src[3]; k3=src[5]; k4=src[6]; k5=src[7]; k7=src[1]; k8=src[2]; blendIdx=3; }
337
190
 
338
- } else {
339
- // 4x, 5x, 6x - use 4x algorithm with interpolation
340
- vec3 dst[16];
341
- for (int i = 0; i < 16; i++) dst[i] = src[0];
191
+ float vk0 = v[0], vk4 = reduce(k4), vk5 = reduce(k5), vk7 = reduce(k7), vk8 = reduce(k8);
192
+ float dist_01_04 = DistYCbCr(k1, k4);
193
+ float dist_03_08 = DistYCbCr(k3, k8);
194
+ bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
195
+ bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
196
+ bool needBlend = (blendResult[blendIdx] != BLEND_NONE);
197
+ bool doLineBlend = blendResult[blendIdx] >= BLEND_DOMINANT ||
198
+ !((blendResult[(blendIdx+3)%4] != BLEND_NONE && !IsPixEqual(src[0], k4)) ||
199
+ (blendResult[(blendIdx+1)%4] != BLEND_NONE && !IsPixEqual(src[0], k8)) ||
200
+ (IsPixEqual(k4, k3) && IsPixEqual(k3, k8) && !IsPixEqual(src[0], k3)));
342
201
 
343
- // Process all 4 corners
344
- for (int rot = 0; rot < 4; rot++) {
345
- ivec4 blend = (rot == 0) ? blendResult :
346
- (rot == 1) ? blendResult.wxyz :
347
- (rot == 2) ? blendResult.zwxy : blendResult.yzwx;
348
-
349
- vec3 k[9];
350
- k[0] = src[0];
351
- k[1] = (rot == 0) ? src[1] : (rot == 1) ? src[7] : (rot == 2) ? src[5] : src[3];
352
- k[2] = (rot == 0) ? src[2] : (rot == 1) ? src[8] : (rot == 2) ? src[6] : src[4];
353
- k[3] = (rot == 0) ? src[3] : (rot == 1) ? src[1] : (rot == 2) ? src[7] : src[5];
354
- k[4] = (rot == 0) ? src[4] : (rot == 1) ? src[2] : (rot == 2) ? src[8] : src[6];
355
- k[5] = (rot == 0) ? src[5] : (rot == 1) ? src[3] : (rot == 2) ? src[1] : src[7];
356
- k[6] = (rot == 0) ? src[6] : (rot == 1) ? src[4] : (rot == 2) ? src[2] : src[8];
357
- k[7] = (rot == 0) ? src[7] : (rot == 1) ? src[5] : (rot == 2) ? src[3] : src[1];
358
- k[8] = (rot == 0) ? src[8] : (rot == 1) ? src[6] : (rot == 2) ? src[4] : src[2];
359
-
360
- float vk0 = reduce(k[0]);
361
- float vk4 = reduce(k[4]);
362
- float vk5 = reduce(k[5]);
363
- float vk7 = reduce(k[7]);
364
- float vk8 = reduce(k[8]);
365
-
366
- float dist_01_04 = distYCbCr(k[1], k[4]);
367
- float dist_03_08 = distYCbCr(k[3], k[8]);
368
- bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
369
- bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
370
- bool needBlend = (blend[2] != BLEND_NONE);
371
- bool doLineBlend = blend[2] >= BLEND_DOMINANT ||
372
- !((blend[1] != BLEND_NONE && !isPixEqual(k[0], k[4])) ||
373
- (blend[3] != BLEND_NONE && !isPixEqual(k[0], k[8])) ||
374
- (isPixEqual(k[4], k[3]) && isPixEqual(k[3], k[2]) &&
375
- isPixEqual(k[2], k[1]) && isPixEqual(k[1], k[8]) && !isPixEqual(k[0], k[2])));
376
-
377
- vec3 blendPix = (distYCbCr(k[0], k[1]) <= distYCbCr(k[0], k[3])) ? k[1] : k[3];
378
-
379
- // 4x layout with rotation
380
- // Output mapping: 06|07|08|09
381
- // 05|00|01|10
382
- // 04|03|02|11
383
- // 15|14|13|12
384
-
385
- const float NOLINEBLEND = 0.08677704501;
386
- const float LINEBLEND_CENTER = 0.6848532563;
387
-
388
- // Apply blends based on rotation
389
- if (rot == 0) {
390
- dst[2] = mix(dst[2], blendPix, (needBlend && doLineBlend) ?
391
- (haveShallowLine ? (haveSteepLine ? 1.0/3.0 : 0.25) : (haveSteepLine ? 0.25 : 0.0)) : 0.0);
392
- dst[9] = mix(dst[9], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0);
393
- dst[10] = mix(dst[10], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.75 : 0.0);
394
- dst[11] = mix(dst[11], blendPix, needBlend ? (doLineBlend ?
395
- (haveSteepLine ? 1.0 : (haveShallowLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
396
- dst[12] = mix(dst[12], blendPix, needBlend ? (doLineBlend ? 1.0 : LINEBLEND_CENTER) : 0.0);
397
- dst[13] = mix(dst[13], blendPix, needBlend ? (doLineBlend ?
398
- (haveShallowLine ? 1.0 : (haveSteepLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
399
- dst[14] = mix(dst[14], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.75 : 0.0);
400
- dst[15] = mix(dst[15], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0);
401
- } else if (rot == 1) {
402
- dst[1] = mix(dst[1], blendPix, (needBlend && doLineBlend) ?
403
- (haveShallowLine ? (haveSteepLine ? 1.0/3.0 : 0.25) : (haveSteepLine ? 0.25 : 0.0)) : 0.0);
404
- dst[6] = mix(dst[6], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0);
405
- dst[7] = mix(dst[7], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.75 : 0.0);
406
- dst[8] = mix(dst[8], blendPix, needBlend ? (doLineBlend ?
407
- (haveSteepLine ? 1.0 : (haveShallowLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
408
- dst[9] = mix(dst[9], blendPix, needBlend ? (doLineBlend ? 1.0 : LINEBLEND_CENTER) : 0.0);
409
- dst[10] = mix(dst[10], blendPix, needBlend ? (doLineBlend ?
410
- (haveShallowLine ? 1.0 : (haveSteepLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
411
- dst[11] = mix(dst[11], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.75 : 0.0);
412
- dst[12] = mix(dst[12], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0);
413
- } else if (rot == 2) {
414
- dst[0] = mix(dst[0], blendPix, (needBlend && doLineBlend) ?
415
- (haveShallowLine ? (haveSteepLine ? 1.0/3.0 : 0.25) : (haveSteepLine ? 0.25 : 0.0)) : 0.0);
416
- dst[15] = mix(dst[15], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0);
417
- dst[4] = mix(dst[4], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.75 : 0.0);
418
- dst[5] = mix(dst[5], blendPix, needBlend ? (doLineBlend ?
419
- (haveSteepLine ? 1.0 : (haveShallowLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
420
- dst[6] = mix(dst[6], blendPix, needBlend ? (doLineBlend ? 1.0 : LINEBLEND_CENTER) : 0.0);
421
- dst[7] = mix(dst[7], blendPix, needBlend ? (doLineBlend ?
422
- (haveShallowLine ? 1.0 : (haveSteepLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
423
- dst[8] = mix(dst[8], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.75 : 0.0);
424
- dst[9] = mix(dst[9], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0);
425
- } else {
426
- dst[3] = mix(dst[3], blendPix, (needBlend && doLineBlend) ?
427
- (haveShallowLine ? (haveSteepLine ? 1.0/3.0 : 0.25) : (haveSteepLine ? 0.25 : 0.0)) : 0.0);
428
- dst[12] = mix(dst[12], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0);
429
- dst[13] = mix(dst[13], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.75 : 0.0);
430
- dst[14] = mix(dst[14], blendPix, needBlend ? (doLineBlend ?
431
- (haveSteepLine ? 1.0 : (haveShallowLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
432
- dst[15] = mix(dst[15], blendPix, needBlend ? (doLineBlend ? 1.0 : LINEBLEND_CENTER) : 0.0);
433
- dst[4] = mix(dst[4], blendPix, needBlend ? (doLineBlend ?
434
- (haveShallowLine ? 1.0 : (haveSteepLine ? 0.75 : 0.5)) : NOLINEBLEND) : 0.0);
435
- dst[5] = mix(dst[5], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.75 : 0.0);
436
- dst[6] = mix(dst[6], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0);
437
- }
438
- }
202
+ vec3 blendPix = (DistYCbCr(src[0], k1) <= DistYCbCr(src[0], k3)) ? k1 : k3;
203
+ float b1 = (needBlend && doLineBlend) ? (haveSteepLine ? 0.75 : (haveShallowLine ? 0.25 : 0.125)) : 0.0;
204
+ float b2 = needBlend ? (doLineBlend ? ((!haveShallowLine && !haveSteepLine) ? 0.875 : 1.0) : 0.4545939598) : 0.0;
205
+ float b3 = (needBlend && doLineBlend) ? (haveShallowLine ? 0.75 : (haveSteepLine ? 0.25 : 0.125)) : 0.0;
206
+ float b4 = (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0;
207
+ float b8 = (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0;
439
208
 
440
- // Map 4x4 to output with bilinear interpolation for 5x, 6x
441
- float scale4 = 4.0;
442
- float fx = f.x * scale4;
443
- float fy = f.y * scale4;
209
+ if (corner == 0) { dst[1]=mix(dst[1],blendPix,b1); dst[2]=mix(dst[2],blendPix,b2); dst[3]=mix(dst[3],blendPix,b3); dst[4]=mix(dst[4],blendPix,b4); dst[8]=mix(dst[8],blendPix,b8); }
210
+ else if (corner == 1) { dst[7]=mix(dst[7],blendPix,b1); dst[8]=mix(dst[8],blendPix,b2); dst[1]=mix(dst[1],blendPix,b3); dst[2]=mix(dst[2],blendPix,b4); dst[6]=mix(dst[6],blendPix,b8); }
211
+ else if (corner == 2) { dst[5]=mix(dst[5],blendPix,b1); dst[6]=mix(dst[6],blendPix,b2); dst[7]=mix(dst[7],blendPix,b3); dst[8]=mix(dst[8],blendPix,b4); dst[4]=mix(dst[4],blendPix,b8); }
212
+ else { dst[3]=mix(dst[3],blendPix,b1); dst[4]=mix(dst[4],blendPix,b2); dst[5]=mix(dst[5],blendPix,b3); dst[6]=mix(dst[6],blendPix,b4); dst[2]=mix(dst[2],blendPix,b8); }
213
+ }
214
+ }
215
+
216
+ vec3 res = mix(mix(dst[6], mix(dst[7], dst[8], step(two_third, f.x)), step(one_third, f.x)),
217
+ mix(mix(dst[5], mix(dst[0], dst[1], step(two_third, f.x)), step(one_third, f.x)),
218
+ mix(dst[4], mix(dst[3], dst[2], step(two_third, f.x)), step(one_third, f.x)), step(two_third, f.y)),
219
+ step(one_third, f.y));
220
+ outColor = vec4(res, tex(tc).a);
221
+ }`;
222
+ // 4x xBRZ fragment shader
223
+ const FRAG_4X = COMMON_HEADER + `
224
+ void main() {
225
+ vec2 texelSize = 1.0 / uInputRes;
226
+ vec2 srcPos = vUv * uInputRes;
227
+ vec2 srcPixel = floor(srcPos);
228
+ vec2 f = fract(srcPos);
229
+ vec2 tc = (srcPixel + 0.5) * texelSize;
230
+
231
+ vec3 src[25]; float v[9];
232
+ sampleNeighborhood(tc, texelSize.x, texelSize.y, src, v);
233
+ ivec4 blendResult = detectCorners(src, v);
234
+
235
+ // Output: 06|07|08|09 / 05|00|01|10 / 04|03|02|11 / 15|14|13|12
236
+ vec3 dst[16];
237
+ for (int i = 0; i < 16; i++) dst[i] = src[0];
238
+
239
+ if (IsBlendingNeeded(blendResult)) {
240
+ for (int corner = 0; corner < 4; corner++) {
241
+ vec3 k1, k3, k4, k5, k7, k8;
242
+ int blendIdx;
243
+ if (corner == 0) { k1=src[1]; k3=src[3]; k4=src[4]; k5=src[5]; k7=src[7]; k8=src[8]; blendIdx=2; }
244
+ else if (corner == 1) { k1=src[7]; k3=src[1]; k4=src[2]; k5=src[3]; k7=src[5]; k8=src[6]; blendIdx=1; }
245
+ else if (corner == 2) { k1=src[5]; k3=src[7]; k4=src[8]; k5=src[1]; k7=src[3]; k8=src[4]; blendIdx=0; }
246
+ else { k1=src[3]; k3=src[5]; k4=src[6]; k5=src[7]; k7=src[1]; k8=src[2]; blendIdx=3; }
444
247
 
445
- // 4x layout: 06|07|08|09
446
- // 05|00|01|10
447
- // 04|03|02|11
448
- // 15|14|13|12
449
- int mapping[16] = int[16](6, 7, 8, 9, 5, 0, 1, 10, 4, 3, 2, 11, 15, 14, 13, 12);
248
+ float vk0 = v[0], vk4 = reduce(k4), vk5 = reduce(k5), vk7 = reduce(k7), vk8 = reduce(k8);
249
+ float dist_01_04 = DistYCbCr(k1, k4);
250
+ float dist_03_08 = DistYCbCr(k3, k8);
251
+ bool haveShallowLine = (uSteepDirectionThreshold * dist_01_04 <= dist_03_08) && (vk0 != vk4) && (vk5 != vk4);
252
+ bool haveSteepLine = (uSteepDirectionThreshold * dist_03_08 <= dist_01_04) && (vk0 != vk8) && (vk7 != vk8);
253
+ bool needBlend = (blendResult[blendIdx] != BLEND_NONE);
254
+ bool doLineBlend = blendResult[blendIdx] >= BLEND_DOMINANT ||
255
+ !((blendResult[(blendIdx+3)%4] != BLEND_NONE && !IsPixEqual(src[0], k4)) ||
256
+ (blendResult[(blendIdx+1)%4] != BLEND_NONE && !IsPixEqual(src[0], k8)) ||
257
+ (IsPixEqual(k4, k3) && IsPixEqual(k3, k8) && !IsPixEqual(src[0], k3)));
450
258
 
451
- // Bilinear interpolation within 4x grid
452
- int xi = clamp(int(fx), 0, 3);
453
- int yi = clamp(int(fy), 0, 3);
454
- float xf = fract(fx);
455
- float yf = fract(fy);
259
+ vec3 blendPix = (DistYCbCr(src[0], k1) <= DistYCbCr(src[0], k3)) ? k1 : k3;
456
260
 
457
- int i00 = mapping[yi * 4 + xi];
458
- int i10 = mapping[yi * 4 + min(xi + 1, 3)];
459
- int i01 = mapping[min(yi + 1, 3) * 4 + xi];
460
- int i11 = mapping[min(yi + 1, 3) * 4 + min(xi + 1, 3)];
261
+ // 4x specific blend weights
262
+ float bCorner = (needBlend && doLineBlend) ? (haveShallowLine ? (haveSteepLine ? 1.0/3.0 : 0.25) : (haveSteepLine ? 0.25 : 0.0)) : 0.0;
263
+ float bSteep1 = (needBlend && doLineBlend && haveSteepLine) ? 0.25 : 0.0;
264
+ float bSteep2 = (needBlend && doLineBlend && haveSteepLine) ? 0.75 : 0.0;
265
+ float bEdge1 = needBlend ? (doLineBlend ? (haveSteepLine ? 1.0 : (haveShallowLine ? 0.75 : 0.5)) : 0.08677704501) : 0.0;
266
+ float bCenter = needBlend ? (doLineBlend ? 1.0 : 0.6848532563) : 0.0;
267
+ float bEdge2 = needBlend ? (doLineBlend ? (haveShallowLine ? 1.0 : (haveSteepLine ? 0.75 : 0.5)) : 0.08677704501) : 0.0;
268
+ float bShallow1 = (needBlend && doLineBlend && haveShallowLine) ? 0.75 : 0.0;
269
+ float bShallow2 = (needBlend && doLineBlend && haveShallowLine) ? 0.25 : 0.0;
461
270
 
462
- result = mix(mix(dst[i00], dst[i10], xf),
463
- mix(dst[i01], dst[i11], xf), yf);
271
+ if (corner == 0) {
272
+ dst[2]=mix(dst[2],blendPix,bCorner); dst[9]=mix(dst[9],blendPix,bSteep1); dst[10]=mix(dst[10],blendPix,bSteep2);
273
+ dst[11]=mix(dst[11],blendPix,bEdge1); dst[12]=mix(dst[12],blendPix,bCenter); dst[13]=mix(dst[13],blendPix,bEdge2);
274
+ dst[14]=mix(dst[14],blendPix,bShallow1); dst[15]=mix(dst[15],blendPix,bShallow2);
275
+ } else if (corner == 1) {
276
+ dst[1]=mix(dst[1],blendPix,bCorner); dst[6]=mix(dst[6],blendPix,bSteep1); dst[7]=mix(dst[7],blendPix,bSteep2);
277
+ dst[8]=mix(dst[8],blendPix,bEdge1); dst[9]=mix(dst[9],blendPix,bCenter); dst[10]=mix(dst[10],blendPix,bEdge2);
278
+ dst[11]=mix(dst[11],blendPix,bShallow1); dst[12]=mix(dst[12],blendPix,bShallow2);
279
+ } else if (corner == 2) {
280
+ dst[0]=mix(dst[0],blendPix,bCorner); dst[15]=mix(dst[15],blendPix,bSteep1); dst[4]=mix(dst[4],blendPix,bSteep2);
281
+ dst[5]=mix(dst[5],blendPix,bEdge1); dst[6]=mix(dst[6],blendPix,bCenter); dst[7]=mix(dst[7],blendPix,bEdge2);
282
+ dst[8]=mix(dst[8],blendPix,bShallow1); dst[9]=mix(dst[9],blendPix,bShallow2);
283
+ } else {
284
+ dst[3]=mix(dst[3],blendPix,bCorner); dst[12]=mix(dst[12],blendPix,bSteep1); dst[13]=mix(dst[13],blendPix,bSteep2);
285
+ dst[14]=mix(dst[14],blendPix,bEdge1); dst[15]=mix(dst[15],blendPix,bCenter); dst[4]=mix(dst[4],blendPix,bEdge2);
286
+ dst[5]=mix(dst[5],blendPix,bShallow1); dst[6]=mix(dst[6],blendPix,bShallow2);
287
+ }
464
288
  }
465
289
  }
466
290
 
467
- outColor = vec4(result, tex(tc).a);
291
+ vec3 res = mix(mix(mix(mix(dst[6], dst[7], step(0.25, f.x)), mix(dst[8], dst[9], step(0.75, f.x)), step(0.5, f.x)),
292
+ mix(mix(dst[5], dst[0], step(0.25, f.x)), mix(dst[1], dst[10], step(0.75, f.x)), step(0.5, f.x)), step(0.25, f.y)),
293
+ mix(mix(mix(dst[4], dst[3], step(0.25, f.x)), mix(dst[2], dst[11], step(0.75, f.x)), step(0.5, f.x)),
294
+ mix(mix(dst[15], dst[14], step(0.25, f.x)), mix(dst[13], dst[12], step(0.75, f.x)), step(0.5, f.x)), step(0.75, f.y)),
295
+ step(0.5, f.y));
296
+ outColor = vec4(res, tex(tc).a);
468
297
  }`;
298
+ // 5x and 6x use 4x with interpolation
299
+ const FRAG_5X = FRAG_4X;
300
+ const FRAG_6X = FRAG_4X;
469
301
  /** xBRZ GPU Renderer */
470
302
  export class XbrzGpuRenderer {
471
303
  gl = null;
472
304
  canvas = null;
473
- program = null;
305
+ programs = new Map();
474
306
  texture = null;
475
- uniforms = {};
307
+ uniforms = new Map();
476
308
  initialized = false;
477
309
  currentCanvasSize = { width: 0, height: 0 };
478
310
  currentTexSize = { width: 0, height: 0 };
479
- /** Create a new xBRZ GPU renderer */
311
+ currentScale = 0;
480
312
  static create() {
481
313
  const renderer = new XbrzGpuRenderer();
482
314
  renderer.init();
483
315
  return renderer;
484
316
  }
485
317
  init() {
486
- if (typeof OffscreenCanvas === 'undefined') {
318
+ if (typeof OffscreenCanvas === 'undefined')
487
319
  throw new Error('OffscreenCanvas not supported');
488
- }
489
320
  this.canvas = new OffscreenCanvas(1, 1);
490
- this.gl = this.canvas.getContext('webgl2', {
491
- alpha: true,
492
- premultipliedAlpha: false,
493
- desynchronized: true,
494
- powerPreference: 'high-performance',
495
- antialias: false,
496
- });
497
- if (!this.gl) {
321
+ this.gl = this.canvas.getContext('webgl2', { alpha: true, premultipliedAlpha: false, desynchronized: true, powerPreference: 'high-performance', antialias: false });
322
+ if (!this.gl)
498
323
  throw new Error('WebGL2 not supported');
499
- }
500
324
  const gl = this.gl;
501
- // Create shaders
502
325
  const vs = this.createShader(gl.VERTEX_SHADER, VERTEX_SHADER);
503
- const fs = this.createShader(gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
504
- // Create program
505
- this.program = gl.createProgram();
506
- gl.attachShader(this.program, vs);
507
- gl.attachShader(this.program, fs);
508
- gl.linkProgram(this.program);
509
- if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
510
- throw new Error('Shader program link failed: ' + gl.getProgramInfoLog(this.program));
326
+ const shaders = { 2: FRAG_2X, 3: FRAG_3X, 4: FRAG_4X, 5: FRAG_5X, 6: FRAG_6X };
327
+ for (const [scale, fragSource] of Object.entries(shaders)) {
328
+ const fs = this.createShader(gl.FRAGMENT_SHADER, fragSource);
329
+ const program = gl.createProgram();
330
+ gl.attachShader(program, vs);
331
+ gl.attachShader(program, fs);
332
+ gl.linkProgram(program);
333
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS))
334
+ throw new Error(`Shader link failed for ${scale}x: ` + gl.getProgramInfoLog(program));
335
+ this.programs.set(Number(scale), program);
336
+ this.uniforms.set(Number(scale), {
337
+ uTex: gl.getUniformLocation(program, 'uTex'),
338
+ uInputRes: gl.getUniformLocation(program, 'uInputRes'),
339
+ uLuminanceWeight: gl.getUniformLocation(program, 'uLuminanceWeight'),
340
+ uEqualColorTolerance: gl.getUniformLocation(program, 'uEqualColorTolerance'),
341
+ uSteepDirectionThreshold: gl.getUniformLocation(program, 'uSteepDirectionThreshold'),
342
+ uDominantDirectionThreshold: gl.getUniformLocation(program, 'uDominantDirectionThreshold'),
343
+ });
511
344
  }
512
- gl.useProgram(this.program);
513
- // Get uniform locations
514
- this.uniforms = {
515
- uTex: gl.getUniformLocation(this.program, 'uTex'),
516
- uInputRes: gl.getUniformLocation(this.program, 'uInputRes'),
517
- uOutputRes: gl.getUniformLocation(this.program, 'uOutputRes'),
518
- uScale: gl.getUniformLocation(this.program, 'uScale'),
519
- uLuminanceWeight: gl.getUniformLocation(this.program, 'uLuminanceWeight'),
520
- uEqualColorTolerance: gl.getUniformLocation(this.program, 'uEqualColorTolerance'),
521
- uSteepDirectionThreshold: gl.getUniformLocation(this.program, 'uSteepDirectionThreshold'),
522
- uDominantDirectionThreshold: gl.getUniformLocation(this.program, 'uDominantDirectionThreshold'),
523
- };
524
- gl.uniform1i(this.uniforms.uTex, 0);
525
- // Setup geometry (fullscreen triangle)
526
345
  const buf = gl.createBuffer();
527
346
  gl.bindBuffer(gl.ARRAY_BUFFER, buf);
528
347
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
529
348
  gl.enableVertexAttribArray(0);
530
349
  gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
531
- // Create texture with NEAREST filtering for pixel art
532
350
  this.texture = gl.createTexture();
533
351
  gl.bindTexture(gl.TEXTURE_2D, this.texture);
534
352
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
@@ -550,15 +368,10 @@ export class XbrzGpuRenderer {
550
368
  }
551
369
  return shader;
552
370
  }
553
- /** Check if renderer is ready */
554
- isReady() {
555
- return this.initialized;
556
- }
557
- /** Render xBRZ effect */
371
+ isReady() { return this.initialized; }
558
372
  render(input, options = {}) {
559
- if (!this.initialized || !this.gl || !this.canvas) {
373
+ if (!this.initialized || !this.gl || !this.canvas)
560
374
  throw new Error('Renderer not initialized');
561
- }
562
375
  const gl = this.gl;
563
376
  const data = input instanceof ImageData ? input.data : input.data;
564
377
  const width = input.width;
@@ -566,22 +379,26 @@ export class XbrzGpuRenderer {
566
379
  const scale = Math.min(6, Math.max(2, options.scale ?? 2));
567
380
  const outWidth = width * scale;
568
381
  const outHeight = height * scale;
569
- // Resize canvas if needed
382
+ const program = this.programs.get(scale);
383
+ const uniforms = this.uniforms.get(scale);
384
+ if (!program || !uniforms)
385
+ throw new Error(`No program for scale ${scale}`);
386
+ if (this.currentScale !== scale) {
387
+ gl.useProgram(program);
388
+ gl.uniform1i(uniforms.uTex, 0);
389
+ this.currentScale = scale;
390
+ }
570
391
  if (this.currentCanvasSize.width !== outWidth || this.currentCanvasSize.height !== outHeight) {
571
392
  this.canvas.width = outWidth;
572
393
  this.canvas.height = outHeight;
573
394
  this.currentCanvasSize = { width: outWidth, height: outHeight };
574
395
  gl.viewport(0, 0, outWidth, outHeight);
575
396
  }
576
- // Update uniforms
577
- gl.uniform2f(this.uniforms.uInputRes, width, height);
578
- gl.uniform2f(this.uniforms.uOutputRes, outWidth, outHeight);
579
- gl.uniform1i(this.uniforms.uScale, scale);
580
- gl.uniform1f(this.uniforms.uLuminanceWeight, options.luminanceWeight ?? 1.0);
581
- gl.uniform1f(this.uniforms.uEqualColorTolerance, (options.equalColorTolerance ?? 30) / 255.0);
582
- gl.uniform1f(this.uniforms.uSteepDirectionThreshold, options.steepDirectionThreshold ?? 2.2);
583
- gl.uniform1f(this.uniforms.uDominantDirectionThreshold, options.dominantDirectionThreshold ?? 3.6);
584
- // Update texture
397
+ gl.uniform2f(uniforms.uInputRes, width, height);
398
+ gl.uniform1f(uniforms.uLuminanceWeight, options.luminanceWeight ?? 1.0);
399
+ gl.uniform1f(uniforms.uEqualColorTolerance, (options.equalColorTolerance ?? 30) / 255.0);
400
+ gl.uniform1f(uniforms.uSteepDirectionThreshold, options.steepDirectionThreshold ?? 2.2);
401
+ gl.uniform1f(uniforms.uDominantDirectionThreshold, options.dominantDirectionThreshold ?? 3.6);
585
402
  gl.bindTexture(gl.TEXTURE_2D, this.texture);
586
403
  if (this.currentTexSize.width !== width || this.currentTexSize.height !== height) {
587
404
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
@@ -590,51 +407,30 @@ export class XbrzGpuRenderer {
590
407
  else {
591
408
  gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);
592
409
  }
593
- // Render
594
410
  gl.clear(gl.COLOR_BUFFER_BIT);
595
411
  gl.drawArrays(gl.TRIANGLES, 0, 3);
596
- // Read pixels
597
412
  const pixels = new Uint8ClampedArray(outWidth * outHeight * 4);
598
413
  gl.readPixels(0, 0, outWidth, outHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
599
- return {
600
- data: pixels,
601
- width: outWidth,
602
- height: outHeight,
603
- };
414
+ return { data: pixels, width: outWidth, height: outHeight };
604
415
  }
605
- /** Dispose resources */
606
416
  dispose() {
607
417
  if (this.gl) {
608
418
  if (this.texture)
609
419
  this.gl.deleteTexture(this.texture);
610
- if (this.program)
611
- this.gl.deleteProgram(this.program);
420
+ for (const program of this.programs.values())
421
+ this.gl.deleteProgram(program);
612
422
  this.gl = null;
613
423
  }
614
424
  this.canvas = null;
425
+ this.programs.clear();
426
+ this.uniforms.clear();
615
427
  this.initialized = false;
616
428
  }
617
429
  }
618
- /** xBRZ presets */
619
430
  export const XBRZ_PRESETS = {
620
431
  default: {},
621
- sharp: {
622
- luminanceWeight: 1.0,
623
- equalColorTolerance: 20,
624
- steepDirectionThreshold: 2.0,
625
- dominantDirectionThreshold: 3.2,
626
- },
627
- smooth: {
628
- luminanceWeight: 1.0,
629
- equalColorTolerance: 40,
630
- steepDirectionThreshold: 2.4,
631
- dominantDirectionThreshold: 4.0,
632
- },
633
- colorful: {
634
- luminanceWeight: 0.5,
635
- equalColorTolerance: 30,
636
- steepDirectionThreshold: 2.2,
637
- dominantDirectionThreshold: 3.6,
638
- },
432
+ sharp: { luminanceWeight: 1.0, equalColorTolerance: 20, steepDirectionThreshold: 2.0, dominantDirectionThreshold: 3.2 },
433
+ smooth: { luminanceWeight: 1.0, equalColorTolerance: 40, steepDirectionThreshold: 2.4, dominantDirectionThreshold: 4.0 },
434
+ colorful: { luminanceWeight: 0.5, equalColorTolerance: 30, steepDirectionThreshold: 2.2, dominantDirectionThreshold: 3.6 },
639
435
  };
640
436
  //# sourceMappingURL=xbrz-gpu.js.map