@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quake2ts/test-utils",
3
- "version": "0.0.764",
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.764",
43
- "@quake2ts/server": "0.0.764",
44
- "@quake2ts/game": "0.0.764",
45
- "@quake2ts/shared": "0.0.764"
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.764",
97
- "@quake2ts/game": "0.0.764",
98
- "@quake2ts/server": "0.0.764",
99
- "@quake2ts/shared": "0.0.764"
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
- * Creates a headless WebGL2 context using the 'gl' package.
17
- * Note: 'gl' is lazy-loaded to avoid issues in environments where it's not supported/needed.
18
- */
19
- export async function createHeadlessWebGL(
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
- ): Promise<HeadlessWebGLContext> {
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
- // Dynamically import gl
26
- // @ts-ignore - gl package might not be typed correctly for dynamic import or TS config
27
- const { default: gl } = await import('gl');
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
- // The 'gl' function signature is gl(width, height, options)
30
- const context = gl(width, height, {
31
- antialias: options.antialias ?? false, // Default to false for determinism
32
- preserveDrawingBuffer: options.preserveDrawingBuffer ?? true, // Default to true for readback
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
- const glContext = context as unknown as WebGL2RenderingContext;
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: glContext,
66
+ gl: gl2,
47
67
  width,
48
68
  height,
49
69
  cleanup: () => {
50
- // gl package extension to destroy context
51
- const ext = glContext.getExtension('STACKGL_destroy_context');
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
- * Captures the current framebuffer content as a Uint8ClampedArray (RGBA).
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 pixels = new Uint8ClampedArray(width * height * 4);
83
+ const flipped = new Uint8ClampedArray(pixels.length);
84
+ const rowSize = width * 4;
69
85
 
70
- // readPixels reads from bottom-left
71
- glContext.readPixels(
72
- 0,
73
- 0,
74
- width,
75
- height,
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 flipPixelsVertically(pixels, width, height);
93
+ return flipped;
82
94
  }
83
95
 
84
- /**
85
- * Flips pixel data vertically in-place.
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 rowSize = width * 4;
93
- const halfHeight = Math.floor(height / 2);
94
- const tempRow = new Uint8Array(rowSize);
95
-
96
- // Swap rows
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
  }