@pixagram/renderart 1.0.1 → 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/README.md +1 -0
- package/dist/xbrz-gpu.d.ts +2 -6
- package/dist/xbrz-gpu.d.ts.map +1 -1
- package/dist/xbrz-gpu.js +260 -464
- package/dist/xbrz-gpu.js.map +1 -1
- package/package.json +1 -1
- package/src/xbrz-gpu.ts +264 -481
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
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
bool
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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 =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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 =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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 =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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 =
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
185
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
330
|
-
float
|
|
331
|
-
float
|
|
332
|
-
|
|
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
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
463
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
577
|
-
gl.
|
|
578
|
-
gl.
|
|
579
|
-
gl.
|
|
580
|
-
gl.uniform1f(
|
|
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
|
-
|
|
611
|
-
this.gl.deleteProgram(
|
|
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
|
-
|
|
623
|
-
|
|
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
|