@plasius/gpu-renderer 0.2.43 → 0.2.44
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/CHANGELOG.md +38 -1
- package/README.md +40 -3
- package/dist/index.cjs +154 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +154 -2
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/feedback-diagnostics.js +190 -0
- package/src/index.d.ts +75 -13
- package/src/index.js +6 -0
- package/src/wavefront-compute.js +4 -0
- package/src/wavefront-gpu-resources.js +8 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plasius/gpu-renderer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.44",
|
|
4
4
|
"description": "Framework-agnostic WebGPU renderer runtime for Plasius projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsup",
|
|
29
29
|
"demo": "python3 -m http.server --directory ..",
|
|
30
|
-
"typecheck": "node scripts/check-source-syntax.cjs",
|
|
30
|
+
"typecheck": "node scripts/check-source-syntax.cjs && tsc -p tsconfig.types.json && node scripts/verify-clean-type-consumer.cjs",
|
|
31
31
|
"audit:eslint": "eslint . --max-warnings=0",
|
|
32
32
|
"audit:deps": "npm ls --all --omit=optional --omit=peer > /dev/null 2>&1 || true",
|
|
33
33
|
"audit:npm": "npm audit --audit-level=high --omit=dev",
|
|
@@ -53,14 +53,15 @@
|
|
|
53
53
|
"license": "Apache-2.0",
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@plasius/gpu-camera": "^0.1.18",
|
|
56
|
-
"@plasius/gpu-shared": "^1.
|
|
57
|
-
"@plasius/gpu-xr": "^0.1.17"
|
|
56
|
+
"@plasius/gpu-shared": "^1.1.1",
|
|
57
|
+
"@plasius/gpu-xr": "^0.1.17",
|
|
58
|
+
"@types/webxr": "^0.5.24"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
60
61
|
"@eslint/js": "^10.0.1",
|
|
61
62
|
"c8": "^11.0.0",
|
|
62
|
-
"eslint": "^10.
|
|
63
|
-
"globals": "^17.
|
|
63
|
+
"eslint": "^10.9.1",
|
|
64
|
+
"globals": "^17.11.0",
|
|
64
65
|
"tsup": "^8.5.1",
|
|
65
66
|
"typescript": "^7.0.2"
|
|
66
67
|
},
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FEEDBACK_GAME_DIAGNOSTICS_CONTRACT_VERSION,
|
|
3
|
+
feedbackGameDiagnosticSurfaceRegistrations,
|
|
4
|
+
parseFeedbackGameDiagnostics,
|
|
5
|
+
} from "@plasius/gpu-shared/feedback-diagnostics";
|
|
6
|
+
|
|
7
|
+
const inputKeys = Object.freeze([
|
|
8
|
+
"featureEnabled",
|
|
9
|
+
"capabilityGranted",
|
|
10
|
+
"consentConfirmed",
|
|
11
|
+
"surfaceId",
|
|
12
|
+
"renderer",
|
|
13
|
+
"backend",
|
|
14
|
+
"viewportWidth",
|
|
15
|
+
"viewportHeight",
|
|
16
|
+
"frameRate",
|
|
17
|
+
"frameTimeMs",
|
|
18
|
+
"featureIds",
|
|
19
|
+
"counters",
|
|
20
|
+
"errorCodes",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export function bucketFeedbackGameViewport(width, height) {
|
|
24
|
+
if (
|
|
25
|
+
typeof width !== "number" ||
|
|
26
|
+
typeof height !== "number" ||
|
|
27
|
+
!Number.isFinite(width) ||
|
|
28
|
+
!Number.isFinite(height) ||
|
|
29
|
+
width <= 0 ||
|
|
30
|
+
height <= 0
|
|
31
|
+
) {
|
|
32
|
+
return "unknown";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const shorterEdge = Math.min(width, height);
|
|
36
|
+
const longerEdge = Math.max(width, height);
|
|
37
|
+
const size =
|
|
38
|
+
shorterEdge < 600
|
|
39
|
+
? "small"
|
|
40
|
+
: shorterEdge < 900 || longerEdge < 1_440
|
|
41
|
+
? "medium"
|
|
42
|
+
: "large";
|
|
43
|
+
const orientation = width > height ? "landscape" : "portrait";
|
|
44
|
+
return `${size}-${orientation}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function bucketFeedbackGameFrameRate(frameRate) {
|
|
48
|
+
if (
|
|
49
|
+
typeof frameRate !== "number" ||
|
|
50
|
+
!Number.isFinite(frameRate) ||
|
|
51
|
+
frameRate < 0
|
|
52
|
+
) {
|
|
53
|
+
return "unknown";
|
|
54
|
+
}
|
|
55
|
+
if (frameRate < 15) {
|
|
56
|
+
return "under-15";
|
|
57
|
+
}
|
|
58
|
+
if (frameRate < 30) {
|
|
59
|
+
return "15-29";
|
|
60
|
+
}
|
|
61
|
+
if (frameRate < 60) {
|
|
62
|
+
return "30-59";
|
|
63
|
+
}
|
|
64
|
+
return "60-plus";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function bucketFeedbackGameFrameTime(frameTimeMs) {
|
|
68
|
+
if (
|
|
69
|
+
typeof frameTimeMs !== "number" ||
|
|
70
|
+
!Number.isFinite(frameTimeMs) ||
|
|
71
|
+
frameTimeMs < 0
|
|
72
|
+
) {
|
|
73
|
+
return "unknown";
|
|
74
|
+
}
|
|
75
|
+
if (frameTimeMs < 17) {
|
|
76
|
+
return "under-17ms";
|
|
77
|
+
}
|
|
78
|
+
if (frameTimeMs < 34) {
|
|
79
|
+
return "17-33ms";
|
|
80
|
+
}
|
|
81
|
+
if (frameTimeMs < 67) {
|
|
82
|
+
return "34-66ms";
|
|
83
|
+
}
|
|
84
|
+
return "over-66ms";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createFeedbackGameDiagnosticSnapshot(input) {
|
|
88
|
+
try {
|
|
89
|
+
return createFeedbackGameDiagnosticSnapshotInternal(input);
|
|
90
|
+
} catch {
|
|
91
|
+
throw new TypeError("Invalid privacy-safe renderer diagnostics input.");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function createFeedbackGameDiagnosticSnapshotInternal(input) {
|
|
96
|
+
const snapshot = snapshotInputIfEnabled(input);
|
|
97
|
+
if (snapshot === null) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const registration =
|
|
102
|
+
typeof snapshot.surfaceId === "string" &&
|
|
103
|
+
Object.hasOwn(
|
|
104
|
+
feedbackGameDiagnosticSurfaceRegistrations,
|
|
105
|
+
snapshot.surfaceId
|
|
106
|
+
)
|
|
107
|
+
? feedbackGameDiagnosticSurfaceRegistrations[snapshot.surfaceId]
|
|
108
|
+
: undefined;
|
|
109
|
+
|
|
110
|
+
return parseFeedbackGameDiagnostics({
|
|
111
|
+
type: "feedback-game-diagnostics",
|
|
112
|
+
version: FEEDBACK_GAME_DIAGNOSTICS_CONTRACT_VERSION,
|
|
113
|
+
surfaceId: snapshot.surfaceId,
|
|
114
|
+
consentConfirmed: true,
|
|
115
|
+
provenanceContractId: registration?.provenanceContractId,
|
|
116
|
+
renderer: snapshot.renderer,
|
|
117
|
+
backend: snapshot.backend,
|
|
118
|
+
viewportBucket: bucketFeedbackGameViewport(
|
|
119
|
+
snapshot.viewportWidth,
|
|
120
|
+
snapshot.viewportHeight
|
|
121
|
+
),
|
|
122
|
+
frameRateBucket: bucketFeedbackGameFrameRate(snapshot.frameRate),
|
|
123
|
+
frameTimeBucket: bucketFeedbackGameFrameTime(snapshot.frameTimeMs),
|
|
124
|
+
featureIds: snapshot.featureIds,
|
|
125
|
+
counters: snapshot.counters,
|
|
126
|
+
errorCodes: snapshot.errorCodes,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function snapshotInputIfEnabled(input) {
|
|
131
|
+
assertValid(
|
|
132
|
+
input !== null &&
|
|
133
|
+
typeof input === "object" &&
|
|
134
|
+
!Array.isArray(input) &&
|
|
135
|
+
(Object.getPrototypeOf(input) === Object.prototype ||
|
|
136
|
+
Object.getPrototypeOf(input) === null)
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const gateKeys = [
|
|
140
|
+
"featureEnabled",
|
|
141
|
+
"capabilityGranted",
|
|
142
|
+
"consentConfirmed",
|
|
143
|
+
];
|
|
144
|
+
const snapshot = Object.create(null);
|
|
145
|
+
for (const key of gateKeys) {
|
|
146
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
147
|
+
assertValid(
|
|
148
|
+
descriptor !== undefined &&
|
|
149
|
+
descriptor.enumerable === true &&
|
|
150
|
+
Object.hasOwn(descriptor, "value") &&
|
|
151
|
+
typeof descriptor.value === "boolean"
|
|
152
|
+
);
|
|
153
|
+
snapshot[key] = descriptor.value;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const { featureEnabled, capabilityGranted, consentConfirmed } = snapshot;
|
|
157
|
+
if (
|
|
158
|
+
!featureEnabled ||
|
|
159
|
+
!capabilityGranted ||
|
|
160
|
+
!consentConfirmed
|
|
161
|
+
) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const actualKeys = Reflect.ownKeys(input);
|
|
166
|
+
assertValid(
|
|
167
|
+
actualKeys.length === inputKeys.length &&
|
|
168
|
+
inputKeys.every((key) => actualKeys.includes(key)) &&
|
|
169
|
+
actualKeys.every(
|
|
170
|
+
(key) => typeof key === "string" && inputKeys.includes(key)
|
|
171
|
+
)
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
for (const key of inputKeys.filter((key) => !gateKeys.includes(key))) {
|
|
175
|
+
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
176
|
+
assertValid(
|
|
177
|
+
descriptor !== undefined &&
|
|
178
|
+
descriptor.enumerable === true &&
|
|
179
|
+
Object.hasOwn(descriptor, "value")
|
|
180
|
+
);
|
|
181
|
+
snapshot[key] = descriptor.value;
|
|
182
|
+
}
|
|
183
|
+
return Object.freeze(snapshot);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function assertValid(condition) {
|
|
187
|
+
if (!condition) {
|
|
188
|
+
throw new TypeError("Invalid privacy-safe renderer diagnostics input.");
|
|
189
|
+
}
|
|
190
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,3 +1,60 @@
|
|
|
1
|
+
/// <reference types="webxr" />
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
FeedbackGameDiagnosticBackend,
|
|
5
|
+
FeedbackGameDiagnosticCounter,
|
|
6
|
+
FeedbackGameDiagnosticErrorCode,
|
|
7
|
+
FeedbackGameDiagnosticFeatureId,
|
|
8
|
+
FeedbackGameDiagnosticFrameRateBucket,
|
|
9
|
+
FeedbackGameDiagnosticFrameTimeBucket,
|
|
10
|
+
FeedbackGameDiagnostics,
|
|
11
|
+
FeedbackGameDiagnosticRenderer,
|
|
12
|
+
FeedbackGameDiagnosticSurfaceId,
|
|
13
|
+
FeedbackGameDiagnosticViewportBucket,
|
|
14
|
+
} from "@plasius/gpu-shared/feedback-diagnostics";
|
|
15
|
+
import type { XrStoreState } from "@plasius/gpu-xr";
|
|
16
|
+
|
|
17
|
+
export interface CreateFeedbackGameDiagnosticSnapshotInput {
|
|
18
|
+
readonly featureEnabled: boolean;
|
|
19
|
+
readonly capabilityGranted: boolean;
|
|
20
|
+
readonly consentConfirmed: boolean;
|
|
21
|
+
readonly surfaceId: FeedbackGameDiagnosticSurfaceId;
|
|
22
|
+
readonly renderer: FeedbackGameDiagnosticRenderer;
|
|
23
|
+
readonly backend: FeedbackGameDiagnosticBackend;
|
|
24
|
+
/** CSS-pixel viewport width; it is bucketed synchronously and never returned. */
|
|
25
|
+
readonly viewportWidth: number | null;
|
|
26
|
+
/** CSS-pixel viewport height; it is bucketed synchronously and never returned. */
|
|
27
|
+
readonly viewportHeight: number | null;
|
|
28
|
+
/** Recent coarse FPS observation; it is bucketed synchronously and never returned. */
|
|
29
|
+
readonly frameRate: number | null;
|
|
30
|
+
/** Recent coarse frame-time observation; it is bucketed synchronously and never returned. */
|
|
31
|
+
readonly frameTimeMs: number | null;
|
|
32
|
+
readonly featureIds: readonly FeedbackGameDiagnosticFeatureId[];
|
|
33
|
+
readonly counters: readonly FeedbackGameDiagnosticCounter[];
|
|
34
|
+
readonly errorCodes: readonly FeedbackGameDiagnosticErrorCode[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function bucketFeedbackGameViewport(
|
|
38
|
+
width: unknown,
|
|
39
|
+
height: unknown,
|
|
40
|
+
): FeedbackGameDiagnosticViewportBucket;
|
|
41
|
+
|
|
42
|
+
export function bucketFeedbackGameFrameRate(
|
|
43
|
+
frameRate: unknown,
|
|
44
|
+
): FeedbackGameDiagnosticFrameRateBucket;
|
|
45
|
+
|
|
46
|
+
export function bucketFeedbackGameFrameTime(
|
|
47
|
+
frameTimeMs: unknown,
|
|
48
|
+
): FeedbackGameDiagnosticFrameTimeBucket;
|
|
49
|
+
|
|
50
|
+
export function createFeedbackGameDiagnosticSnapshot<
|
|
51
|
+
SurfaceId extends FeedbackGameDiagnosticSurfaceId,
|
|
52
|
+
>(
|
|
53
|
+
input: CreateFeedbackGameDiagnosticSnapshotInput & {
|
|
54
|
+
readonly surfaceId: SurfaceId;
|
|
55
|
+
},
|
|
56
|
+
): Extract<FeedbackGameDiagnostics, { readonly surfaceId: SurfaceId }> | null;
|
|
57
|
+
|
|
1
58
|
export type RendererColor = string | [number, number, number, number?];
|
|
2
59
|
export type AnimatedSceneVector3 = readonly [number, number, number] | readonly number[];
|
|
3
60
|
export type AnimatedScenePropKind =
|
|
@@ -509,7 +566,6 @@ export interface WavefrontMeshInput {
|
|
|
509
566
|
readonly occlusionTexture?: WavefrontTextureSampleInput | null;
|
|
510
567
|
readonly emissiveTexture?: WavefrontTextureSampleInput | null;
|
|
511
568
|
readonly medium?: WavefrontMediumInput | null;
|
|
512
|
-
readonly extensions?: Record<string, Record<string, unknown>>;
|
|
513
569
|
}
|
|
514
570
|
|
|
515
571
|
export interface WavefrontTriangleRecord {
|
|
@@ -1594,13 +1650,12 @@ export interface GpuRenderer {
|
|
|
1594
1650
|
setXrActive(active: boolean): void;
|
|
1595
1651
|
getSnapshot(): RendererSnapshot;
|
|
1596
1652
|
bindXrManager(
|
|
1597
|
-
xrManager:
|
|
1598
|
-
subscribe: (listener: (state: { activeSession: XRSession | null }) => void) => () => void;
|
|
1599
|
-
getState?: () => { activeSession: XRSession | null };
|
|
1600
|
-
store?: { getSnapshot: () => { activeSession: XRSession | null } };
|
|
1601
|
-
},
|
|
1653
|
+
xrManager: RendererXrManager,
|
|
1602
1654
|
bindOptions?: {
|
|
1603
|
-
onSessionStart?: (
|
|
1655
|
+
onSessionStart?: (
|
|
1656
|
+
session: NonNullable<XrStoreState["activeSession"]>,
|
|
1657
|
+
renderer: GpuRenderer
|
|
1658
|
+
) => void;
|
|
1604
1659
|
onSessionEnd?: (renderer: GpuRenderer) => void;
|
|
1605
1660
|
}
|
|
1606
1661
|
): () => void;
|
|
@@ -1636,15 +1691,22 @@ export function createRendererDebugHooks(
|
|
|
1636
1691
|
options: RendererDebugHooksOptions
|
|
1637
1692
|
): Pick<RendererHooks, "onFrameStart" | "onFrameComplete">;
|
|
1638
1693
|
|
|
1694
|
+
export type RendererXrState = Pick<XrStoreState, "activeSession">;
|
|
1695
|
+
|
|
1696
|
+
export interface RendererXrManager {
|
|
1697
|
+
subscribe(listener: (state: RendererXrState) => void): () => void;
|
|
1698
|
+
getState?(): RendererXrState;
|
|
1699
|
+
store?: { getSnapshot(): RendererXrState };
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1639
1702
|
export function bindRendererToXrManager(
|
|
1640
1703
|
renderer: Pick<GpuRenderer, "setXrActive">,
|
|
1641
|
-
xrManager:
|
|
1642
|
-
subscribe: (listener: (state: { activeSession: XRSession | null }) => void) => () => void;
|
|
1643
|
-
getState?: () => { activeSession: XRSession | null };
|
|
1644
|
-
store?: { getSnapshot: () => { activeSession: XRSession | null } };
|
|
1645
|
-
},
|
|
1704
|
+
xrManager: RendererXrManager,
|
|
1646
1705
|
options?: {
|
|
1647
|
-
onSessionStart?: (
|
|
1706
|
+
onSessionStart?: (
|
|
1707
|
+
session: NonNullable<XrStoreState["activeSession"]>,
|
|
1708
|
+
renderer: Pick<GpuRenderer, "setXrActive">
|
|
1709
|
+
) => void;
|
|
1648
1710
|
onSessionEnd?: (renderer: Pick<GpuRenderer, "setXrActive">) => void;
|
|
1649
1711
|
}
|
|
1650
1712
|
): () => void;
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,12 @@ export {
|
|
|
4
4
|
export {
|
|
5
5
|
createProfessionalAnimatedSceneRenderer,
|
|
6
6
|
} from "./professional-animated-scene-renderer.js";
|
|
7
|
+
export {
|
|
8
|
+
bucketFeedbackGameFrameRate,
|
|
9
|
+
bucketFeedbackGameFrameTime,
|
|
10
|
+
bucketFeedbackGameViewport,
|
|
11
|
+
createFeedbackGameDiagnosticSnapshot,
|
|
12
|
+
} from "./feedback-diagnostics.js";
|
|
7
13
|
export {
|
|
8
14
|
createDefaultWavefrontSceneObjects,
|
|
9
15
|
createWavefrontBvhBuildLevels,
|
package/src/wavefront-compute.js
CHANGED
|
@@ -211,6 +211,10 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
|
|
|
211
211
|
width: config.width,
|
|
212
212
|
height: config.height,
|
|
213
213
|
tileSize: safeTileSize,
|
|
214
|
+
tilePixelCapacity: Math.min(
|
|
215
|
+
config.tilePixelCapacity,
|
|
216
|
+
safeTileSize * safeTileSize
|
|
217
|
+
),
|
|
214
218
|
});
|
|
215
219
|
}
|
|
216
220
|
config = Object.freeze({
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
DEFAULT_BRDF_LUT_SIZE,
|
|
5
5
|
HIT_RECORD_BYTES,
|
|
6
6
|
MEDIUM_TABLE_ROWS,
|
|
7
|
+
PATH_VERTEX_RECORD_BYTES,
|
|
7
8
|
RAY_RECORD_BYTES,
|
|
8
9
|
} from "./wavefront-core.js";
|
|
9
10
|
import {
|
|
@@ -28,7 +29,13 @@ export function clampTileSizeForDevice(config, device) {
|
|
|
28
29
|
|
|
29
30
|
const maxPixelsByRay = Math.floor(limit / RAY_RECORD_BYTES);
|
|
30
31
|
const maxPixelsByHit = Math.floor(limit / HIT_RECORD_BYTES);
|
|
31
|
-
const
|
|
32
|
+
const maxPixelsByPathVertex = Math.floor(
|
|
33
|
+
limit / ((config.maxDepth + 1) * PATH_VERTEX_RECORD_BYTES)
|
|
34
|
+
);
|
|
35
|
+
const maxPixels = Math.max(
|
|
36
|
+
256,
|
|
37
|
+
Math.min(maxPixelsByRay, maxPixelsByHit, maxPixelsByPathVertex)
|
|
38
|
+
);
|
|
32
39
|
if (config.tilePixelCapacity <= maxPixels) {
|
|
33
40
|
return config.tileSize;
|
|
34
41
|
}
|