@pixagram/renderart 0.4.4 → 1.0.0
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/LICENSE +1 -1
- package/README.md +171 -67
- package/dist/crt-gpu.d.ts +30 -0
- package/dist/crt-gpu.d.ts.map +1 -0
- package/dist/crt-gpu.js +282 -0
- package/dist/crt-gpu.js.map +1 -0
- package/dist/hex-gpu.d.ts +35 -0
- package/dist/hex-gpu.d.ts.map +1 -0
- package/dist/hex-gpu.js +382 -0
- package/dist/hex-gpu.js.map +1 -0
- package/dist/index.d.ts +21 -300
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -963
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +84 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/wasm-wrapper.d.ts +71 -0
- package/dist/wasm-wrapper.d.ts.map +1 -0
- package/dist/wasm-wrapper.js +76 -0
- package/dist/wasm-wrapper.js.map +1 -0
- package/dist/xbrz-gpu.d.ts +34 -0
- package/dist/xbrz-gpu.d.ts.map +1 -0
- package/dist/xbrz-gpu.js +640 -0
- package/dist/xbrz-gpu.js.map +1 -0
- package/package.json +48 -35
- package/src/crt-gpu.ts +313 -0
- package/src/hex-gpu.ts +426 -0
- package/src/index.ts +52 -0
- package/src/types.ts +90 -0
- package/src/wasm/crt.rs +181 -0
- package/src/wasm/hex.rs +324 -0
- package/src/wasm/lib.rs +285 -0
- package/src/wasm/xbrz.rs +262 -0
- package/src/wasm-wrapper.ts +195 -0
- package/src/xbrz-gpu.ts +671 -0
- package/dist/index.d.mts +0 -305
- package/dist/index.mjs +0 -948
- package/dist/index.mjs.map +0 -1
- package/pkg/LICENSE +0 -21
- package/pkg/README.md +0 -117
- package/pkg/renderart_wasm.d.ts +0 -52
- package/pkg/renderart_wasm.js +0 -5
- package/pkg/renderart_wasm_bg.js +0 -283
- package/pkg/renderart_wasm_bg.wasm +0 -0
- package/pkg/renderart_wasm_bg.wasm.d.ts +0 -24
package/dist/index.mjs
DELETED
|
@@ -1,948 +0,0 @@
|
|
|
1
|
-
// src/crt-gpu.ts
|
|
2
|
-
var VERTEX_SHADER = `#version 300 es
|
|
3
|
-
layout(location = 0) in vec2 position;
|
|
4
|
-
out vec2 vUv;
|
|
5
|
-
|
|
6
|
-
void main() {
|
|
7
|
-
vUv = position * 0.5 + 0.5;
|
|
8
|
-
gl_Position = vec4(position, 0.0, 1.0);
|
|
9
|
-
}`;
|
|
10
|
-
var FRAGMENT_SHADER = `#version 300 es
|
|
11
|
-
precision highp float;
|
|
12
|
-
|
|
13
|
-
uniform sampler2D uTex;
|
|
14
|
-
uniform vec2 uRes;
|
|
15
|
-
uniform vec2 uWarp;
|
|
16
|
-
uniform float uScanHardness;
|
|
17
|
-
uniform float uScanOpacity;
|
|
18
|
-
uniform float uMaskOpacity;
|
|
19
|
-
uniform int uEnableWarp;
|
|
20
|
-
uniform int uEnableScanlines;
|
|
21
|
-
uniform int uEnableMask;
|
|
22
|
-
|
|
23
|
-
in vec2 vUv;
|
|
24
|
-
out vec4 outColor;
|
|
25
|
-
|
|
26
|
-
// Gamma 2.0 approximation
|
|
27
|
-
vec3 toLinear(vec3 c) { return c * c; }
|
|
28
|
-
vec3 toSrgb(vec3 c) { return sqrt(c); }
|
|
29
|
-
|
|
30
|
-
vec2 warp(vec2 uv) {
|
|
31
|
-
if (uEnableWarp == 0) return uv;
|
|
32
|
-
vec2 dc = abs(0.5 - uv);
|
|
33
|
-
vec2 dc2 = dc * dc;
|
|
34
|
-
uv.x -= 0.5; uv.x *= 1.0 + (dc2.y * (0.3 * uWarp.x)); uv.x += 0.5;
|
|
35
|
-
uv.y -= 0.5; uv.y *= 1.0 + (dc2.x * (0.4 * uWarp.y)); uv.y += 0.5;
|
|
36
|
-
return uv;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
float scanline(float y, float sourceHeight) {
|
|
40
|
-
if (uEnableScanlines == 0) return 1.0;
|
|
41
|
-
float v = fract(y * sourceHeight);
|
|
42
|
-
float d = abs(v - 0.5);
|
|
43
|
-
float line = exp(d * d * uScanHardness);
|
|
44
|
-
return mix(1.0, line, uScanOpacity);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
vec3 mask(vec2 pos) {
|
|
48
|
-
if (uEnableMask == 0) return vec3(1.0);
|
|
49
|
-
float x = fract(pos.x / 6.0);
|
|
50
|
-
vec3 m = vec3(1.0);
|
|
51
|
-
float step1 = 0.333;
|
|
52
|
-
float step2 = 0.666;
|
|
53
|
-
|
|
54
|
-
m.r = step(0.0, x) - step(step1, x);
|
|
55
|
-
m.g = step(step1, x) - step(step2, x);
|
|
56
|
-
m.b = step(step2, x) - step(1.0, x);
|
|
57
|
-
|
|
58
|
-
return mix(vec3(1.0), m, uMaskOpacity);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
void main() {
|
|
62
|
-
// 1. Geometry
|
|
63
|
-
vec2 uv = warp(vUv);
|
|
64
|
-
|
|
65
|
-
// 2. Bounds Check
|
|
66
|
-
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
|
|
67
|
-
outColor = vec4(0.0);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// 3. Texture Sample
|
|
72
|
-
vec4 texSample = texture(uTex, uv);
|
|
73
|
-
|
|
74
|
-
if (texSample.a == 0.0) {
|
|
75
|
-
outColor = vec4(0.0);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
vec3 linearColor = toLinear(texSample.rgb);
|
|
80
|
-
|
|
81
|
-
// 4. CRT Effects
|
|
82
|
-
ivec2 texSize = textureSize(uTex, 0);
|
|
83
|
-
|
|
84
|
-
// Smart Bloom
|
|
85
|
-
float luma = dot(linearColor, vec3(0.299, 0.587, 0.114));
|
|
86
|
-
float bloom = luma * 0.7;
|
|
87
|
-
|
|
88
|
-
// Calculate patterns
|
|
89
|
-
float scan = scanline(uv.y, float(texSize.y));
|
|
90
|
-
vec3 m = mask(gl_FragCoord.xy);
|
|
91
|
-
|
|
92
|
-
// Apply effects
|
|
93
|
-
vec3 effects = m * scan;
|
|
94
|
-
vec3 finalRGB = linearColor * mix(effects, vec3(1.0), bloom);
|
|
95
|
-
|
|
96
|
-
// 5. Output
|
|
97
|
-
outColor = vec4(toSrgb(finalRGB), texSample.a);
|
|
98
|
-
}`;
|
|
99
|
-
var CrtGpuRenderer = class _CrtGpuRenderer {
|
|
100
|
-
constructor() {
|
|
101
|
-
this.gl = null;
|
|
102
|
-
this.canvas = null;
|
|
103
|
-
this.program = null;
|
|
104
|
-
this.texture = null;
|
|
105
|
-
this.uniforms = {};
|
|
106
|
-
this.initialized = false;
|
|
107
|
-
this.currentCanvasSize = { width: 0, height: 0 };
|
|
108
|
-
this.currentTexSize = { width: 0, height: 0 };
|
|
109
|
-
}
|
|
110
|
-
/** Create a new CRT GPU renderer */
|
|
111
|
-
static create() {
|
|
112
|
-
const renderer = new _CrtGpuRenderer();
|
|
113
|
-
renderer.init();
|
|
114
|
-
return renderer;
|
|
115
|
-
}
|
|
116
|
-
init() {
|
|
117
|
-
if (typeof OffscreenCanvas === "undefined") {
|
|
118
|
-
throw new Error("OffscreenCanvas not supported");
|
|
119
|
-
}
|
|
120
|
-
this.canvas = new OffscreenCanvas(1, 1);
|
|
121
|
-
this.gl = this.canvas.getContext("webgl2", {
|
|
122
|
-
alpha: true,
|
|
123
|
-
premultipliedAlpha: false,
|
|
124
|
-
desynchronized: true,
|
|
125
|
-
powerPreference: "high-performance",
|
|
126
|
-
antialias: false
|
|
127
|
-
});
|
|
128
|
-
if (!this.gl) {
|
|
129
|
-
throw new Error("WebGL2 not supported");
|
|
130
|
-
}
|
|
131
|
-
const gl = this.gl;
|
|
132
|
-
const vs = this.createShader(gl.VERTEX_SHADER, VERTEX_SHADER);
|
|
133
|
-
const fs = this.createShader(gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
|
|
134
|
-
this.program = gl.createProgram();
|
|
135
|
-
gl.attachShader(this.program, vs);
|
|
136
|
-
gl.attachShader(this.program, fs);
|
|
137
|
-
gl.linkProgram(this.program);
|
|
138
|
-
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
|
|
139
|
-
throw new Error("Shader program link failed: " + gl.getProgramInfoLog(this.program));
|
|
140
|
-
}
|
|
141
|
-
gl.useProgram(this.program);
|
|
142
|
-
this.uniforms = {
|
|
143
|
-
uTex: gl.getUniformLocation(this.program, "uTex"),
|
|
144
|
-
uRes: gl.getUniformLocation(this.program, "uRes"),
|
|
145
|
-
uWarp: gl.getUniformLocation(this.program, "uWarp"),
|
|
146
|
-
uScanHardness: gl.getUniformLocation(this.program, "uScanHardness"),
|
|
147
|
-
uScanOpacity: gl.getUniformLocation(this.program, "uScanOpacity"),
|
|
148
|
-
uMaskOpacity: gl.getUniformLocation(this.program, "uMaskOpacity"),
|
|
149
|
-
uEnableWarp: gl.getUniformLocation(this.program, "uEnableWarp"),
|
|
150
|
-
uEnableScanlines: gl.getUniformLocation(this.program, "uEnableScanlines"),
|
|
151
|
-
uEnableMask: gl.getUniformLocation(this.program, "uEnableMask")
|
|
152
|
-
};
|
|
153
|
-
gl.uniform1i(this.uniforms.uTex, 0);
|
|
154
|
-
const buf = gl.createBuffer();
|
|
155
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
|
156
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
|
|
157
|
-
gl.enableVertexAttribArray(0);
|
|
158
|
-
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
159
|
-
this.texture = gl.createTexture();
|
|
160
|
-
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
161
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
162
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
163
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
164
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
165
|
-
gl.clearColor(0, 0, 0, 0);
|
|
166
|
-
this.initialized = true;
|
|
167
|
-
}
|
|
168
|
-
createShader(type, source) {
|
|
169
|
-
const gl = this.gl;
|
|
170
|
-
const shader = gl.createShader(type);
|
|
171
|
-
gl.shaderSource(shader, source);
|
|
172
|
-
gl.compileShader(shader);
|
|
173
|
-
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
174
|
-
const info = gl.getShaderInfoLog(shader);
|
|
175
|
-
gl.deleteShader(shader);
|
|
176
|
-
throw new Error("Shader compile failed: " + info);
|
|
177
|
-
}
|
|
178
|
-
return shader;
|
|
179
|
-
}
|
|
180
|
-
/** Check if renderer is ready */
|
|
181
|
-
isReady() {
|
|
182
|
-
return this.initialized;
|
|
183
|
-
}
|
|
184
|
-
/** Render CRT effect */
|
|
185
|
-
render(input, options = {}) {
|
|
186
|
-
if (!this.initialized || !this.gl || !this.canvas) {
|
|
187
|
-
throw new Error("Renderer not initialized");
|
|
188
|
-
}
|
|
189
|
-
const gl = this.gl;
|
|
190
|
-
const data = input instanceof ImageData ? input.data : input.data;
|
|
191
|
-
const width = input.width;
|
|
192
|
-
const height = input.height;
|
|
193
|
-
const scale = Math.min(32, Math.max(2, options.scale ?? 3));
|
|
194
|
-
const outWidth = width * scale;
|
|
195
|
-
const outHeight = height * scale;
|
|
196
|
-
if (this.currentCanvasSize.width !== outWidth || this.currentCanvasSize.height !== outHeight) {
|
|
197
|
-
this.canvas.width = outWidth;
|
|
198
|
-
this.canvas.height = outHeight;
|
|
199
|
-
this.currentCanvasSize = { width: outWidth, height: outHeight };
|
|
200
|
-
gl.viewport(0, 0, outWidth, outHeight);
|
|
201
|
-
}
|
|
202
|
-
gl.uniform2f(this.uniforms.uRes, outWidth, outHeight);
|
|
203
|
-
gl.uniform2f(this.uniforms.uWarp, options.warpX ?? 0.015, options.warpY ?? 0.02);
|
|
204
|
-
gl.uniform1f(this.uniforms.uScanHardness, options.scanHardness ?? -4);
|
|
205
|
-
gl.uniform1f(this.uniforms.uScanOpacity, options.scanOpacity ?? 0.5);
|
|
206
|
-
gl.uniform1f(this.uniforms.uMaskOpacity, options.maskOpacity ?? 0.3);
|
|
207
|
-
gl.uniform1i(this.uniforms.uEnableWarp, options.enableWarp !== false ? 1 : 0);
|
|
208
|
-
gl.uniform1i(this.uniforms.uEnableScanlines, options.enableScanlines !== false ? 1 : 0);
|
|
209
|
-
gl.uniform1i(this.uniforms.uEnableMask, options.enableMask !== false ? 1 : 0);
|
|
210
|
-
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
211
|
-
if (this.currentTexSize.width !== width || this.currentTexSize.height !== height) {
|
|
212
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
|
213
|
-
this.currentTexSize = { width, height };
|
|
214
|
-
} else {
|
|
215
|
-
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
|
216
|
-
}
|
|
217
|
-
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
218
|
-
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
219
|
-
const pixels = new Uint8ClampedArray(outWidth * outHeight * 4);
|
|
220
|
-
gl.readPixels(0, 0, outWidth, outHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
221
|
-
return {
|
|
222
|
-
data: pixels,
|
|
223
|
-
width: outWidth,
|
|
224
|
-
height: outHeight
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
/** Dispose resources */
|
|
228
|
-
dispose() {
|
|
229
|
-
if (this.gl) {
|
|
230
|
-
if (this.texture) this.gl.deleteTexture(this.texture);
|
|
231
|
-
if (this.program) this.gl.deleteProgram(this.program);
|
|
232
|
-
this.gl = null;
|
|
233
|
-
}
|
|
234
|
-
this.canvas = null;
|
|
235
|
-
this.initialized = false;
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
var CRT_PRESETS = {
|
|
239
|
-
default: {},
|
|
240
|
-
authentic: {
|
|
241
|
-
warpX: 0.02,
|
|
242
|
-
warpY: 0.025,
|
|
243
|
-
scanHardness: -6,
|
|
244
|
-
scanOpacity: 0.6,
|
|
245
|
-
maskOpacity: 0.4
|
|
246
|
-
},
|
|
247
|
-
subtle: {
|
|
248
|
-
warpX: 8e-3,
|
|
249
|
-
warpY: 0.01,
|
|
250
|
-
scanHardness: -3,
|
|
251
|
-
scanOpacity: 0.3,
|
|
252
|
-
maskOpacity: 0.15
|
|
253
|
-
},
|
|
254
|
-
flat: {
|
|
255
|
-
warpX: 0,
|
|
256
|
-
warpY: 0,
|
|
257
|
-
enableWarp: false,
|
|
258
|
-
scanHardness: -4,
|
|
259
|
-
scanOpacity: 0.5,
|
|
260
|
-
maskOpacity: 0.3
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
// src/hex-gpu.ts
|
|
265
|
-
var VERTEX_SHADER2 = `#version 300 es
|
|
266
|
-
layout(location = 0) in vec2 position;
|
|
267
|
-
out vec2 vUv;
|
|
268
|
-
|
|
269
|
-
void main() {
|
|
270
|
-
vUv = position * 0.5 + 0.5;
|
|
271
|
-
gl_Position = vec4(position, 0.0, 1.0);
|
|
272
|
-
}`;
|
|
273
|
-
var FRAGMENT_SHADER2 = `#version 300 es
|
|
274
|
-
precision highp float;
|
|
275
|
-
|
|
276
|
-
uniform sampler2D uTex;
|
|
277
|
-
uniform vec2 uOutputRes;
|
|
278
|
-
uniform vec2 uInputRes;
|
|
279
|
-
uniform float uScale;
|
|
280
|
-
uniform int uOrientation; // 0 = flat-top, 1 = pointy-top
|
|
281
|
-
uniform int uDrawBorders;
|
|
282
|
-
uniform vec4 uBorderColor;
|
|
283
|
-
uniform float uBorderThickness;
|
|
284
|
-
uniform vec4 uBackgroundColor;
|
|
285
|
-
|
|
286
|
-
in vec2 vUv;
|
|
287
|
-
out vec4 outColor;
|
|
288
|
-
|
|
289
|
-
const float SQRT3 = 1.732050808;
|
|
290
|
-
const float INV_SQRT3 = 0.577350269;
|
|
291
|
-
|
|
292
|
-
// Convert output pixel to hex coordinate (flat-top)
|
|
293
|
-
vec2 pixelToHexFlat(vec2 pos, float scale) {
|
|
294
|
-
float hSpacing = scale * 1.5;
|
|
295
|
-
float vSpacing = scale * SQRT3;
|
|
296
|
-
|
|
297
|
-
float col = pos.x / hSpacing;
|
|
298
|
-
int colInt = int(floor(col));
|
|
299
|
-
|
|
300
|
-
// Check if odd column (offset)
|
|
301
|
-
bool isOffset = (colInt & 1) == 1;
|
|
302
|
-
float yOffset = isOffset ? vSpacing * 0.5 : 0.0;
|
|
303
|
-
|
|
304
|
-
float row = (pos.y - yOffset) / vSpacing;
|
|
305
|
-
int rowInt = int(floor(row));
|
|
306
|
-
|
|
307
|
-
// Refine with corner detection
|
|
308
|
-
float cellX = pos.x - float(colInt) * hSpacing;
|
|
309
|
-
float cellY = pos.y - float(rowInt) * vSpacing - yOffset;
|
|
310
|
-
|
|
311
|
-
float quarterW = scale * 0.5;
|
|
312
|
-
float halfH = vSpacing * 0.5;
|
|
313
|
-
|
|
314
|
-
if (cellX < quarterW) {
|
|
315
|
-
float distFromCenter = abs(cellY - halfH);
|
|
316
|
-
float edgeX = distFromCenter * INV_SQRT3;
|
|
317
|
-
|
|
318
|
-
if (cellX < quarterW - edgeX) {
|
|
319
|
-
if (cellY < halfH) {
|
|
320
|
-
rowInt -= 1;
|
|
321
|
-
}
|
|
322
|
-
colInt -= 1;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return vec2(float(colInt), float(rowInt));
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Convert output pixel to hex coordinate (pointy-top)
|
|
330
|
-
vec2 pixelToHexPointy(vec2 pos, float scale) {
|
|
331
|
-
float hSpacing = scale * SQRT3;
|
|
332
|
-
float vSpacing = scale * 1.5;
|
|
333
|
-
|
|
334
|
-
float row = pos.y / vSpacing;
|
|
335
|
-
int rowInt = int(floor(row));
|
|
336
|
-
|
|
337
|
-
// Check if odd row (offset)
|
|
338
|
-
bool isOffset = (rowInt & 1) == 1;
|
|
339
|
-
float xOffset = isOffset ? hSpacing * 0.5 : 0.0;
|
|
340
|
-
|
|
341
|
-
float col = (pos.x - xOffset) / hSpacing;
|
|
342
|
-
int colInt = int(floor(col));
|
|
343
|
-
|
|
344
|
-
// Refine with corner detection
|
|
345
|
-
float cellX = pos.x - float(colInt) * hSpacing - xOffset;
|
|
346
|
-
float cellY = pos.y - float(rowInt) * vSpacing;
|
|
347
|
-
|
|
348
|
-
float halfW = hSpacing * 0.5;
|
|
349
|
-
float quarterH = scale * 0.5;
|
|
350
|
-
|
|
351
|
-
if (cellY < quarterH) {
|
|
352
|
-
float distFromCenter = abs(cellX - halfW);
|
|
353
|
-
float edgeY = distFromCenter * SQRT3;
|
|
354
|
-
|
|
355
|
-
if (cellY < edgeY) {
|
|
356
|
-
if (cellX < halfW && isOffset) {
|
|
357
|
-
colInt -= 1;
|
|
358
|
-
}
|
|
359
|
-
rowInt -= 1;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return vec2(float(colInt), float(rowInt));
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Check if pixel is on hex border
|
|
367
|
-
bool isBorderPixel(vec2 pos, float scale, int orientation, float thickness) {
|
|
368
|
-
for (float dy = -thickness; dy <= thickness; dy += 1.0) {
|
|
369
|
-
for (float dx = -thickness; dx <= thickness; dx += 1.0) {
|
|
370
|
-
if (dx == 0.0 && dy == 0.0) continue;
|
|
371
|
-
|
|
372
|
-
vec2 h1 = orientation == 0
|
|
373
|
-
? pixelToHexFlat(pos, scale)
|
|
374
|
-
: pixelToHexPointy(pos, scale);
|
|
375
|
-
vec2 h2 = orientation == 0
|
|
376
|
-
? pixelToHexFlat(pos + vec2(dx, dy), scale)
|
|
377
|
-
: pixelToHexPointy(pos + vec2(dx, dy), scale);
|
|
378
|
-
|
|
379
|
-
if (h1 != h2) return true;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
return false;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
void main() {
|
|
386
|
-
vec2 pixelPos = vUv * uOutputRes;
|
|
387
|
-
|
|
388
|
-
// Get hex coordinate
|
|
389
|
-
vec2 hexCoord = uOrientation == 0
|
|
390
|
-
? pixelToHexFlat(pixelPos, uScale)
|
|
391
|
-
: pixelToHexPointy(pixelPos, uScale);
|
|
392
|
-
|
|
393
|
-
// Check bounds
|
|
394
|
-
if (hexCoord.x < 0.0 || hexCoord.y < 0.0 ||
|
|
395
|
-
hexCoord.x >= uInputRes.x || hexCoord.y >= uInputRes.y) {
|
|
396
|
-
outColor = uBackgroundColor;
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Check for border
|
|
401
|
-
if (uDrawBorders == 1 && uBorderThickness > 0.0) {
|
|
402
|
-
if (isBorderPixel(pixelPos, uScale, uOrientation, uBorderThickness)) {
|
|
403
|
-
outColor = uBorderColor;
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Sample source pixel (add 0.5 for pixel center)
|
|
409
|
-
vec2 texCoord = (hexCoord + 0.5) / uInputRes;
|
|
410
|
-
outColor = texture(uTex, texCoord);
|
|
411
|
-
}`;
|
|
412
|
-
function parseColor(color, defaultColor) {
|
|
413
|
-
if (color === void 0) return defaultColor;
|
|
414
|
-
if (typeof color === "number") {
|
|
415
|
-
return [
|
|
416
|
-
(color >> 24 & 255) / 255,
|
|
417
|
-
(color >> 16 & 255) / 255,
|
|
418
|
-
(color >> 8 & 255) / 255,
|
|
419
|
-
(color & 255) / 255
|
|
420
|
-
];
|
|
421
|
-
}
|
|
422
|
-
if (color === "transparent") return [0, 0, 0, 0];
|
|
423
|
-
if (color.startsWith("#")) {
|
|
424
|
-
const hex = color.slice(1);
|
|
425
|
-
if (hex.length === 6) {
|
|
426
|
-
return [
|
|
427
|
-
parseInt(hex.slice(0, 2), 16) / 255,
|
|
428
|
-
parseInt(hex.slice(2, 4), 16) / 255,
|
|
429
|
-
parseInt(hex.slice(4, 6), 16) / 255,
|
|
430
|
-
1
|
|
431
|
-
];
|
|
432
|
-
}
|
|
433
|
-
if (hex.length === 8) {
|
|
434
|
-
return [
|
|
435
|
-
parseInt(hex.slice(0, 2), 16) / 255,
|
|
436
|
-
parseInt(hex.slice(2, 4), 16) / 255,
|
|
437
|
-
parseInt(hex.slice(4, 6), 16) / 255,
|
|
438
|
-
parseInt(hex.slice(6, 8), 16) / 255
|
|
439
|
-
];
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
return defaultColor;
|
|
443
|
-
}
|
|
444
|
-
function hexGetDimensions(srcWidth, srcHeight, scale, orientation = "flat-top") {
|
|
445
|
-
const SQRT3 = 1.732050808;
|
|
446
|
-
if (orientation === "flat-top") {
|
|
447
|
-
const hSpacing = scale * 1.5;
|
|
448
|
-
const vSpacing = scale * SQRT3;
|
|
449
|
-
const cellWidth = scale * 2;
|
|
450
|
-
const cellHeight = scale * SQRT3;
|
|
451
|
-
return {
|
|
452
|
-
width: Math.ceil(srcWidth * hSpacing + cellWidth),
|
|
453
|
-
height: Math.ceil(srcHeight * vSpacing + cellHeight)
|
|
454
|
-
};
|
|
455
|
-
} else {
|
|
456
|
-
const hSpacing = scale * SQRT3;
|
|
457
|
-
const vSpacing = scale * 1.5;
|
|
458
|
-
const cellWidth = scale * SQRT3;
|
|
459
|
-
const cellHeight = scale * 2;
|
|
460
|
-
return {
|
|
461
|
-
width: Math.ceil(srcWidth * hSpacing + cellWidth),
|
|
462
|
-
height: Math.ceil(srcHeight * vSpacing + cellHeight)
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
var HexGpuRenderer = class _HexGpuRenderer {
|
|
467
|
-
constructor() {
|
|
468
|
-
this.gl = null;
|
|
469
|
-
this.canvas = null;
|
|
470
|
-
this.program = null;
|
|
471
|
-
this.texture = null;
|
|
472
|
-
this.uniforms = {};
|
|
473
|
-
this.initialized = false;
|
|
474
|
-
this.currentCanvasSize = { width: 0, height: 0 };
|
|
475
|
-
this.currentTexSize = { width: 0, height: 0 };
|
|
476
|
-
}
|
|
477
|
-
/** Create a new HEX GPU renderer */
|
|
478
|
-
static create() {
|
|
479
|
-
const renderer = new _HexGpuRenderer();
|
|
480
|
-
renderer.init();
|
|
481
|
-
return renderer;
|
|
482
|
-
}
|
|
483
|
-
init() {
|
|
484
|
-
if (typeof OffscreenCanvas === "undefined") {
|
|
485
|
-
throw new Error("OffscreenCanvas not supported");
|
|
486
|
-
}
|
|
487
|
-
this.canvas = new OffscreenCanvas(1, 1);
|
|
488
|
-
this.gl = this.canvas.getContext("webgl2", {
|
|
489
|
-
alpha: true,
|
|
490
|
-
premultipliedAlpha: false,
|
|
491
|
-
desynchronized: true,
|
|
492
|
-
powerPreference: "high-performance",
|
|
493
|
-
antialias: false
|
|
494
|
-
});
|
|
495
|
-
if (!this.gl) {
|
|
496
|
-
throw new Error("WebGL2 not supported");
|
|
497
|
-
}
|
|
498
|
-
const gl = this.gl;
|
|
499
|
-
const vs = this.createShader(gl.VERTEX_SHADER, VERTEX_SHADER2);
|
|
500
|
-
const fs = this.createShader(gl.FRAGMENT_SHADER, FRAGMENT_SHADER2);
|
|
501
|
-
this.program = gl.createProgram();
|
|
502
|
-
gl.attachShader(this.program, vs);
|
|
503
|
-
gl.attachShader(this.program, fs);
|
|
504
|
-
gl.linkProgram(this.program);
|
|
505
|
-
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
|
|
506
|
-
throw new Error("Shader program link failed: " + gl.getProgramInfoLog(this.program));
|
|
507
|
-
}
|
|
508
|
-
gl.useProgram(this.program);
|
|
509
|
-
this.uniforms = {
|
|
510
|
-
uTex: gl.getUniformLocation(this.program, "uTex"),
|
|
511
|
-
uOutputRes: gl.getUniformLocation(this.program, "uOutputRes"),
|
|
512
|
-
uInputRes: gl.getUniformLocation(this.program, "uInputRes"),
|
|
513
|
-
uScale: gl.getUniformLocation(this.program, "uScale"),
|
|
514
|
-
uOrientation: gl.getUniformLocation(this.program, "uOrientation"),
|
|
515
|
-
uDrawBorders: gl.getUniformLocation(this.program, "uDrawBorders"),
|
|
516
|
-
uBorderColor: gl.getUniformLocation(this.program, "uBorderColor"),
|
|
517
|
-
uBorderThickness: gl.getUniformLocation(this.program, "uBorderThickness"),
|
|
518
|
-
uBackgroundColor: gl.getUniformLocation(this.program, "uBackgroundColor")
|
|
519
|
-
};
|
|
520
|
-
gl.uniform1i(this.uniforms.uTex, 0);
|
|
521
|
-
const buf = gl.createBuffer();
|
|
522
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
|
|
523
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
|
|
524
|
-
gl.enableVertexAttribArray(0);
|
|
525
|
-
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
|
|
526
|
-
this.texture = gl.createTexture();
|
|
527
|
-
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
528
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
529
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
530
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
531
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
532
|
-
gl.clearColor(0, 0, 0, 0);
|
|
533
|
-
this.initialized = true;
|
|
534
|
-
}
|
|
535
|
-
createShader(type, source) {
|
|
536
|
-
const gl = this.gl;
|
|
537
|
-
const shader = gl.createShader(type);
|
|
538
|
-
gl.shaderSource(shader, source);
|
|
539
|
-
gl.compileShader(shader);
|
|
540
|
-
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
541
|
-
const info = gl.getShaderInfoLog(shader);
|
|
542
|
-
gl.deleteShader(shader);
|
|
543
|
-
throw new Error("Shader compile failed: " + info);
|
|
544
|
-
}
|
|
545
|
-
return shader;
|
|
546
|
-
}
|
|
547
|
-
/** Check if renderer is ready */
|
|
548
|
-
isReady() {
|
|
549
|
-
return this.initialized;
|
|
550
|
-
}
|
|
551
|
-
/** Render hexagonal effect */
|
|
552
|
-
render(input, options = {}) {
|
|
553
|
-
if (!this.initialized || !this.gl || !this.canvas) {
|
|
554
|
-
throw new Error("Renderer not initialized");
|
|
555
|
-
}
|
|
556
|
-
const gl = this.gl;
|
|
557
|
-
const data = input instanceof ImageData ? input.data : input.data;
|
|
558
|
-
const width = input.width;
|
|
559
|
-
const height = input.height;
|
|
560
|
-
const scale = Math.min(32, Math.max(2, options.scale ?? 16));
|
|
561
|
-
const orientation = options.orientation ?? "flat-top";
|
|
562
|
-
const { width: outWidth, height: outHeight } = hexGetDimensions(width, height, scale, orientation);
|
|
563
|
-
if (this.currentCanvasSize.width !== outWidth || this.currentCanvasSize.height !== outHeight) {
|
|
564
|
-
this.canvas.width = outWidth;
|
|
565
|
-
this.canvas.height = outHeight;
|
|
566
|
-
this.currentCanvasSize = { width: outWidth, height: outHeight };
|
|
567
|
-
gl.viewport(0, 0, outWidth, outHeight);
|
|
568
|
-
}
|
|
569
|
-
gl.uniform2f(this.uniforms.uOutputRes, outWidth, outHeight);
|
|
570
|
-
gl.uniform2f(this.uniforms.uInputRes, width, height);
|
|
571
|
-
gl.uniform1f(this.uniforms.uScale, scale);
|
|
572
|
-
gl.uniform1i(this.uniforms.uOrientation, orientation === "flat-top" ? 0 : 1);
|
|
573
|
-
gl.uniform1i(this.uniforms.uDrawBorders, options.drawBorders ? 1 : 0);
|
|
574
|
-
gl.uniform1f(this.uniforms.uBorderThickness, options.borderThickness ?? 1);
|
|
575
|
-
const borderColor = parseColor(options.borderColor, [0.16, 0.16, 0.16, 1]);
|
|
576
|
-
gl.uniform4f(this.uniforms.uBorderColor, ...borderColor);
|
|
577
|
-
const bgColor = parseColor(options.backgroundColor, [0, 0, 0, 0]);
|
|
578
|
-
gl.uniform4f(this.uniforms.uBackgroundColor, ...bgColor);
|
|
579
|
-
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
580
|
-
if (this.currentTexSize.width !== width || this.currentTexSize.height !== height) {
|
|
581
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
|
582
|
-
this.currentTexSize = { width, height };
|
|
583
|
-
} else {
|
|
584
|
-
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
|
585
|
-
}
|
|
586
|
-
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
587
|
-
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
588
|
-
const pixels = new Uint8ClampedArray(outWidth * outHeight * 4);
|
|
589
|
-
gl.readPixels(0, 0, outWidth, outHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
590
|
-
return {
|
|
591
|
-
data: pixels,
|
|
592
|
-
width: outWidth,
|
|
593
|
-
height: outHeight
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
/** Dispose resources */
|
|
597
|
-
dispose() {
|
|
598
|
-
if (this.gl) {
|
|
599
|
-
if (this.texture) this.gl.deleteTexture(this.texture);
|
|
600
|
-
if (this.program) this.gl.deleteProgram(this.program);
|
|
601
|
-
this.gl = null;
|
|
602
|
-
}
|
|
603
|
-
this.canvas = null;
|
|
604
|
-
this.initialized = false;
|
|
605
|
-
}
|
|
606
|
-
};
|
|
607
|
-
var HEX_PRESETS = {
|
|
608
|
-
default: {},
|
|
609
|
-
bordered: {
|
|
610
|
-
drawBorders: true,
|
|
611
|
-
borderColor: "#282828",
|
|
612
|
-
borderThickness: 1
|
|
613
|
-
},
|
|
614
|
-
pointy: {
|
|
615
|
-
orientation: "pointy-top",
|
|
616
|
-
drawBorders: false
|
|
617
|
-
}
|
|
618
|
-
};
|
|
619
|
-
|
|
620
|
-
// src/wasm-loader.ts
|
|
621
|
-
var wasm = null;
|
|
622
|
-
var wasmReady = false;
|
|
623
|
-
function initWasm(wasmModule) {
|
|
624
|
-
wasm = wasmModule;
|
|
625
|
-
wasmReady = true;
|
|
626
|
-
}
|
|
627
|
-
function isWasmLoaded() {
|
|
628
|
-
return wasmReady;
|
|
629
|
-
}
|
|
630
|
-
function getOutputData(result) {
|
|
631
|
-
const memory = wasm.get_memory();
|
|
632
|
-
const buffer = new Uint8Array(memory.buffer, result.ptr, result.len);
|
|
633
|
-
return new Uint8ClampedArray(buffer.slice());
|
|
634
|
-
}
|
|
635
|
-
function toUint8Array(input) {
|
|
636
|
-
const data = input instanceof ImageData ? input.data : input.data;
|
|
637
|
-
return data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
638
|
-
}
|
|
639
|
-
function ensureWasm() {
|
|
640
|
-
if (!wasmReady || !wasm) {
|
|
641
|
-
throw new Error("WASM not initialized. Call initWasm() first.");
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
var CrtCpuRenderer = class _CrtCpuRenderer {
|
|
645
|
-
/** Create renderer (WASM must be initialized first) */
|
|
646
|
-
static create() {
|
|
647
|
-
ensureWasm();
|
|
648
|
-
return new _CrtCpuRenderer();
|
|
649
|
-
}
|
|
650
|
-
/** Check if renderer is ready */
|
|
651
|
-
isReady() {
|
|
652
|
-
return wasmReady;
|
|
653
|
-
}
|
|
654
|
-
/** Render CRT effect */
|
|
655
|
-
render(input, options = {}) {
|
|
656
|
-
ensureWasm();
|
|
657
|
-
const data = toUint8Array(input);
|
|
658
|
-
const width = input.width;
|
|
659
|
-
const height = input.height;
|
|
660
|
-
const scale = Math.min(32, Math.max(2, options.scale ?? 3));
|
|
661
|
-
const result = wasm.crt_upscale_config(
|
|
662
|
-
data,
|
|
663
|
-
width,
|
|
664
|
-
height,
|
|
665
|
-
scale,
|
|
666
|
-
options.warpX ?? 0.015,
|
|
667
|
-
options.warpY ?? 0.02,
|
|
668
|
-
options.scanHardness ?? -4,
|
|
669
|
-
options.scanOpacity ?? 0.5,
|
|
670
|
-
options.maskOpacity ?? 0.3,
|
|
671
|
-
options.enableWarp !== false,
|
|
672
|
-
options.enableScanlines !== false,
|
|
673
|
-
options.enableMask !== false
|
|
674
|
-
);
|
|
675
|
-
return {
|
|
676
|
-
data: getOutputData(result),
|
|
677
|
-
width: result.width,
|
|
678
|
-
height: result.height
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
dispose() {
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
function parseColorToU32(color, defaultVal) {
|
|
685
|
-
if (color === void 0) return defaultVal;
|
|
686
|
-
if (typeof color === "number") return color;
|
|
687
|
-
if (color === "transparent") return 0;
|
|
688
|
-
if (color.startsWith("#")) {
|
|
689
|
-
const hex = color.slice(1);
|
|
690
|
-
if (hex.length === 6) return parseInt(hex, 16) << 8 | 255;
|
|
691
|
-
if (hex.length === 8) return parseInt(hex, 16);
|
|
692
|
-
}
|
|
693
|
-
return defaultVal;
|
|
694
|
-
}
|
|
695
|
-
var HexCpuRenderer = class _HexCpuRenderer {
|
|
696
|
-
static create() {
|
|
697
|
-
ensureWasm();
|
|
698
|
-
return new _HexCpuRenderer();
|
|
699
|
-
}
|
|
700
|
-
isReady() {
|
|
701
|
-
return wasmReady;
|
|
702
|
-
}
|
|
703
|
-
render(input, options = {}) {
|
|
704
|
-
ensureWasm();
|
|
705
|
-
const data = toUint8Array(input);
|
|
706
|
-
const width = input.width;
|
|
707
|
-
const height = input.height;
|
|
708
|
-
const scale = Math.min(32, Math.max(2, options.scale ?? 16));
|
|
709
|
-
const result = wasm.hex_upscale_config(
|
|
710
|
-
data,
|
|
711
|
-
width,
|
|
712
|
-
height,
|
|
713
|
-
scale,
|
|
714
|
-
options.orientation === "pointy-top" ? 1 : 0,
|
|
715
|
-
options.drawBorders ?? false,
|
|
716
|
-
parseColorToU32(options.borderColor, 673720575),
|
|
717
|
-
options.borderThickness ?? 1,
|
|
718
|
-
parseColorToU32(options.backgroundColor, 0)
|
|
719
|
-
);
|
|
720
|
-
return {
|
|
721
|
-
data: getOutputData(result),
|
|
722
|
-
width: result.width,
|
|
723
|
-
height: result.height
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
getDimensions(srcWidth, srcHeight, scale, orientation = "flat-top") {
|
|
727
|
-
ensureWasm();
|
|
728
|
-
const dims = wasm.hex_get_dimensions(srcWidth, srcHeight, scale, orientation === "pointy-top" ? 1 : 0);
|
|
729
|
-
return { width: dims[0], height: dims[1] };
|
|
730
|
-
}
|
|
731
|
-
dispose() {
|
|
732
|
-
}
|
|
733
|
-
};
|
|
734
|
-
var XbrzCpuRenderer = class _XbrzCpuRenderer {
|
|
735
|
-
static create() {
|
|
736
|
-
ensureWasm();
|
|
737
|
-
return new _XbrzCpuRenderer();
|
|
738
|
-
}
|
|
739
|
-
isReady() {
|
|
740
|
-
return wasmReady;
|
|
741
|
-
}
|
|
742
|
-
render(input, options = {}) {
|
|
743
|
-
ensureWasm();
|
|
744
|
-
const data = toUint8Array(input);
|
|
745
|
-
const width = input.width;
|
|
746
|
-
const height = input.height;
|
|
747
|
-
const scale = Math.min(6, Math.max(2, options.scale ?? 2));
|
|
748
|
-
const result = wasm.xbrz_upscale_config(
|
|
749
|
-
data,
|
|
750
|
-
width,
|
|
751
|
-
height,
|
|
752
|
-
scale,
|
|
753
|
-
options.luminanceWeight ?? 1,
|
|
754
|
-
options.equalColorTolerance ?? 30,
|
|
755
|
-
options.dominantDirectionThreshold ?? 4.4,
|
|
756
|
-
options.steepDirectionThreshold ?? 2.2
|
|
757
|
-
);
|
|
758
|
-
return {
|
|
759
|
-
data: getOutputData(result),
|
|
760
|
-
width: result.width,
|
|
761
|
-
height: result.height
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
dispose() {
|
|
765
|
-
}
|
|
766
|
-
};
|
|
767
|
-
var XBRZ_PRESETS = {
|
|
768
|
-
default: {},
|
|
769
|
-
sharp: {
|
|
770
|
-
luminanceWeight: 1,
|
|
771
|
-
equalColorTolerance: 20,
|
|
772
|
-
dominantDirectionThreshold: 4.4,
|
|
773
|
-
steepDirectionThreshold: 2.2
|
|
774
|
-
},
|
|
775
|
-
smooth: {
|
|
776
|
-
luminanceWeight: 0.8,
|
|
777
|
-
equalColorTolerance: 35,
|
|
778
|
-
dominantDirectionThreshold: 5,
|
|
779
|
-
steepDirectionThreshold: 2.5
|
|
780
|
-
}
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
// src/renderart.ts
|
|
784
|
-
function checkWebGL2() {
|
|
785
|
-
if (typeof OffscreenCanvas === "undefined") return false;
|
|
786
|
-
try {
|
|
787
|
-
const canvas = new OffscreenCanvas(1, 1);
|
|
788
|
-
return !!canvas.getContext("webgl2");
|
|
789
|
-
} catch {
|
|
790
|
-
return false;
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
function getMaxTextureSize() {
|
|
794
|
-
if (!checkWebGL2()) return 0;
|
|
795
|
-
try {
|
|
796
|
-
const canvas = new OffscreenCanvas(1, 1);
|
|
797
|
-
const gl = canvas.getContext("webgl2");
|
|
798
|
-
return gl?.getParameter(gl.MAX_TEXTURE_SIZE) ?? 0;
|
|
799
|
-
} catch {
|
|
800
|
-
return 0;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
var CrtEngine = class {
|
|
804
|
-
constructor(gpuAvailable) {
|
|
805
|
-
this.gpuRenderer = null;
|
|
806
|
-
this.cpuRenderer = null;
|
|
807
|
-
this.gpuAvailable = gpuAvailable;
|
|
808
|
-
}
|
|
809
|
-
ensureGpu() {
|
|
810
|
-
if (!this.gpuRenderer && this.gpuAvailable) {
|
|
811
|
-
this.gpuRenderer = CrtGpuRenderer.create();
|
|
812
|
-
}
|
|
813
|
-
if (!this.gpuRenderer) throw new Error("GPU renderer not available");
|
|
814
|
-
return this.gpuRenderer;
|
|
815
|
-
}
|
|
816
|
-
ensureCpu() {
|
|
817
|
-
if (!this.cpuRenderer) {
|
|
818
|
-
this.cpuRenderer = CrtCpuRenderer.create();
|
|
819
|
-
}
|
|
820
|
-
return this.cpuRenderer;
|
|
821
|
-
}
|
|
822
|
-
/** Render with options or preset */
|
|
823
|
-
render(input, optionsOrPreset = {}) {
|
|
824
|
-
const options = typeof optionsOrPreset === "string" ? { ...CRT_PRESETS[optionsOrPreset] } : optionsOrPreset;
|
|
825
|
-
const backend = options.backend ?? "auto";
|
|
826
|
-
if (backend === "gpu" || backend === "auto" && this.gpuAvailable) {
|
|
827
|
-
return this.ensureGpu().render(input, options);
|
|
828
|
-
}
|
|
829
|
-
return this.ensureCpu().render(input, options);
|
|
830
|
-
}
|
|
831
|
-
/** Synchronous GPU-only render */
|
|
832
|
-
renderSync(input, options = {}) {
|
|
833
|
-
return this.ensureGpu().render(input, options);
|
|
834
|
-
}
|
|
835
|
-
dispose() {
|
|
836
|
-
this.gpuRenderer?.dispose();
|
|
837
|
-
this.cpuRenderer?.dispose();
|
|
838
|
-
this.gpuRenderer = null;
|
|
839
|
-
this.cpuRenderer = null;
|
|
840
|
-
}
|
|
841
|
-
};
|
|
842
|
-
var HexEngine = class {
|
|
843
|
-
constructor(gpuAvailable) {
|
|
844
|
-
this.gpuRenderer = null;
|
|
845
|
-
this.cpuRenderer = null;
|
|
846
|
-
this.gpuAvailable = gpuAvailable;
|
|
847
|
-
}
|
|
848
|
-
ensureGpu() {
|
|
849
|
-
if (!this.gpuRenderer && this.gpuAvailable) {
|
|
850
|
-
this.gpuRenderer = HexGpuRenderer.create();
|
|
851
|
-
}
|
|
852
|
-
if (!this.gpuRenderer) throw new Error("GPU renderer not available");
|
|
853
|
-
return this.gpuRenderer;
|
|
854
|
-
}
|
|
855
|
-
ensureCpu() {
|
|
856
|
-
if (!this.cpuRenderer) {
|
|
857
|
-
this.cpuRenderer = HexCpuRenderer.create();
|
|
858
|
-
}
|
|
859
|
-
return this.cpuRenderer;
|
|
860
|
-
}
|
|
861
|
-
render(input, optionsOrPreset = {}) {
|
|
862
|
-
const options = typeof optionsOrPreset === "string" ? { ...HEX_PRESETS[optionsOrPreset] } : optionsOrPreset;
|
|
863
|
-
const backend = options.backend ?? "auto";
|
|
864
|
-
if (backend === "gpu" || backend === "auto" && this.gpuAvailable) {
|
|
865
|
-
return this.ensureGpu().render(input, options);
|
|
866
|
-
}
|
|
867
|
-
return this.ensureCpu().render(input, options);
|
|
868
|
-
}
|
|
869
|
-
renderSync(input, options = {}) {
|
|
870
|
-
return this.ensureGpu().render(input, options);
|
|
871
|
-
}
|
|
872
|
-
getDimensions(srcWidth, srcHeight, options = {}) {
|
|
873
|
-
const scale = options.scale ?? 16;
|
|
874
|
-
const orientation = options.orientation ?? "flat-top";
|
|
875
|
-
return hexGetDimensions(srcWidth, srcHeight, scale, orientation);
|
|
876
|
-
}
|
|
877
|
-
dispose() {
|
|
878
|
-
this.gpuRenderer?.dispose();
|
|
879
|
-
this.cpuRenderer?.dispose();
|
|
880
|
-
this.gpuRenderer = null;
|
|
881
|
-
this.cpuRenderer = null;
|
|
882
|
-
}
|
|
883
|
-
};
|
|
884
|
-
var XbrzEngine = class {
|
|
885
|
-
constructor() {
|
|
886
|
-
this.cpuRenderer = null;
|
|
887
|
-
}
|
|
888
|
-
ensureCpu() {
|
|
889
|
-
if (!this.cpuRenderer) {
|
|
890
|
-
this.cpuRenderer = XbrzCpuRenderer.create();
|
|
891
|
-
}
|
|
892
|
-
return this.cpuRenderer;
|
|
893
|
-
}
|
|
894
|
-
render(input, optionsOrPreset = {}) {
|
|
895
|
-
const options = typeof optionsOrPreset === "string" ? { ...XBRZ_PRESETS[optionsOrPreset] } : optionsOrPreset;
|
|
896
|
-
return this.ensureCpu().render(input, options);
|
|
897
|
-
}
|
|
898
|
-
dispose() {
|
|
899
|
-
this.cpuRenderer?.dispose();
|
|
900
|
-
this.cpuRenderer = null;
|
|
901
|
-
}
|
|
902
|
-
};
|
|
903
|
-
var RenderArt = class _RenderArt {
|
|
904
|
-
constructor(capabilities) {
|
|
905
|
-
this.capabilities = capabilities;
|
|
906
|
-
this.crt = new CrtEngine(capabilities.gpu);
|
|
907
|
-
this.hex = new HexEngine(capabilities.gpu);
|
|
908
|
-
this.xbrz = new XbrzEngine();
|
|
909
|
-
}
|
|
910
|
-
/**
|
|
911
|
-
* Create RenderArt instance.
|
|
912
|
-
* For CPU/WASM support, call initWasm() first.
|
|
913
|
-
*/
|
|
914
|
-
static create() {
|
|
915
|
-
const gpuAvailable = checkWebGL2();
|
|
916
|
-
const cpuAvailable = isWasmLoaded();
|
|
917
|
-
return new _RenderArt({
|
|
918
|
-
gpu: gpuAvailable,
|
|
919
|
-
cpu: cpuAvailable,
|
|
920
|
-
maxTextureSize: gpuAvailable ? getMaxTextureSize() : 0,
|
|
921
|
-
recommendedBackend: gpuAvailable ? "gpu" : cpuAvailable ? "cpu" : "gpu"
|
|
922
|
-
});
|
|
923
|
-
}
|
|
924
|
-
/**
|
|
925
|
-
* Create GPU-only RenderArt instance (no WASM needed).
|
|
926
|
-
*/
|
|
927
|
-
static createGpuOnly() {
|
|
928
|
-
const gpuAvailable = checkWebGL2();
|
|
929
|
-
if (!gpuAvailable) {
|
|
930
|
-
throw new Error("WebGL2 not available");
|
|
931
|
-
}
|
|
932
|
-
return new _RenderArt({
|
|
933
|
-
gpu: true,
|
|
934
|
-
cpu: false,
|
|
935
|
-
maxTextureSize: getMaxTextureSize(),
|
|
936
|
-
recommendedBackend: "gpu"
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
dispose() {
|
|
940
|
-
this.crt.dispose();
|
|
941
|
-
this.hex.dispose();
|
|
942
|
-
this.xbrz.dispose();
|
|
943
|
-
}
|
|
944
|
-
};
|
|
945
|
-
|
|
946
|
-
export { CRT_PRESETS, CrtCpuRenderer, CrtEngine, CrtGpuRenderer, HEX_PRESETS, HexCpuRenderer, HexEngine, HexGpuRenderer, RenderArt, XBRZ_PRESETS, XbrzCpuRenderer, XbrzEngine, hexGetDimensions, initWasm, isWasmLoaded };
|
|
947
|
-
//# sourceMappingURL=index.mjs.map
|
|
948
|
-
//# sourceMappingURL=index.mjs.map
|