@quake2ts/test-utils 0.0.764 → 0.0.765
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/index.cjs +56 -5989
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -13
- package/dist/index.d.ts +2 -13
- package/dist/index.js +35 -5998
- package/dist/index.js.map +1 -1
- package/package.json +14 -9
- package/src/setup/headless-webgl.ts +57 -63
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quake2ts/test-utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.765",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
"types": "./src/setup/webgpu.ts",
|
|
21
21
|
"import": "./src/setup/webgpu.ts",
|
|
22
22
|
"require": "./src/setup/webgpu.ts"
|
|
23
|
+
},
|
|
24
|
+
"./src/setup/headless-webgl": {
|
|
25
|
+
"types": "./src/setup/headless-webgl.ts",
|
|
26
|
+
"import": "./src/setup/headless-webgl.ts",
|
|
27
|
+
"require": "./src/setup/headless-webgl.ts"
|
|
23
28
|
}
|
|
24
29
|
},
|
|
25
30
|
"files": [
|
|
@@ -39,10 +44,10 @@
|
|
|
39
44
|
"pngjs": "^7.0.0",
|
|
40
45
|
"vitest": "^1.6.0",
|
|
41
46
|
"webgpu": "^0.3.8",
|
|
42
|
-
"@quake2ts/engine": "^0.0.
|
|
43
|
-
"@quake2ts/
|
|
44
|
-
"@quake2ts/
|
|
45
|
-
"@quake2ts/
|
|
47
|
+
"@quake2ts/engine": "^0.0.765",
|
|
48
|
+
"@quake2ts/game": "0.0.765",
|
|
49
|
+
"@quake2ts/shared": "0.0.765",
|
|
50
|
+
"@quake2ts/server": "0.0.765"
|
|
46
51
|
},
|
|
47
52
|
"peerDependenciesMeta": {
|
|
48
53
|
"@quake2ts/engine": {
|
|
@@ -93,10 +98,10 @@
|
|
|
93
98
|
"typescript": "^5.4.5",
|
|
94
99
|
"vitest": "^1.6.0",
|
|
95
100
|
"webgpu": "^0.3.8",
|
|
96
|
-
"@quake2ts/engine": "^0.0.
|
|
97
|
-
"@quake2ts/game": "0.0.
|
|
98
|
-
"@quake2ts/server": "0.0.
|
|
99
|
-
"@quake2ts/shared": "0.0.
|
|
101
|
+
"@quake2ts/engine": "^0.0.765",
|
|
102
|
+
"@quake2ts/game": "0.0.765",
|
|
103
|
+
"@quake2ts/server": "0.0.765",
|
|
104
|
+
"@quake2ts/shared": "0.0.765"
|
|
100
105
|
},
|
|
101
106
|
"optionalDependencies": {
|
|
102
107
|
"gl": "^8.1.6"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
|
|
1
3
|
export interface HeadlessWebGLOptions {
|
|
2
4
|
width?: number;
|
|
3
5
|
height?: number;
|
|
@@ -12,27 +14,42 @@ export interface HeadlessWebGLContext {
|
|
|
12
14
|
cleanup: () => void;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
// Use createRequire to load 'gl' dynamically/safely if needed, or just to handle the CommonJS nature better.
|
|
18
|
+
// We also wrap the require in a try-catch or just checking existence if we were worried about load-time failure,
|
|
19
|
+
// but usually 'gl' loads fine and fails at runtime.
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
|
|
22
|
+
let headlessGL: any;
|
|
23
|
+
try {
|
|
24
|
+
headlessGL = require('gl');
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// If 'gl' is not installed or fails to load bindings, we leave it undefined.
|
|
27
|
+
// The createHeadlessWebGL function will handle this.
|
|
28
|
+
console.warn('Failed to load "gl" package. Headless WebGL creation will fail.', e);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createHeadlessWebGL(
|
|
20
32
|
options: HeadlessWebGLOptions = {}
|
|
21
|
-
):
|
|
33
|
+
): HeadlessWebGLContext {
|
|
34
|
+
if (!headlessGL) {
|
|
35
|
+
throw new Error('Headless GL package is not available or failed to load.');
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
const width = options.width ?? 256;
|
|
23
39
|
const height = options.height ?? 256;
|
|
24
40
|
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
// Ensure antialias is false for deterministic testing unless explicitly enabled
|
|
42
|
+
const antialias = options.antialias ?? false;
|
|
43
|
+
// Ensure preserveDrawingBuffer is true for readback unless explicitly disabled
|
|
44
|
+
const preserveDrawingBuffer = options.preserveDrawingBuffer ?? true;
|
|
28
45
|
|
|
29
|
-
//
|
|
30
|
-
const context =
|
|
31
|
-
antialias
|
|
32
|
-
preserveDrawingBuffer
|
|
46
|
+
// Create context using 'gl' package
|
|
47
|
+
const context = headlessGL(width, height, {
|
|
48
|
+
antialias,
|
|
49
|
+
preserveDrawingBuffer,
|
|
33
50
|
stencil: true,
|
|
34
|
-
alpha: true,
|
|
35
51
|
depth: true,
|
|
52
|
+
alpha: true
|
|
36
53
|
});
|
|
37
54
|
|
|
38
55
|
if (!context) {
|
|
@@ -40,73 +57,50 @@ export async function createHeadlessWebGL(
|
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
// Cast to WebGL2RenderingContext
|
|
43
|
-
|
|
60
|
+
// Note: The 'gl' package implements WebGL 1, but we cast to WebGL 2 for type compatibility.
|
|
61
|
+
// Engine code should gracefuly handle missing WebGL 2 features or this environment is for specific subsets.
|
|
62
|
+
// WARNING: Calling actual WebGL 2 methods (e.g. createVertexArray) will crash at runtime.
|
|
63
|
+
const gl2 = context as unknown as WebGL2RenderingContext;
|
|
44
64
|
|
|
45
65
|
return {
|
|
46
|
-
gl:
|
|
66
|
+
gl: gl2,
|
|
47
67
|
width,
|
|
48
68
|
height,
|
|
49
69
|
cleanup: () => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (ext) {
|
|
70
|
+
const ext = context.getExtension('STACKGL_destroy_context');
|
|
71
|
+
if (ext && typeof ext.destroy === 'function') {
|
|
53
72
|
ext.destroy();
|
|
54
73
|
}
|
|
55
|
-
}
|
|
74
|
+
}
|
|
56
75
|
};
|
|
57
76
|
}
|
|
58
77
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* Flips the pixels vertically to match standard image orientation (top-left origin).
|
|
62
|
-
*/
|
|
63
|
-
export function captureWebGLFramebuffer(
|
|
64
|
-
glContext: WebGL2RenderingContext,
|
|
78
|
+
export function flipPixelsVertically(
|
|
79
|
+
pixels: Uint8ClampedArray,
|
|
65
80
|
width: number,
|
|
66
81
|
height: number
|
|
67
82
|
): Uint8ClampedArray {
|
|
68
|
-
const
|
|
83
|
+
const flipped = new Uint8ClampedArray(pixels.length);
|
|
84
|
+
const rowSize = width * 4;
|
|
69
85
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
glContext.RGBA,
|
|
77
|
-
glContext.UNSIGNED_BYTE,
|
|
78
|
-
pixels
|
|
79
|
-
);
|
|
86
|
+
for (let y = 0; y < height; y++) {
|
|
87
|
+
const srcRowStart = y * rowSize;
|
|
88
|
+
const dstRowStart = (height - 1 - y) * rowSize;
|
|
89
|
+
// Copy row
|
|
90
|
+
flipped.set(pixels.subarray(srcRowStart, srcRowStart + rowSize), dstRowStart);
|
|
91
|
+
}
|
|
80
92
|
|
|
81
|
-
return
|
|
93
|
+
return flipped;
|
|
82
94
|
}
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
*/
|
|
87
|
-
export function flipPixelsVertically(
|
|
88
|
-
pixels: Uint8ClampedArray,
|
|
96
|
+
export function captureWebGLFramebuffer(
|
|
97
|
+
gl: WebGL2RenderingContext,
|
|
89
98
|
width: number,
|
|
90
99
|
height: number
|
|
91
100
|
): Uint8ClampedArray {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
for (let y = 0; y < halfHeight; y++) {
|
|
98
|
-
const topOffset = y * rowSize;
|
|
99
|
-
const bottomOffset = (height - 1 - y) * rowSize;
|
|
100
|
-
|
|
101
|
-
// Copy top to temp
|
|
102
|
-
tempRow.set(pixels.subarray(topOffset, topOffset + rowSize));
|
|
103
|
-
|
|
104
|
-
// Copy bottom to top
|
|
105
|
-
pixels.copyWithin(topOffset, bottomOffset, bottomOffset + rowSize);
|
|
106
|
-
|
|
107
|
-
// Copy temp to bottom
|
|
108
|
-
pixels.set(tempRow, bottomOffset);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return pixels;
|
|
101
|
+
const pixels = new Uint8ClampedArray(width * height * 4);
|
|
102
|
+
// Read pixels (returns bottom-up)
|
|
103
|
+
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
104
|
+
// Flip to top-down
|
|
105
|
+
return flipPixelsVertically(pixels, width, height);
|
|
112
106
|
}
|