@quake2ts/test-utils 0.0.1
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 +454 -0
- package/dist/index.cjs +5432 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2150 -0
- package/dist/index.d.ts +2150 -0
- package/dist/index.js +5165 -0
- package/dist/index.js.map +1 -0
- package/package.json +82 -0
- package/src/client/helpers/hud.ts +114 -0
- package/src/client/helpers/prediction.ts +136 -0
- package/src/client/helpers/view.ts +201 -0
- package/src/client/mocks/console.ts +75 -0
- package/src/client/mocks/download.ts +48 -0
- package/src/client/mocks/input.ts +246 -0
- package/src/client/mocks/network.ts +148 -0
- package/src/client/mocks/state.ts +148 -0
- package/src/e2e/network.ts +47 -0
- package/src/e2e/playwright.ts +90 -0
- package/src/e2e/visual.ts +172 -0
- package/src/engine/helpers/pipeline-test-template.ts +113 -0
- package/src/engine/helpers/webgpu-rendering.ts +251 -0
- package/src/engine/mocks/assets.ts +129 -0
- package/src/engine/mocks/audio.ts +152 -0
- package/src/engine/mocks/buffers.ts +88 -0
- package/src/engine/mocks/lighting.ts +64 -0
- package/src/engine/mocks/particles.ts +76 -0
- package/src/engine/mocks/renderer.ts +218 -0
- package/src/engine/mocks/webgl.ts +267 -0
- package/src/engine/mocks/webgpu.ts +262 -0
- package/src/engine/rendering.ts +103 -0
- package/src/game/factories.ts +204 -0
- package/src/game/helpers/physics.ts +171 -0
- package/src/game/helpers/save.ts +232 -0
- package/src/game/helpers.ts +310 -0
- package/src/game/mocks/ai.ts +67 -0
- package/src/game/mocks/combat.ts +61 -0
- package/src/game/mocks/items.ts +166 -0
- package/src/game/mocks.ts +105 -0
- package/src/index.ts +93 -0
- package/src/server/helpers/bandwidth.ts +127 -0
- package/src/server/helpers/multiplayer.ts +158 -0
- package/src/server/helpers/snapshot.ts +241 -0
- package/src/server/mockNetDriver.ts +106 -0
- package/src/server/mockTransport.ts +50 -0
- package/src/server/mocks/commands.ts +93 -0
- package/src/server/mocks/connection.ts +139 -0
- package/src/server/mocks/master.ts +97 -0
- package/src/server/mocks/physics.ts +32 -0
- package/src/server/mocks/state.ts +162 -0
- package/src/server/mocks/transport.ts +161 -0
- package/src/setup/audio.ts +118 -0
- package/src/setup/browser.ts +249 -0
- package/src/setup/canvas.ts +142 -0
- package/src/setup/node.ts +21 -0
- package/src/setup/storage.ts +60 -0
- package/src/setup/timing.ts +142 -0
- package/src/setup/webgl.ts +8 -0
- package/src/setup/webgpu.ts +113 -0
- package/src/shared/bsp.ts +145 -0
- package/src/shared/collision.ts +64 -0
- package/src/shared/factories.ts +88 -0
- package/src/shared/math.ts +65 -0
- package/src/shared/mocks.ts +243 -0
- package/src/shared/pak-loader.ts +45 -0
- package/src/visual/snapshots.ts +292 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
interface ShaderRecord {
|
|
4
|
+
readonly id: number;
|
|
5
|
+
readonly type: GLenum;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ProgramRecord {
|
|
9
|
+
readonly id: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class MockWebGL2RenderingContext {
|
|
13
|
+
readonly ARRAY_BUFFER = 0x8892;
|
|
14
|
+
readonly ELEMENT_ARRAY_BUFFER = 0x8893;
|
|
15
|
+
readonly STATIC_DRAW = 0x88e4;
|
|
16
|
+
readonly DYNAMIC_DRAW = 0x88e8;
|
|
17
|
+
readonly FLOAT = 0x1406;
|
|
18
|
+
readonly UNSIGNED_SHORT = 0x1403;
|
|
19
|
+
readonly TEXTURE_2D = 0x0de1;
|
|
20
|
+
readonly TEXTURE_CUBE_MAP = 0x8513;
|
|
21
|
+
readonly TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515;
|
|
22
|
+
readonly TEXTURE0 = 0x84c0;
|
|
23
|
+
readonly TEXTURE_WRAP_S = 0x2802;
|
|
24
|
+
readonly TEXTURE_WRAP_T = 0x2803;
|
|
25
|
+
readonly TEXTURE_MIN_FILTER = 0x2801;
|
|
26
|
+
readonly TEXTURE_MAG_FILTER = 0x2800;
|
|
27
|
+
readonly LINEAR = 0x2601;
|
|
28
|
+
readonly NEAREST = 0x2600;
|
|
29
|
+
readonly CLAMP_TO_EDGE = 0x812f;
|
|
30
|
+
readonly RGBA = 0x1908;
|
|
31
|
+
readonly UNSIGNED_BYTE = 0x1401;
|
|
32
|
+
readonly FRAMEBUFFER = 0x8d40;
|
|
33
|
+
readonly COLOR_ATTACHMENT0 = 0x8ce0;
|
|
34
|
+
readonly DEPTH_ATTACHMENT = 0x8d00;
|
|
35
|
+
readonly RENDERBUFFER = 0x8d41;
|
|
36
|
+
readonly DEPTH_COMPONENT24 = 0x81a6;
|
|
37
|
+
readonly FRAMEBUFFER_COMPLETE = 0x8cd5;
|
|
38
|
+
readonly TRIANGLES = 0x0004;
|
|
39
|
+
readonly DEPTH_TEST = 0x0b71;
|
|
40
|
+
readonly CULL_FACE = 0x0b44;
|
|
41
|
+
readonly BLEND = 0x0be2;
|
|
42
|
+
readonly SRC_ALPHA = 0x0302;
|
|
43
|
+
readonly ONE_MINUS_SRC_ALPHA = 0x0303;
|
|
44
|
+
readonly ONE = 1;
|
|
45
|
+
readonly BACK = 0x0405;
|
|
46
|
+
readonly LEQUAL = 0x0203;
|
|
47
|
+
readonly VERTEX_SHADER = 0x8b31;
|
|
48
|
+
readonly FRAGMENT_SHADER = 0x8b30;
|
|
49
|
+
readonly COMPILE_STATUS = 0x8b81;
|
|
50
|
+
readonly LINK_STATUS = 0x8b82;
|
|
51
|
+
readonly ONE_MINUS_SRC_COLOR = 0x0301;
|
|
52
|
+
readonly TRIANGLE_STRIP = 0x0005;
|
|
53
|
+
readonly QUERY_RESULT_AVAILABLE = 0x8867;
|
|
54
|
+
readonly QUERY_RESULT = 0x8866;
|
|
55
|
+
|
|
56
|
+
// Constants commonly used in setup/webgl.ts
|
|
57
|
+
readonly TRIANGLE_FAN = 0x0006;
|
|
58
|
+
readonly COLOR_BUFFER_BIT = 0x4000;
|
|
59
|
+
readonly DEPTH_BUFFER_BIT = 0x0100;
|
|
60
|
+
|
|
61
|
+
readonly canvas: HTMLCanvasElement | { width: number; height: number };
|
|
62
|
+
readonly drawingBufferWidth: number;
|
|
63
|
+
readonly drawingBufferHeight: number;
|
|
64
|
+
|
|
65
|
+
private shaderCounter = 0;
|
|
66
|
+
private programCounter = 0;
|
|
67
|
+
|
|
68
|
+
compileSucceeds = true;
|
|
69
|
+
linkSucceeds = true;
|
|
70
|
+
shaderInfoLog = 'shader failed';
|
|
71
|
+
programInfoLog = 'program failed';
|
|
72
|
+
|
|
73
|
+
readonly extensions = new Map<string, unknown>();
|
|
74
|
+
readonly calls: string[] = [];
|
|
75
|
+
readonly uniformLocations = new Map<string, WebGLUniformLocation | null>();
|
|
76
|
+
readonly attributeLocations = new Map<string, number>();
|
|
77
|
+
|
|
78
|
+
constructor(canvas?: HTMLCanvasElement) {
|
|
79
|
+
if (canvas) {
|
|
80
|
+
this.canvas = canvas;
|
|
81
|
+
this.drawingBufferWidth = canvas.width;
|
|
82
|
+
this.drawingBufferHeight = canvas.height;
|
|
83
|
+
} else {
|
|
84
|
+
this.canvas = { width: 800, height: 600 };
|
|
85
|
+
this.drawingBufferWidth = 800;
|
|
86
|
+
this.drawingBufferHeight = 600;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
enable = vi.fn((cap: GLenum) => this.calls.push(`enable:${cap}`));
|
|
91
|
+
disable = vi.fn((cap: GLenum) => this.calls.push(`disable:${cap}`));
|
|
92
|
+
depthFunc = vi.fn((func: GLenum) => this.calls.push(`depthFunc:${func}`));
|
|
93
|
+
cullFace = vi.fn((mode: GLenum) => this.calls.push(`cullFace:${mode}`));
|
|
94
|
+
depthMask = vi.fn((flag: GLboolean) => this.calls.push(`depthMask:${flag}`));
|
|
95
|
+
blendFuncSeparate = vi.fn((srcRGB: GLenum, dstRGB: GLenum, srcAlpha: GLenum, dstAlpha: GLenum) =>
|
|
96
|
+
this.calls.push(`blendFuncSeparate:${srcRGB}:${dstRGB}:${srcAlpha}:${dstAlpha}`)
|
|
97
|
+
);
|
|
98
|
+
blendFunc = vi.fn((sfactor: GLenum, dfactor: GLenum) => this.calls.push(`blendFunc:${sfactor}:${dfactor}`));
|
|
99
|
+
getExtension = vi.fn((name: string) => this.extensions.get(name) ?? null);
|
|
100
|
+
viewport = vi.fn((x: number, y: number, w: number, h: number) => this.calls.push(`viewport:${x}:${y}:${w}:${h}`));
|
|
101
|
+
clear = vi.fn((mask: number) => this.calls.push(`clear:${mask}`));
|
|
102
|
+
clearColor = vi.fn((r: number, g: number, b: number, a: number) => this.calls.push(`clearColor:${r}:${g}:${b}:${a}`));
|
|
103
|
+
|
|
104
|
+
createShader = vi.fn((type: GLenum) => ({ id: ++this.shaderCounter, type } as unknown as WebGLShader));
|
|
105
|
+
shaderSource = vi.fn((shader: ShaderRecord, source: string) => this.calls.push(`shaderSource:${shader.id}:${source}`));
|
|
106
|
+
compileShader = vi.fn((shader: ShaderRecord) => this.calls.push(`compileShader:${shader.id}`));
|
|
107
|
+
getShaderParameter = vi.fn((shader: ShaderRecord, pname: GLenum) =>
|
|
108
|
+
pname === this.COMPILE_STATUS ? this.compileSucceeds : null
|
|
109
|
+
);
|
|
110
|
+
getShaderInfoLog = vi.fn(() => (this.compileSucceeds ? '' : this.shaderInfoLog));
|
|
111
|
+
deleteShader = vi.fn((shader: ShaderRecord) => this.calls.push(`deleteShader:${shader.id}`));
|
|
112
|
+
|
|
113
|
+
createProgram = vi.fn(() => ({ id: ++this.programCounter } as unknown as WebGLProgram));
|
|
114
|
+
attachShader = vi.fn((program: ProgramRecord, shader: ShaderRecord) =>
|
|
115
|
+
this.calls.push(`attach:${program.id}:${shader.id}`)
|
|
116
|
+
);
|
|
117
|
+
bindAttribLocation = vi.fn((program: ProgramRecord, index: number, name: string) =>
|
|
118
|
+
this.calls.push(`bindAttribLocation:${program.id}:${index}:${name}`)
|
|
119
|
+
);
|
|
120
|
+
linkProgram = vi.fn((program: ProgramRecord) => this.calls.push(`link:${program.id}`));
|
|
121
|
+
getProgramParameter = vi.fn((program: ProgramRecord, pname: GLenum) =>
|
|
122
|
+
pname === this.LINK_STATUS ? this.linkSucceeds : null
|
|
123
|
+
);
|
|
124
|
+
getProgramInfoLog = vi.fn(() => (this.linkSucceeds ? '' : this.programInfoLog));
|
|
125
|
+
deleteProgram = vi.fn((program: ProgramRecord) => this.calls.push(`deleteProgram:${program.id}`));
|
|
126
|
+
useProgram = vi.fn((program: ProgramRecord | null) => this.calls.push(`useProgram:${program?.id ?? 'null'}`));
|
|
127
|
+
getUniformLocation = vi.fn((program: ProgramRecord, name: string) => {
|
|
128
|
+
this.calls.push(`getUniformLocation:${program.id}:${name}`);
|
|
129
|
+
return this.uniformLocations.get(name) ?? null;
|
|
130
|
+
});
|
|
131
|
+
getAttribLocation = vi.fn((program: ProgramRecord, name: string) => {
|
|
132
|
+
this.calls.push(`getAttribLocation:${program.id}:${name}`);
|
|
133
|
+
return this.attributeLocations.get(name) ?? -1;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
createBuffer = vi.fn(() => ({ buffer: {} } as unknown as WebGLBuffer));
|
|
137
|
+
bindBuffer = vi.fn((target: GLenum, buffer: WebGLBuffer | null) => this.calls.push(`bindBuffer:${target}:${!!buffer}`));
|
|
138
|
+
bufferData = vi.fn((target: GLenum, data: number | BufferSource, usage: GLenum) =>
|
|
139
|
+
this.calls.push(`bufferData:${target}:${usage}:${typeof data === 'number' ? data : 'data'}`)
|
|
140
|
+
);
|
|
141
|
+
bufferSubData = vi.fn((target: GLenum, offset: number, data: BufferSource) =>
|
|
142
|
+
this.calls.push(`bufferSubData:${target}:${offset}:${data.byteLength ?? 'len'}`)
|
|
143
|
+
);
|
|
144
|
+
deleteBuffer = vi.fn((buffer: WebGLBuffer) => this.calls.push(`deleteBuffer:${!!buffer}`));
|
|
145
|
+
|
|
146
|
+
createVertexArray = vi.fn(() => ({ vao: {} } as unknown as WebGLVertexArrayObject));
|
|
147
|
+
bindVertexArray = vi.fn((vao: WebGLVertexArrayObject | null) => this.calls.push(`bindVertexArray:${!!vao}`));
|
|
148
|
+
enableVertexAttribArray = vi.fn((index: number) => this.calls.push(`enableAttrib:${index}`));
|
|
149
|
+
vertexAttribPointer = vi.fn(
|
|
150
|
+
(index: number, size: number, type: GLenum, normalized: boolean, stride: number, offset: number) =>
|
|
151
|
+
this.calls.push(`vertexAttribPointer:${index}:${size}:${type}:${normalized}:${stride}:${offset}`)
|
|
152
|
+
);
|
|
153
|
+
vertexAttribDivisor = vi.fn((index: number, divisor: number) => this.calls.push(`divisor:${index}:${divisor}`));
|
|
154
|
+
deleteVertexArray = vi.fn((vao: WebGLVertexArrayObject) => this.calls.push(`deleteVertexArray:${!!vao}`));
|
|
155
|
+
|
|
156
|
+
createTexture = vi.fn(() => ({ texture: {} } as unknown as WebGLTexture));
|
|
157
|
+
activeTexture = vi.fn((unit: GLenum) => this.calls.push(`activeTexture:${unit}`));
|
|
158
|
+
bindTexture = vi.fn((target: GLenum, texture: WebGLTexture | null) => this.calls.push(`bindTexture:${target}:${!!texture}`));
|
|
159
|
+
texParameteri = vi.fn((target: GLenum, pname: GLenum, param: GLint) =>
|
|
160
|
+
this.calls.push(`texParameteri:${target}:${pname}:${param}`)
|
|
161
|
+
);
|
|
162
|
+
texImage2D = vi.fn(
|
|
163
|
+
(
|
|
164
|
+
target: GLenum,
|
|
165
|
+
level: GLint,
|
|
166
|
+
internalFormat: GLenum,
|
|
167
|
+
width: GLsizei,
|
|
168
|
+
height: GLsizei,
|
|
169
|
+
border: GLint,
|
|
170
|
+
format: GLenum,
|
|
171
|
+
type: GLenum,
|
|
172
|
+
pixels: ArrayBufferView | null
|
|
173
|
+
) =>
|
|
174
|
+
this.calls.push(
|
|
175
|
+
`texImage2D:${target}:${level}:${internalFormat}:${width}:${height}:${border}:${format}:${type}:${pixels ? 'data' : 'null'}`
|
|
176
|
+
)
|
|
177
|
+
);
|
|
178
|
+
texImage3D = vi.fn(); // Stub for compatibility
|
|
179
|
+
deleteTexture = vi.fn((texture: WebGLTexture) => this.calls.push(`deleteTexture:${!!texture}`));
|
|
180
|
+
|
|
181
|
+
createFramebuffer = vi.fn(() => ({ fb: {} } as unknown as WebGLFramebuffer));
|
|
182
|
+
bindFramebuffer = vi.fn((target: GLenum, framebuffer: WebGLFramebuffer | null) =>
|
|
183
|
+
this.calls.push(`bindFramebuffer:${target}:${!!framebuffer}`)
|
|
184
|
+
);
|
|
185
|
+
framebufferTexture2D = vi.fn(
|
|
186
|
+
(target: GLenum, attachment: GLenum, textarget: GLenum, texture: WebGLTexture | null, level: GLint) =>
|
|
187
|
+
this.calls.push(`framebufferTexture2D:${target}:${attachment}:${textarget}:${!!texture}:${level}`)
|
|
188
|
+
);
|
|
189
|
+
deleteFramebuffer = vi.fn((fb: WebGLFramebuffer) => this.calls.push(`deleteFramebuffer:${!!fb}`));
|
|
190
|
+
checkFramebufferStatus = vi.fn((target: GLenum) => this.FRAMEBUFFER_COMPLETE);
|
|
191
|
+
|
|
192
|
+
createRenderbuffer = vi.fn(() => ({ rb: {} } as unknown as WebGLRenderbuffer));
|
|
193
|
+
bindRenderbuffer = vi.fn((target: GLenum, renderbuffer: WebGLRenderbuffer | null) =>
|
|
194
|
+
this.calls.push(`bindRenderbuffer:${target}:${!!renderbuffer}`)
|
|
195
|
+
);
|
|
196
|
+
renderbufferStorage = vi.fn((target: GLenum, internalformat: GLenum, width: GLsizei, height: GLsizei) =>
|
|
197
|
+
this.calls.push(`renderbufferStorage:${target}:${internalformat}:${width}:${height}`)
|
|
198
|
+
);
|
|
199
|
+
framebufferRenderbuffer = vi.fn((target: GLenum, attachment: GLenum, renderbuffertarget: GLenum, renderbuffer: WebGLRenderbuffer | null) =>
|
|
200
|
+
this.calls.push(`framebufferRenderbuffer:${target}:${attachment}:${renderbuffertarget}:${!!renderbuffer}`)
|
|
201
|
+
);
|
|
202
|
+
deleteRenderbuffer = vi.fn((rb: WebGLRenderbuffer) => this.calls.push(`deleteRenderbuffer:${!!rb}`));
|
|
203
|
+
|
|
204
|
+
drawArrays = vi.fn((mode: GLenum, first: GLint, count: GLsizei) =>
|
|
205
|
+
this.calls.push(`drawArrays:${mode}:${first}:${count}`)
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
drawElements = vi.fn((mode: GLenum, count: GLsizei, type: GLenum, offset: GLintptr) =>
|
|
209
|
+
this.calls.push(`drawElements:${mode}:${count}:${type}:${offset}`)
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// Queries
|
|
213
|
+
createQuery = vi.fn(() => ({}) as WebGLQuery);
|
|
214
|
+
beginQuery = vi.fn();
|
|
215
|
+
endQuery = vi.fn();
|
|
216
|
+
deleteQuery = vi.fn();
|
|
217
|
+
getQueryParameter = vi.fn();
|
|
218
|
+
getParameter = vi.fn();
|
|
219
|
+
|
|
220
|
+
uniform1f = vi.fn((location: WebGLUniformLocation | null, x: GLfloat) =>
|
|
221
|
+
this.calls.push(`uniform1f:${location ? 'set' : 'null'}:${x}`)
|
|
222
|
+
);
|
|
223
|
+
uniform1i = vi.fn((location: WebGLUniformLocation | null, x: GLint) =>
|
|
224
|
+
this.calls.push(`uniform1i:${location ? 'set' : 'null'}:${x}`)
|
|
225
|
+
);
|
|
226
|
+
uniform4f = vi.fn((location: WebGLUniformLocation | null, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) =>
|
|
227
|
+
this.calls.push(`uniform4f:${location ? 'set' : 'null'}:${x}:${y}:${z}:${w}`)
|
|
228
|
+
);
|
|
229
|
+
uniform3fv = vi.fn((location: WebGLUniformLocation | null, data: Float32List | number[]) =>
|
|
230
|
+
this.calls.push(`uniform3fv:${location ? 'set' : 'null'}:${Array.from(data as Iterable<number>).join(',')}`)
|
|
231
|
+
);
|
|
232
|
+
uniform3f = vi.fn((location: WebGLUniformLocation | null, x: GLfloat, y: GLfloat, z: GLfloat) =>
|
|
233
|
+
this.calls.push(`uniform3f:${location ? 'set' : 'null'}:${x}:${y}:${z}`)
|
|
234
|
+
);
|
|
235
|
+
uniform2f = vi.fn((location: WebGLUniformLocation | null, x: GLfloat, y: GLfloat) =>
|
|
236
|
+
this.calls.push(`uniform2f:${location ? 'set' : 'null'}:${x}:${y}`)
|
|
237
|
+
);
|
|
238
|
+
uniform4fv = vi.fn((location: WebGLUniformLocation | null, data: Float32List) =>
|
|
239
|
+
this.calls.push(`uniform4fv:${location ? 'set' : 'null'}:${Array.from(data as Iterable<number>).join(',')}`)
|
|
240
|
+
);
|
|
241
|
+
uniformMatrix4fv = vi.fn(
|
|
242
|
+
(location: WebGLUniformLocation | null, transpose: GLboolean, data: Float32List | Iterable<number>) =>
|
|
243
|
+
this.calls.push(`uniformMatrix4fv:${location ? 'set' : 'null'}:${transpose}:${Array.from(data as Iterable<number>).join(',')}`)
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
uniformBlockBinding = vi.fn();
|
|
247
|
+
|
|
248
|
+
isContextLost = vi.fn(() => false);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function createMockWebGL2Context(
|
|
252
|
+
overridesOrCanvas?: Partial<WebGL2RenderingContext> | HTMLCanvasElement
|
|
253
|
+
): MockWebGL2RenderingContext {
|
|
254
|
+
let context: MockWebGL2RenderingContext;
|
|
255
|
+
|
|
256
|
+
if (overridesOrCanvas instanceof Object && 'width' in overridesOrCanvas && 'height' in overridesOrCanvas && 'getContext' in overridesOrCanvas) {
|
|
257
|
+
// It's likely a canvas element (or mock of it)
|
|
258
|
+
context = new MockWebGL2RenderingContext(overridesOrCanvas as HTMLCanvasElement);
|
|
259
|
+
} else {
|
|
260
|
+
context = new MockWebGL2RenderingContext();
|
|
261
|
+
if (overridesOrCanvas) {
|
|
262
|
+
Object.assign(context, overridesOrCanvas);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return context;
|
|
267
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { globals } from 'webgpu';
|
|
3
|
+
|
|
4
|
+
export interface MockWebGPUContext {
|
|
5
|
+
adapter: GPUAdapter;
|
|
6
|
+
device: GPUDevice;
|
|
7
|
+
queue: GPUQueue;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Patches globalThis with WebGPU globals (GPUBufferUsage, etc.)
|
|
12
|
+
* and optionally patches navigator.gpu.
|
|
13
|
+
*/
|
|
14
|
+
export function setupWebGPUMocks() {
|
|
15
|
+
// 1. Inject globals like GPUBufferUsage, GPUTextureUsage
|
|
16
|
+
Object.assign(globalThis, globals);
|
|
17
|
+
|
|
18
|
+
// 2. Setup Navigator mock
|
|
19
|
+
const mockGpu = {
|
|
20
|
+
requestAdapter: vi.fn(),
|
|
21
|
+
getPreferredCanvasFormat: vi.fn().mockReturnValue('bgra8unorm'),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Create mocks for Adapter and Device
|
|
25
|
+
const mockAdapter = createMockGPUAdapter();
|
|
26
|
+
const mockDevice = createMockGPUDevice();
|
|
27
|
+
|
|
28
|
+
// Wire them up
|
|
29
|
+
mockGpu.requestAdapter.mockResolvedValue(mockAdapter);
|
|
30
|
+
// @ts-ignore - vitest mock manipulation
|
|
31
|
+
mockAdapter.requestDevice.mockResolvedValue(mockDevice);
|
|
32
|
+
|
|
33
|
+
if (!globalThis.navigator) {
|
|
34
|
+
// @ts-ignore
|
|
35
|
+
globalThis.navigator = {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Safely redefine navigator.gpu
|
|
39
|
+
try {
|
|
40
|
+
// If it exists and is configurable, define it.
|
|
41
|
+
// If it doesn't exist, define it.
|
|
42
|
+
// If it exists and is NOT configurable, we can't do much (but that shouldn't happen in our test env if we control it)
|
|
43
|
+
Object.defineProperty(globalThis.navigator, 'gpu', {
|
|
44
|
+
value: mockGpu,
|
|
45
|
+
writable: true,
|
|
46
|
+
configurable: true
|
|
47
|
+
});
|
|
48
|
+
} catch (e) {
|
|
49
|
+
// Fallback: simple assignment if defineProperty fails (e.g. some JSDOM quirks)
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
globalThis.navigator.gpu = mockGpu;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
mockGpu,
|
|
56
|
+
mockAdapter,
|
|
57
|
+
mockDevice
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createMockGPUAdapter(options: Partial<GPUAdapter> = {}): GPUAdapter {
|
|
62
|
+
return {
|
|
63
|
+
features: new Set(),
|
|
64
|
+
limits: {},
|
|
65
|
+
isFallbackAdapter: false,
|
|
66
|
+
requestDevice: vi.fn().mockResolvedValue(createMockGPUDevice()),
|
|
67
|
+
requestAdapterInfo: vi.fn().mockResolvedValue({}),
|
|
68
|
+
...options,
|
|
69
|
+
} as unknown as GPUAdapter;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function createMockGPUDevice(features: Set<GPUFeatureName> = new Set()): GPUDevice {
|
|
73
|
+
const queue = createMockQueue();
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
features,
|
|
77
|
+
limits: {},
|
|
78
|
+
queue,
|
|
79
|
+
destroy: vi.fn(),
|
|
80
|
+
createBuffer: vi.fn((descriptor: GPUBufferDescriptor) => createMockGPUBuffer(descriptor)),
|
|
81
|
+
createTexture: vi.fn((descriptor: GPUTextureDescriptor) => createMockGPUTexture(descriptor)),
|
|
82
|
+
createSampler: vi.fn(() => createMockSampler()),
|
|
83
|
+
createBindGroupLayout: vi.fn(() => ({ label: 'mock-bind-group-layout' })),
|
|
84
|
+
createPipelineLayout: vi.fn(() => ({ label: 'mock-pipeline-layout' })),
|
|
85
|
+
createBindGroup: vi.fn(() => ({ label: 'mock-bind-group' })),
|
|
86
|
+
createShaderModule: vi.fn((descriptor: GPUShaderModuleDescriptor) => createMockShaderModule(descriptor)),
|
|
87
|
+
createComputePipeline: vi.fn(() => createMockComputePipeline()),
|
|
88
|
+
createRenderPipeline: vi.fn(() => createMockRenderPipeline()),
|
|
89
|
+
createComputePipelineAsync: vi.fn().mockResolvedValue(createMockComputePipeline()),
|
|
90
|
+
createRenderPipelineAsync: vi.fn().mockResolvedValue(createMockRenderPipeline()),
|
|
91
|
+
createCommandEncoder: vi.fn(() => createMockCommandEncoder()),
|
|
92
|
+
createQuerySet: vi.fn(() => ({ label: 'mock-query-set' })),
|
|
93
|
+
pushErrorScope: vi.fn(),
|
|
94
|
+
popErrorScope: vi.fn().mockResolvedValue(null),
|
|
95
|
+
addEventListener: vi.fn(),
|
|
96
|
+
removeEventListener: vi.fn(),
|
|
97
|
+
dispatchEvent: vi.fn(),
|
|
98
|
+
onuncapturederror: null,
|
|
99
|
+
label: '',
|
|
100
|
+
lost: Promise.resolve({ reason: 'destroyed', message: 'Device lost' }),
|
|
101
|
+
} as unknown as GPUDevice;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function createMockQueue(): GPUQueue {
|
|
105
|
+
return {
|
|
106
|
+
submit: vi.fn(),
|
|
107
|
+
onSubmittedWorkDone: vi.fn().mockResolvedValue(undefined),
|
|
108
|
+
writeBuffer: vi.fn(),
|
|
109
|
+
writeTexture: vi.fn(),
|
|
110
|
+
copyExternalImageToTexture: vi.fn(),
|
|
111
|
+
label: '',
|
|
112
|
+
} as unknown as GPUQueue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function createMockGPUBuffer(descriptor: GPUBufferDescriptor): GPUBuffer {
|
|
116
|
+
return {
|
|
117
|
+
size: descriptor.size,
|
|
118
|
+
usage: descriptor.usage,
|
|
119
|
+
mapState: 'unmapped',
|
|
120
|
+
mapAsync: vi.fn().mockResolvedValue(undefined),
|
|
121
|
+
getMappedRange: vi.fn(() => new ArrayBuffer(descriptor.size)),
|
|
122
|
+
unmap: vi.fn(),
|
|
123
|
+
destroy: vi.fn(),
|
|
124
|
+
label: descriptor.label || '',
|
|
125
|
+
} as unknown as GPUBuffer;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function createMockGPUTexture(descriptor: GPUTextureDescriptor): GPUTexture {
|
|
129
|
+
const size = descriptor.size;
|
|
130
|
+
let width = 0;
|
|
131
|
+
let height = 0;
|
|
132
|
+
let depthOrArrayLayers = 1;
|
|
133
|
+
|
|
134
|
+
if (Array.isArray(size) || size instanceof Float32Array || size instanceof Uint32Array) {
|
|
135
|
+
// Iterable<number>
|
|
136
|
+
const arr = Array.from(size as Iterable<number>);
|
|
137
|
+
width = arr[0] || 0;
|
|
138
|
+
height = arr[1] || 1;
|
|
139
|
+
depthOrArrayLayers = arr[2] || 1;
|
|
140
|
+
} else if (typeof size === 'object') {
|
|
141
|
+
// GPUExtent3DDict
|
|
142
|
+
const dict = size as GPUExtent3DDict;
|
|
143
|
+
width = dict.width;
|
|
144
|
+
height = dict.height || 1;
|
|
145
|
+
depthOrArrayLayers = dict.depthOrArrayLayers || 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
width,
|
|
150
|
+
height,
|
|
151
|
+
depthOrArrayLayers,
|
|
152
|
+
mipLevelCount: descriptor.mipLevelCount || 1,
|
|
153
|
+
sampleCount: descriptor.sampleCount || 1,
|
|
154
|
+
dimension: descriptor.dimension || '2d',
|
|
155
|
+
format: descriptor.format,
|
|
156
|
+
usage: descriptor.usage,
|
|
157
|
+
createView: vi.fn(() => createMockTextureView()),
|
|
158
|
+
destroy: vi.fn(),
|
|
159
|
+
label: descriptor.label || '',
|
|
160
|
+
} as unknown as GPUTexture;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function createMockTextureView(): GPUTextureView {
|
|
164
|
+
return {
|
|
165
|
+
label: '',
|
|
166
|
+
} as unknown as GPUTextureView;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function createMockSampler(): GPUSampler {
|
|
170
|
+
return {
|
|
171
|
+
label: '',
|
|
172
|
+
} as unknown as GPUSampler;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function createMockShaderModule(descriptor: GPUShaderModuleDescriptor): GPUShaderModule {
|
|
176
|
+
return {
|
|
177
|
+
getCompilationInfo: vi.fn().mockResolvedValue({ messages: [] }),
|
|
178
|
+
label: descriptor.label || '',
|
|
179
|
+
} as unknown as GPUShaderModule;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function createMockComputePipeline(): GPUComputePipeline {
|
|
183
|
+
return {
|
|
184
|
+
getBindGroupLayout: vi.fn(() => ({ label: 'mock-bind-group-layout' })),
|
|
185
|
+
label: '',
|
|
186
|
+
} as unknown as GPUComputePipeline;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function createMockRenderPipeline(): GPURenderPipeline {
|
|
190
|
+
return {
|
|
191
|
+
getBindGroupLayout: vi.fn(() => ({ label: 'mock-bind-group-layout' })),
|
|
192
|
+
label: '',
|
|
193
|
+
} as unknown as GPURenderPipeline;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function createMockCommandEncoder(): GPUCommandEncoder {
|
|
197
|
+
return {
|
|
198
|
+
beginRenderPass: vi.fn(() => createMockRenderPassEncoder()),
|
|
199
|
+
beginComputePass: vi.fn(() => createMockComputePassEncoder()),
|
|
200
|
+
copyBufferToBuffer: vi.fn(),
|
|
201
|
+
copyBufferToTexture: vi.fn(),
|
|
202
|
+
copyTextureToBuffer: vi.fn(),
|
|
203
|
+
copyTextureToTexture: vi.fn(),
|
|
204
|
+
clearBuffer: vi.fn(),
|
|
205
|
+
writeTimestamp: vi.fn(),
|
|
206
|
+
resolveQuerySet: vi.fn(),
|
|
207
|
+
finish: vi.fn(() => ({ label: 'mock-command-buffer' })),
|
|
208
|
+
pushDebugGroup: vi.fn(),
|
|
209
|
+
popDebugGroup: vi.fn(),
|
|
210
|
+
insertDebugMarker: vi.fn(),
|
|
211
|
+
label: '',
|
|
212
|
+
} as unknown as GPUCommandEncoder;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function createMockRenderPassEncoder(): GPURenderPassEncoder {
|
|
216
|
+
return {
|
|
217
|
+
setPipeline: vi.fn(),
|
|
218
|
+
setIndexBuffer: vi.fn(),
|
|
219
|
+
setVertexBuffer: vi.fn(),
|
|
220
|
+
setBindGroup: vi.fn(),
|
|
221
|
+
setViewport: vi.fn(),
|
|
222
|
+
setScissorRect: vi.fn(),
|
|
223
|
+
setBlendConstant: vi.fn(),
|
|
224
|
+
setStencilReference: vi.fn(),
|
|
225
|
+
beginOcclusionQuery: vi.fn(),
|
|
226
|
+
endOcclusionQuery: vi.fn(),
|
|
227
|
+
executeBundles: vi.fn(),
|
|
228
|
+
draw: vi.fn(),
|
|
229
|
+
drawIndexed: vi.fn(),
|
|
230
|
+
drawIndirect: vi.fn(),
|
|
231
|
+
drawIndexedIndirect: vi.fn(),
|
|
232
|
+
end: vi.fn(),
|
|
233
|
+
pushDebugGroup: vi.fn(),
|
|
234
|
+
popDebugGroup: vi.fn(),
|
|
235
|
+
insertDebugMarker: vi.fn(),
|
|
236
|
+
label: '',
|
|
237
|
+
} as unknown as GPURenderPassEncoder;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function createMockComputePassEncoder(): GPUComputePassEncoder {
|
|
241
|
+
return {
|
|
242
|
+
setPipeline: vi.fn(),
|
|
243
|
+
setBindGroup: vi.fn(),
|
|
244
|
+
dispatchWorkgroups: vi.fn(),
|
|
245
|
+
dispatchWorkgroupsIndirect: vi.fn(),
|
|
246
|
+
end: vi.fn(),
|
|
247
|
+
pushDebugGroup: vi.fn(),
|
|
248
|
+
popDebugGroup: vi.fn(),
|
|
249
|
+
insertDebugMarker: vi.fn(),
|
|
250
|
+
label: '',
|
|
251
|
+
} as unknown as GPUComputePassEncoder;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function createMockWebGPUContext(): MockWebGPUContext {
|
|
255
|
+
const adapter = createMockGPUAdapter();
|
|
256
|
+
const device = createMockGPUDevice();
|
|
257
|
+
return {
|
|
258
|
+
adapter,
|
|
259
|
+
device,
|
|
260
|
+
queue: device.queue,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { createMockWebGL2Context, MockWebGL2RenderingContext } from './mocks/webgl.js';
|
|
3
|
+
|
|
4
|
+
// Define minimal mock interfaces for internal engine components not publicly exported.
|
|
5
|
+
interface MockPipeline {
|
|
6
|
+
render: ReturnType<typeof vi.fn>;
|
|
7
|
+
init: ReturnType<typeof vi.fn>;
|
|
8
|
+
resize: ReturnType<typeof vi.fn>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface MockCamera {
|
|
12
|
+
update: ReturnType<typeof vi.fn>;
|
|
13
|
+
getViewMatrix: ReturnType<typeof vi.fn>;
|
|
14
|
+
getProjectionMatrix: ReturnType<typeof vi.fn>;
|
|
15
|
+
getViewProjectionMatrix: ReturnType<typeof vi.fn>;
|
|
16
|
+
getPosition: ReturnType<typeof vi.fn>;
|
|
17
|
+
getForward: ReturnType<typeof vi.fn>;
|
|
18
|
+
getRight: ReturnType<typeof vi.fn>;
|
|
19
|
+
getUp: ReturnType<typeof vi.fn>;
|
|
20
|
+
extractFrustumPlanes: ReturnType<typeof vi.fn>;
|
|
21
|
+
transform: {
|
|
22
|
+
origin: number[];
|
|
23
|
+
angles: number[];
|
|
24
|
+
fov: number;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MockRenderingContext {
|
|
29
|
+
gl: MockWebGL2RenderingContext;
|
|
30
|
+
camera: MockCamera;
|
|
31
|
+
pipelines: {
|
|
32
|
+
md2: MockPipeline;
|
|
33
|
+
bsp: MockPipeline;
|
|
34
|
+
sprite: MockPipeline;
|
|
35
|
+
poly: MockPipeline;
|
|
36
|
+
particle: MockPipeline;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createMockRenderingContext(): MockRenderingContext {
|
|
41
|
+
const gl = createMockWebGL2Context();
|
|
42
|
+
|
|
43
|
+
// Mock Camera
|
|
44
|
+
const camera: MockCamera = {
|
|
45
|
+
update: vi.fn(),
|
|
46
|
+
getViewMatrix: vi.fn().mockReturnValue(new Float32Array(16)),
|
|
47
|
+
getProjectionMatrix: vi.fn().mockReturnValue(new Float32Array(16)),
|
|
48
|
+
getViewProjectionMatrix: vi.fn().mockReturnValue(new Float32Array(16)),
|
|
49
|
+
getPosition: vi.fn().mockReturnValue([0, 0, 0]),
|
|
50
|
+
getForward: vi.fn().mockReturnValue([0, 0, -1]),
|
|
51
|
+
getRight: vi.fn().mockReturnValue([1, 0, 0]),
|
|
52
|
+
getUp: vi.fn().mockReturnValue([0, 1, 0]),
|
|
53
|
+
extractFrustumPlanes: vi.fn(),
|
|
54
|
+
transform: {
|
|
55
|
+
origin: [0, 0, 0],
|
|
56
|
+
angles: [0, 0, 0],
|
|
57
|
+
fov: 90
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Mock Pipelines
|
|
62
|
+
const md2: MockPipeline = {
|
|
63
|
+
render: vi.fn(),
|
|
64
|
+
init: vi.fn(),
|
|
65
|
+
resize: vi.fn(),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const bsp: MockPipeline = {
|
|
69
|
+
render: vi.fn(),
|
|
70
|
+
init: vi.fn(),
|
|
71
|
+
resize: vi.fn(),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const sprite: MockPipeline = {
|
|
75
|
+
render: vi.fn(),
|
|
76
|
+
init: vi.fn(),
|
|
77
|
+
resize: vi.fn(),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const poly: MockPipeline = {
|
|
81
|
+
render: vi.fn(),
|
|
82
|
+
init: vi.fn(),
|
|
83
|
+
resize: vi.fn(),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const particle: MockPipeline = {
|
|
87
|
+
render: vi.fn(),
|
|
88
|
+
init: vi.fn(),
|
|
89
|
+
resize: vi.fn(),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
gl,
|
|
94
|
+
camera,
|
|
95
|
+
pipelines: {
|
|
96
|
+
md2,
|
|
97
|
+
bsp,
|
|
98
|
+
sprite,
|
|
99
|
+
poly,
|
|
100
|
+
particle
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|