@quake2ts/test-utils 0.0.762 → 0.0.764

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.762",
3
+ "version": "0.0.764",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -39,10 +39,10 @@
39
39
  "pngjs": "^7.0.0",
40
40
  "vitest": "^1.6.0",
41
41
  "webgpu": "^0.3.8",
42
- "@quake2ts/engine": "^0.0.762",
43
- "@quake2ts/game": "0.0.762",
44
- "@quake2ts/server": "0.0.762",
45
- "@quake2ts/shared": "0.0.762"
42
+ "@quake2ts/engine": "^0.0.764",
43
+ "@quake2ts/server": "0.0.764",
44
+ "@quake2ts/game": "0.0.764",
45
+ "@quake2ts/shared": "0.0.764"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@quake2ts/engine": {
@@ -77,13 +77,14 @@
77
77
  }
78
78
  },
79
79
  "devDependencies": {
80
- "gl-matrix": "^3.4.4",
81
80
  "@napi-rs/canvas": "^0.1.84",
81
+ "@types/gl": "^6.0.5",
82
82
  "@types/jsdom": "^27.0.0",
83
83
  "@types/pixelmatch": "^5.2.6",
84
84
  "@types/pngjs": "^6.0.5",
85
85
  "@webgpu/types": "^0.1.68",
86
86
  "fake-indexeddb": "^6.0.0",
87
+ "gl-matrix": "^3.4.4",
87
88
  "jsdom": "^27.3.0",
88
89
  "pixelmatch": "^7.1.0",
89
90
  "playwright": "^1.57.0",
@@ -92,14 +93,18 @@
92
93
  "typescript": "^5.4.5",
93
94
  "vitest": "^1.6.0",
94
95
  "webgpu": "^0.3.8",
95
- "@quake2ts/engine": "^0.0.762",
96
- "@quake2ts/game": "0.0.762",
97
- "@quake2ts/server": "0.0.762",
98
- "@quake2ts/shared": "0.0.762"
96
+ "@quake2ts/engine": "^0.0.764",
97
+ "@quake2ts/game": "0.0.764",
98
+ "@quake2ts/server": "0.0.764",
99
+ "@quake2ts/shared": "0.0.764"
100
+ },
101
+ "optionalDependencies": {
102
+ "gl": "^8.1.6"
99
103
  },
100
104
  "scripts": {
101
105
  "build": "tsup src/index.ts --format esm,cjs --dts",
102
106
  "test": "vitest run --passWithNoTests",
103
- "test:webgpu": "cross-env TEST_TYPE=webgpu vitest run"
107
+ "test:webgpu": "cross-env TEST_TYPE=webgpu vitest run",
108
+ "test:webgl": "cross-env TEST_TYPE=webgl vitest run"
104
109
  }
105
110
  }
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ export * from './server/helpers/bandwidth.js';
29
29
  export * from './setup/browser.js';
30
30
  export * from './setup/canvas.js';
31
31
  export * from './setup/webgpu.js';
32
+ export * from './setup/headless-webgl.js'; // Added
32
33
  export * from './engine/mocks/webgpu.js';
33
34
  export * from './setup/timing.js';
34
35
  export * from './setup/node.js';
@@ -71,6 +72,7 @@ export type { StorageScenario } from './setup/storage.js';
71
72
  export type { NetworkSimulator, NetworkCondition } from './e2e/network.js';
72
73
  export type { VisualScenario, VisualDiff } from './e2e/visual.js';
73
74
  export type { HeadlessWebGPUSetup, WebGPUContextState } from './setup/webgpu.js';
75
+ export type { HeadlessWebGLContext, HeadlessWebGLOptions } from './setup/headless-webgl.js'; // Added
74
76
  export type { RenderTestSetup, ComputeTestSetup } from './engine/helpers/webgpu-rendering.js';
75
77
  export type { GeometryBuffers } from './engine/helpers/pipeline-test-template.js';
76
78
 
@@ -0,0 +1,112 @@
1
+ export interface HeadlessWebGLOptions {
2
+ width?: number;
3
+ height?: number;
4
+ antialias?: boolean;
5
+ preserveDrawingBuffer?: boolean;
6
+ }
7
+
8
+ export interface HeadlessWebGLContext {
9
+ gl: WebGL2RenderingContext;
10
+ width: number;
11
+ height: number;
12
+ cleanup: () => void;
13
+ }
14
+
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(
20
+ options: HeadlessWebGLOptions = {}
21
+ ): Promise<HeadlessWebGLContext> {
22
+ const width = options.width ?? 256;
23
+ const height = options.height ?? 256;
24
+
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');
28
+
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
33
+ stencil: true,
34
+ alpha: true,
35
+ depth: true,
36
+ });
37
+
38
+ if (!context) {
39
+ throw new Error('Failed to create headless WebGL context');
40
+ }
41
+
42
+ // Cast to WebGL2RenderingContext
43
+ const glContext = context as unknown as WebGL2RenderingContext;
44
+
45
+ return {
46
+ gl: glContext,
47
+ width,
48
+ height,
49
+ cleanup: () => {
50
+ // gl package extension to destroy context
51
+ const ext = glContext.getExtension('STACKGL_destroy_context');
52
+ if (ext) {
53
+ ext.destroy();
54
+ }
55
+ },
56
+ };
57
+ }
58
+
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,
65
+ width: number,
66
+ height: number
67
+ ): Uint8ClampedArray {
68
+ const pixels = new Uint8ClampedArray(width * height * 4);
69
+
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
+ );
80
+
81
+ return flipPixelsVertically(pixels, width, height);
82
+ }
83
+
84
+ /**
85
+ * Flips pixel data vertically in-place.
86
+ */
87
+ export function flipPixelsVertically(
88
+ pixels: Uint8ClampedArray,
89
+ width: number,
90
+ height: number
91
+ ): 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;
112
+ }