gpu-atlas 0.1.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 +21 -0
- package/README.md +248 -0
- package/dist/gpu-atlas.js +1938 -0
- package/dist/gpu-atlas.js.map +1 -0
- package/dist/index.d.ts +398 -0
- package/package.json +59 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
export declare interface AdapterIdentity {
|
|
2
|
+
vendor: string;
|
|
3
|
+
architecture: string;
|
|
4
|
+
device: string;
|
|
5
|
+
description: string;
|
|
6
|
+
/** A software adapter — every performance number means something else entirely */
|
|
7
|
+
isFallbackAdapter: boolean;
|
|
8
|
+
/** The powerPreference passed to requestAdapter */
|
|
9
|
+
powerPreference: GPUPowerPreference | 'default';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare interface AtlasProfile {
|
|
13
|
+
schema: number;
|
|
14
|
+
capturedAt: string;
|
|
15
|
+
/**
|
|
16
|
+
* Stable hash grouping the same device + browser combination. 32 hex
|
|
17
|
+
* characters — wide enough that collisions stay negligible across a large
|
|
18
|
+
* collection of profiles, which an 8-character hash was not.
|
|
19
|
+
*/
|
|
20
|
+
fingerprint: string;
|
|
21
|
+
environment: EnvironmentInfo;
|
|
22
|
+
adapter: AdapterIdentity | null;
|
|
23
|
+
declared: DeclaredCapabilities | null;
|
|
24
|
+
verified: VerifiedCapabilities | null;
|
|
25
|
+
benchmarks: BenchmarkResults | null;
|
|
26
|
+
discrepancies: Discrepancy[];
|
|
27
|
+
/** Why WebGPU could not be used at all, when that is the case */
|
|
28
|
+
unavailable?: string;
|
|
29
|
+
/** Total time the probe took */
|
|
30
|
+
elapsedMs: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export declare interface BenchDiff {
|
|
34
|
+
id: string;
|
|
35
|
+
description: string;
|
|
36
|
+
unit: string;
|
|
37
|
+
/** fingerprint -> throughput, null when the benchmark did not produce one */
|
|
38
|
+
values: Record<string, number | null>;
|
|
39
|
+
fastest: string | null;
|
|
40
|
+
slowest: string | null;
|
|
41
|
+
/** fastest / slowest */
|
|
42
|
+
ratio: number | null;
|
|
43
|
+
/**
|
|
44
|
+
* Devices whose measurement should not be trusted for this benchmark, either
|
|
45
|
+
* because it sat on the quantization floor or because it was too unstable.
|
|
46
|
+
* A ratio computed against these is not meaningful.
|
|
47
|
+
*/
|
|
48
|
+
unreliable: string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Look up a benchmark result by id */
|
|
52
|
+
export declare function benchmark(profile: AtlasProfile, id: string): BenchResult | null;
|
|
53
|
+
|
|
54
|
+
export declare interface BenchmarkResults {
|
|
55
|
+
results: BenchResult[];
|
|
56
|
+
/** Whether timestamp-query was usable */
|
|
57
|
+
timestampQuery: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Measured granularity of the GPU timer, in nanoseconds. Browsers round
|
|
60
|
+
* timestamps into coarse buckets as a Spectre mitigation and the bucket size
|
|
61
|
+
* differs per browser and device, so it is measured rather than assumed.
|
|
62
|
+
*/
|
|
63
|
+
timerResolutionNs: number | null;
|
|
64
|
+
/**
|
|
65
|
+
* Measured granularity of performance.now(), in milliseconds. Safari rounds
|
|
66
|
+
* to 1ms, which bounds how precisely the wall-clock benchmarks can be read.
|
|
67
|
+
*/
|
|
68
|
+
wallClockResolutionMs: number | null;
|
|
69
|
+
/** Wall-clock time the whole benchmark suite took */
|
|
70
|
+
totalMs: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export declare interface BenchResult {
|
|
74
|
+
id: string;
|
|
75
|
+
description: string;
|
|
76
|
+
/** Representative value (median), in ms */
|
|
77
|
+
medianMs: number;
|
|
78
|
+
/** Lowest sample — a noise-free floor */
|
|
79
|
+
minMs: number;
|
|
80
|
+
/** Coefficient of variation. High means this number should not be trusted */
|
|
81
|
+
variation: number;
|
|
82
|
+
/** Throughput in the unit this benchmark defines (draws/s, MPixel/s, ...) */
|
|
83
|
+
throughput?: number;
|
|
84
|
+
throughputUnit?: string;
|
|
85
|
+
/** Whether this came from GPU timestamps or the wall clock */
|
|
86
|
+
timing: 'timestamp-query' | 'wall-clock';
|
|
87
|
+
samples: number;
|
|
88
|
+
/** How many unit workloads were run per sample after auto-scaling */
|
|
89
|
+
repetitions: number;
|
|
90
|
+
/**
|
|
91
|
+
* Measured duration expressed in timer resolution units. Low values mean the
|
|
92
|
+
* number is riding on the quantization floor and carries little information,
|
|
93
|
+
* which is the one case where a variation of 0 must not be read as stability.
|
|
94
|
+
*/
|
|
95
|
+
ticks?: number;
|
|
96
|
+
/** The measurement sits too close to the timer resolution to be trusted */
|
|
97
|
+
quantized?: boolean;
|
|
98
|
+
failed?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Only the problems that will actually break code */
|
|
102
|
+
export declare function breakingIssues(profile: AtlasProfile): Discrepancy[];
|
|
103
|
+
|
|
104
|
+
export declare function compareProfiles(profiles: AtlasProfile[]): Comparison;
|
|
105
|
+
|
|
106
|
+
export declare interface Comparison {
|
|
107
|
+
devices: DeviceRef[];
|
|
108
|
+
/** Features present on some devices but not others */
|
|
109
|
+
features: FeatureDiff[];
|
|
110
|
+
/** Features every compared device has */
|
|
111
|
+
sharedFeatures: string[];
|
|
112
|
+
formats: FormatDiff[];
|
|
113
|
+
limits: LimitDiff[];
|
|
114
|
+
benchmarks: BenchDiff[];
|
|
115
|
+
/** Profiles that could not be compared, and why */
|
|
116
|
+
excluded: Array<{
|
|
117
|
+
index: number;
|
|
118
|
+
reason: string;
|
|
119
|
+
}>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export declare interface DeclaredCapabilities {
|
|
123
|
+
features: string[];
|
|
124
|
+
limits: Record<string, number>;
|
|
125
|
+
/** navigator.gpu.getPreferredCanvasFormat() */
|
|
126
|
+
preferredCanvasFormat: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export declare function describeDevice(p: AtlasProfile): string;
|
|
130
|
+
|
|
131
|
+
export declare interface DeviceRef {
|
|
132
|
+
fingerprint: string;
|
|
133
|
+
/** Human-readable identity, e.g. "qualcomm adreno-7xx / Samsung Internet 30" */
|
|
134
|
+
label: string;
|
|
135
|
+
mobile: boolean;
|
|
136
|
+
/** Index into the input array, for callers that need to get back to it */
|
|
137
|
+
index: number;
|
|
138
|
+
/** Schema version the profile was captured under */
|
|
139
|
+
schema: number;
|
|
140
|
+
/**
|
|
141
|
+
* The profile predates the schema that made measurement trustworthiness
|
|
142
|
+
* explicit, so its benchmark numbers are reported but never treated as
|
|
143
|
+
* reliable. Capability data from such profiles is still comparable.
|
|
144
|
+
*/
|
|
145
|
+
staleBenchmarks: boolean;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export declare interface Discrepancy {
|
|
149
|
+
kind: DiscrepancyKind;
|
|
150
|
+
/** Where it occurred — a format name, limit name, or shader case id */
|
|
151
|
+
subject: string;
|
|
152
|
+
detail: string;
|
|
153
|
+
/** 'breaking' means code relying on the declaration will fail on this device */
|
|
154
|
+
severity: 'breaking' | 'degraded' | 'note';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export declare type DiscrepancyKind = 'format-declared-not-usable' | 'format-usable-not-declared' | 'limit-not-honored' | 'shader-compile-failure' | 'shader-pipeline-failure' | 'performance-cliff';
|
|
158
|
+
|
|
159
|
+
export declare interface EnvironmentInfo {
|
|
160
|
+
/** From UA-CH when available */
|
|
161
|
+
platform?: string;
|
|
162
|
+
/** 'Chrome' | 'Firefox' | 'Safari' | 'Edge' | 'unknown' */
|
|
163
|
+
browser: string;
|
|
164
|
+
browserVersion: string;
|
|
165
|
+
/** Prefers the UA-CH mobile hint, falls back to UA sniffing */
|
|
166
|
+
mobile: boolean;
|
|
167
|
+
deviceMemoryGB?: number;
|
|
168
|
+
hardwareConcurrency?: number;
|
|
169
|
+
devicePixelRatio: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export declare interface FeatureDiff {
|
|
173
|
+
feature: string;
|
|
174
|
+
supportedBy: string[];
|
|
175
|
+
missingFrom: string[];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export declare function findMeta(format: string): FormatMeta | undefined;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Render a comparison as plain text. Useful for dropping into an issue, a
|
|
182
|
+
* README, or a terminal without building a table by hand.
|
|
183
|
+
*/
|
|
184
|
+
export declare function formatComparison(c: Comparison, options?: FormatOptions): string;
|
|
185
|
+
|
|
186
|
+
/** A texture capability that is not uniform across the compared devices */
|
|
187
|
+
export declare interface FormatDiff {
|
|
188
|
+
format: string;
|
|
189
|
+
capability: 'creatable' | 'sampleable' | 'renderable' | 'blendable' | 'storageWritable' | 'multisample4x';
|
|
190
|
+
supportedBy: string[];
|
|
191
|
+
missingFrom: string[];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export declare type FormatKind = 'color' | 'depth' | 'compressed';
|
|
195
|
+
|
|
196
|
+
export declare interface FormatMeta {
|
|
197
|
+
format: string;
|
|
198
|
+
kind: FormatKind;
|
|
199
|
+
/** Texel type of the WGSL texture */
|
|
200
|
+
texel: 'f32' | 'u32' | 'i32';
|
|
201
|
+
/** Whether a filtering sampler may be attached (decides bindGroupLayout sampleType) */
|
|
202
|
+
filterable: boolean;
|
|
203
|
+
/** The device feature this format requires */
|
|
204
|
+
requiresFeature?: string;
|
|
205
|
+
/** Block size for compressed formats — createTexture must be a multiple of it */
|
|
206
|
+
block?: [number, number];
|
|
207
|
+
hasDepth?: boolean;
|
|
208
|
+
hasStencil?: boolean;
|
|
209
|
+
/** What the spec says to expect */
|
|
210
|
+
expect: {
|
|
211
|
+
renderable: boolean;
|
|
212
|
+
blendable: boolean;
|
|
213
|
+
storage: boolean;
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export declare interface FormatOptions {
|
|
218
|
+
/**
|
|
219
|
+
* Rows to print per section before summarising the rest. Sections are sorted
|
|
220
|
+
* worst-first, so the truncated tail is the least interesting part — but a
|
|
221
|
+
* hundred devices would otherwise produce thousands of lines.
|
|
222
|
+
*/
|
|
223
|
+
limit?: number;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export declare const FORMATS: FormatMeta[];
|
|
227
|
+
|
|
228
|
+
/** Whether a texture format actually works for each usage */
|
|
229
|
+
export declare interface FormatSupport {
|
|
230
|
+
format: string;
|
|
231
|
+
/** The device feature this format requires, if any */
|
|
232
|
+
requiresFeature?: string;
|
|
233
|
+
/** Whether that feature appears in declared.features */
|
|
234
|
+
featureDeclared: boolean;
|
|
235
|
+
/** createTexture succeeds */
|
|
236
|
+
creatable: boolean;
|
|
237
|
+
/** Readable from a shader via TEXTURE_BINDING */
|
|
238
|
+
sampleable: boolean;
|
|
239
|
+
/** A render pass actually runs against it via RENDER_ATTACHMENT */
|
|
240
|
+
renderable: boolean;
|
|
241
|
+
/** Blending works when used as a render target */
|
|
242
|
+
blendable: boolean;
|
|
243
|
+
/** Writable from a compute shader via STORAGE_BINDING */
|
|
244
|
+
storageWritable: boolean;
|
|
245
|
+
/** Works as a 4x MSAA render target */
|
|
246
|
+
multisample4x: boolean;
|
|
247
|
+
/** Errors captured at each stage, for diagnosis */
|
|
248
|
+
errors: ProbeError[];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Whether WebGPU could not be used at all */
|
|
252
|
+
export declare function isUnavailable(profile: AtlasProfile): boolean;
|
|
253
|
+
|
|
254
|
+
export declare interface LimitDiff {
|
|
255
|
+
limit: string;
|
|
256
|
+
/** fingerprint -> value actually achieved */
|
|
257
|
+
values: Record<string, number>;
|
|
258
|
+
min: number;
|
|
259
|
+
max: number;
|
|
260
|
+
/** max / min — how far apart the devices are */
|
|
261
|
+
ratio: number;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Whether a declared limit can actually be used up to its stated value */
|
|
265
|
+
export declare interface LimitProbe {
|
|
266
|
+
limit: string;
|
|
267
|
+
declared: number;
|
|
268
|
+
/** Highest value that actually allocated, found by bisection */
|
|
269
|
+
achieved: number;
|
|
270
|
+
/** achieved < declared means the declaration was not honored */
|
|
271
|
+
honored: boolean;
|
|
272
|
+
error?: ProbeError;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Below this, benchmark numbers cannot be trusted for comparison: version 1
|
|
277
|
+
* profiles carry no quantization information, and their overdraw benchmarks
|
|
278
|
+
* were architecture-dependent.
|
|
279
|
+
*/
|
|
280
|
+
export declare const MIN_COMPARABLE_BENCHMARK_SCHEMA = 2;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Pick the first candidate this device actually supports.
|
|
284
|
+
* The result is measured rather than declared, so whatever comes back works.
|
|
285
|
+
*/
|
|
286
|
+
export declare function pickFormat(profile: AtlasProfile, candidates: string[], usage?: 'render' | 'sample' | 'storage'): string | null;
|
|
287
|
+
|
|
288
|
+
export declare function probe(options?: ProbeOptions): Promise<AtlasProfile>;
|
|
289
|
+
|
|
290
|
+
declare interface ProbeError {
|
|
291
|
+
kind: ProbeErrorKind;
|
|
292
|
+
message: string;
|
|
293
|
+
stage?: ProbeStage;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Why something failed. `validation` and `out-of-memory` come straight from
|
|
298
|
+
* WebGPU error scopes; `exception` means the call threw synchronously, which is
|
|
299
|
+
* what browsers do for a format they do not recognise at all.
|
|
300
|
+
*/
|
|
301
|
+
declare type ProbeErrorKind = 'validation' | 'out-of-memory' | 'internal' | 'exception' | 'queue' | 'scope-unavailable';
|
|
302
|
+
|
|
303
|
+
export declare interface ProbeOptions {
|
|
304
|
+
/** Which GPU to ask for. On laptops this decides which chip you get */
|
|
305
|
+
powerPreference?: GPUPowerPreference;
|
|
306
|
+
/** Run benchmarks. false leaves only capability verification, which is fast */
|
|
307
|
+
benchmark?: boolean;
|
|
308
|
+
/** Samples per benchmark. More is more accurate and slower. Clamped to 1-99 */
|
|
309
|
+
benchSamples?: number;
|
|
310
|
+
/** Progress callback */
|
|
311
|
+
onProgress?: (stage: string, ratio: number) => void;
|
|
312
|
+
/** Verify only these texture formats (default: the full built-in list) */
|
|
313
|
+
formats?: string[];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Which check was running when an error surfaced */
|
|
317
|
+
declare type ProbeStage = 'create' | 'sample' | 'render' | 'blend' | 'storage' | 'msaa4x' | 'limit';
|
|
318
|
+
|
|
319
|
+
/** Formats verified to work as render targets on this device */
|
|
320
|
+
export declare function renderableFormats(profile: AtlasProfile): string[];
|
|
321
|
+
|
|
322
|
+
/** Formats verified to be readable from a shader */
|
|
323
|
+
export declare function sampleableFormats(profile: AtlasProfile): string[];
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Profile schema version.
|
|
327
|
+
*
|
|
328
|
+
* Collecting profiles is the point of this project, so a profile has to say
|
|
329
|
+
* honestly what shape it is. Bump this whenever a field is added, removed, or
|
|
330
|
+
* changes meaning, and record why below — consumers compare against it to know
|
|
331
|
+
* which fields they can rely on.
|
|
332
|
+
*
|
|
333
|
+
* 4 — EnvironmentInfo.userAgent removed.
|
|
334
|
+
* Browser, version, platform and mobile are already parsed into their own
|
|
335
|
+
* fields, so the raw string was duplicate data — and a fingerprinting
|
|
336
|
+
* vector. A project that asks people to share profiles should not ship
|
|
337
|
+
* identifying information it does not use.
|
|
338
|
+
*
|
|
339
|
+
* 3 — Errors became structured, and fingerprints got wider.
|
|
340
|
+
* Wall-clock benchmarks also changed: they now scale to 60 ticks of the
|
|
341
|
+
* measured performance.now() granularity and drop their extreme samples,
|
|
342
|
+
* so those figures shift by a few percent against version 2. GPU-timed
|
|
343
|
+
* benchmarks are unaffected, which is why version 2 profiles remain
|
|
344
|
+
* comparable rather than being cut off.
|
|
345
|
+
* ~ FormatSupport.errors and LimitProbe.error are now ProbeError objects
|
|
346
|
+
* rather than preformatted strings, so a consumer can branch on why
|
|
347
|
+
* something failed without matching on message text.
|
|
348
|
+
* ~ AtlasProfile.fingerprint is a 32-character hash instead of 8. The old
|
|
349
|
+
* 32-bit value collided at a rate that mattered once profiles were being
|
|
350
|
+
* collected in bulk.
|
|
351
|
+
*
|
|
352
|
+
* 2 — Measurement trustworthiness became explicit.
|
|
353
|
+
* + BenchResult.repetitions how far auto-scaling pushed each benchmark
|
|
354
|
+
* + BenchResult.ticks duration in timer-resolution units
|
|
355
|
+
* + BenchResult.quantized sitting on the quantization floor
|
|
356
|
+
* + BenchmarkResults.timerResolutionNs measured GPU timer granularity
|
|
357
|
+
* + BenchmarkResults.wallClockResolutionMs measured performance.now() granularity
|
|
358
|
+
* + ShaderCase.skipped previously inferred from message text
|
|
359
|
+
* Benchmarks also changed: the overdraw ones now blend additively, so their
|
|
360
|
+
* numbers are not comparable with version 1 readings taken on tile-based
|
|
361
|
+
* deferred GPUs, where the unblended version measured almost nothing.
|
|
362
|
+
*
|
|
363
|
+
* 1 — Initial schema.
|
|
364
|
+
*/
|
|
365
|
+
export declare const SCHEMA_VERSION = 4;
|
|
366
|
+
|
|
367
|
+
/** Result of one WGSL compilation case */
|
|
368
|
+
export declare interface ShaderCase {
|
|
369
|
+
id: string;
|
|
370
|
+
/** What this case is probing for */
|
|
371
|
+
description: string;
|
|
372
|
+
/** Skipped because a required feature is missing */
|
|
373
|
+
skipped: boolean;
|
|
374
|
+
compiled: boolean;
|
|
375
|
+
/** Compiling can succeed while pipeline creation still fails — it happens */
|
|
376
|
+
pipelineCreated: boolean;
|
|
377
|
+
/** Warnings and errors from getCompilationInfo() */
|
|
378
|
+
messages: ShaderMessage[];
|
|
379
|
+
/** Compile time in ms. Identifies devices with slow shader compilation */
|
|
380
|
+
compileMs: number;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export declare interface ShaderMessage {
|
|
384
|
+
type: 'error' | 'warning' | 'info';
|
|
385
|
+
message: string;
|
|
386
|
+
lineNum: number;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export declare interface VerifiedCapabilities {
|
|
390
|
+
formats: FormatSupport[];
|
|
391
|
+
shaders: ShaderCase[];
|
|
392
|
+
limits: LimitProbe[];
|
|
393
|
+
/** The device died partway through — everything after that is meaningless */
|
|
394
|
+
deviceLost: boolean;
|
|
395
|
+
deviceLostReason?: string;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export { }
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gpu-atlas",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "What WebGPU actually does on this device — verified by creating the resources and running the passes, not by reading declared limits.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"webgpu",
|
|
7
|
+
"gpu",
|
|
8
|
+
"capabilities",
|
|
9
|
+
"benchmark",
|
|
10
|
+
"compatibility",
|
|
11
|
+
"graphics",
|
|
12
|
+
"rendering"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "ahnminjae08043-glitch (https://github.com/ahnminjae08043-glitch)",
|
|
16
|
+
"homepage": "https://ahnminjae08043-glitch.github.io/gpu-atlas/",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/ahnminjae08043-glitch/gpu-atlas.git"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/ahnminjae08043-glitch/gpu-atlas/issues"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./dist/gpu-atlas.js",
|
|
26
|
+
"module": "./dist/gpu-atlas.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/gpu-atlas.js"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"sideEffects": false,
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"dev": "vite",
|
|
44
|
+
"build": "npm run typecheck && npm test && vite build",
|
|
45
|
+
"build:demo": "vite build --mode demo",
|
|
46
|
+
"preview": "vite preview",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:watch": "vitest"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
|
53
|
+
"@webgpu/types": "^0.1.60",
|
|
54
|
+
"typescript": "^5.7.2",
|
|
55
|
+
"vite": "^6.0.5",
|
|
56
|
+
"vite-plugin-dts": "^4.4.0",
|
|
57
|
+
"vitest": "^4.1.11"
|
|
58
|
+
}
|
|
59
|
+
}
|