@threenative/core 0.1.0 → 0.3.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.
@@ -0,0 +1,608 @@
1
+ import * as three from 'three';
2
+ import { Object3D, Matrix4, Camera, Vector2, Vector3, Scene, OrthographicCamera } from 'three';
3
+ import { MRTNode, Node } from 'three/webgpu';
4
+
5
+ /**
6
+ * Per-presented-frame cost attribution, on by default, for every platform.
7
+ *
8
+ * A device once read 18.3 fps with nothing in the repository able to say where the frame went;
9
+ * the answer took a hand-written 468-line probe that monkey-patched `requestAnimationFrame` and
10
+ * `renderer.render` from inside the game. That probe had to guess the phase boundaries because it
11
+ * lived outside the loop. This lives inside the loop, so it knows them: the framework owns the
12
+ * simulation/render split, the render call and the frame's start and end, and no game should ever
13
+ * write this again.
14
+ *
15
+ * Two consumers, one measurement:
16
+ *
17
+ * - a windowed `TN_FRAME_BUDGET` marker line, printed periodically on stdout (and therefore in
18
+ * logcat on Android), so a cold agent reading standard device-lane output sees the attribution
19
+ * without instrumenting anything;
20
+ * - a per-frame sample carried into the playtest render series, so `assert.performance` can bound
21
+ * an fps floor and a per-phase ceiling and a mobile regression is a red gate rather than a vibe.
22
+ *
23
+ * Fail closed: malformed options throw at construction rather than silently disabling the budget,
24
+ * and a phase that was never measured reports zero samples so a consumer asserting on it fails
25
+ * instead of skipping.
26
+ */
27
+ /** Marker printed once per report window. */
28
+ declare const FRAME_BUDGET_MARKER = "TN_FRAME_BUDGET";
29
+ /** Marker printed the moment a gap between presented frames exceeds `hitchMs`. */
30
+ declare const FRAME_HITCH_MARKER = "TN_FRAME_HITCH";
31
+ /**
32
+ * The named parts of one presented frame. They partition the frame: `hostGap` is the time before
33
+ * the callback (present wait plus whatever the host did between callbacks), and `update`,
34
+ * `render`, `overlay` and `residual` sum to the callback's own duration.
35
+ */
36
+ declare const FRAME_BUDGET_PHASES: readonly ["hostGap", "update", "render", "overlay", "residual"];
37
+ type FrameBudgetPhase = (typeof FRAME_BUDGET_PHASES)[number];
38
+ /** One frame's cost, split by phase. Every field is milliseconds. */
39
+ interface IFramePhaseSample {
40
+ readonly hostGap: number;
41
+ readonly update: number;
42
+ readonly render: number;
43
+ readonly overlay: number;
44
+ readonly residual: number;
45
+ }
46
+ /**
47
+ * What the frame the window measured was actually drawn at.
48
+ *
49
+ * A resolution number without its sample count does not describe an image, and neither of them
50
+ * describes anything at all unless the window that carries the fps also carries them. This is
51
+ * reported whether the scale was pinned by the game or chosen by the engine: turning the
52
+ * convention off does not turn its measurement off.
53
+ */
54
+ interface IFrameSurfaceState {
55
+ /** The applied drawing-buffer scale, in `(0, 1]`. */
56
+ readonly resolutionScale: number;
57
+ /**
58
+ * `"pinned"` when the game fixed the number, `"auto"` when the engine chose it, and
59
+ * `"auto-pinned"` when the engine chose it and then stopped moving it — the oscillation guard
60
+ * holding a rung is a different state from a game that pinned one, and reads as one.
61
+ */
62
+ readonly scaleSource: "pinned" | "auto" | "auto-pinned";
63
+ /** Multisample count of the 3D drawing buffer; 1 when sampling is off. */
64
+ readonly sampleCount: number;
65
+ readonly drawingBufferWidth: number;
66
+ readonly drawingBufferHeight: number;
67
+ /**
68
+ * True when the scaler is at its lowest rung and the tail is still over budget.
69
+ *
70
+ * A window that reports 0.23 and nothing else reads as a met budget at a low resolution. It is
71
+ * the opposite: the engine ran out of room and the game is still missing its target. Always
72
+ * false under a pinned scale, which has no floor to reach.
73
+ */
74
+ readonly atFloor: boolean;
75
+ }
76
+ interface IFrameBudgetSummary {
77
+ readonly samples: number;
78
+ readonly mean: number;
79
+ readonly p50: number;
80
+ readonly p95: number;
81
+ readonly p99: number;
82
+ readonly max: number;
83
+ }
84
+ interface IFrameBudgetWindow {
85
+ /** 1 for the first reported window, incrementing thereafter. */
86
+ readonly window: number;
87
+ /** Presented frames counted in this window, hitches excluded. */
88
+ readonly frames: number;
89
+ /** Frames excluded from the window because their present gap exceeded `hitchMs`. */
90
+ readonly hitches: number;
91
+ /** Derived from the mean presented interval: the number a player would read off a counter. */
92
+ readonly fps: number;
93
+ /** Interval between presented frames — the honest frame period. */
94
+ readonly presented: IFrameBudgetSummary;
95
+ /** Duration of the frame callback itself, entry to exit. */
96
+ readonly frame: IFrameBudgetSummary;
97
+ /** Fixed simulation steps executed in the callback. */
98
+ readonly substeps: IFrameBudgetSummary;
99
+ readonly phases: Readonly<Record<FrameBudgetPhase, IFrameBudgetSummary>>;
100
+ /** Each phase's mean as a fraction of the mean presented interval. */
101
+ readonly shares: Readonly<Record<FrameBudgetPhase, number>>;
102
+ /**
103
+ * The resolution and sampling this window's frames were drawn at, when the loop reported one.
104
+ * Absent rather than defaulted: a consumer asserting on it must fail loudly instead of reading
105
+ * a fabricated `1.0` that no frame was ever drawn at.
106
+ */
107
+ readonly surface?: IFrameSurfaceState;
108
+ /**
109
+ * GPU milliseconds for a frame in this window, from `timestamp-query`, when the adapter has it.
110
+ *
111
+ * Absent rather than zero when there is nothing to report: an adapter without timestamps and a
112
+ * frame that genuinely cost no GPU time are different facts, and a zero would merge them.
113
+ */
114
+ readonly gpuMs?: number;
115
+ }
116
+ interface IFrameBudgetOptions {
117
+ /** Presented frames per report window. Default 300. */
118
+ readonly reportEvery?: number;
119
+ /** A present gap at or above this is a hitch, not a frame. Default 2000 ms. */
120
+ readonly hitchMs?: number;
121
+ /** Ring capacity per series. Default 1024. */
122
+ readonly capacity?: number;
123
+ /** Where marker lines go. Default `console.log`. */
124
+ readonly report?: (line: string) => void;
125
+ /** Wall clock for hitch markers. Default `Date.now`. */
126
+ readonly wallClock?: () => number;
127
+ /**
128
+ * Called with each completed window, after its marker line. A HUD reads it to show the split
129
+ * on screen; a measurement run reads it to advance in lockstep with the instrument instead of
130
+ * guessing when a window closed.
131
+ */
132
+ readonly onWindow?: (window: IFrameBudgetWindow) => void;
133
+ /**
134
+ * Reads what the frames were drawn at, called once per reported window. Wired by the frame
135
+ * loop, which is the only place that knows both the renderer and the window boundary.
136
+ */
137
+ readonly readSurface?: () => IFrameSurfaceState;
138
+ /** Reads the last resolved GPU frame time, called once per reported window. */
139
+ readonly readGpuMs?: () => number | undefined;
140
+ }
141
+ /**
142
+ * Accumulates one frame at a time and reports windowed attribution.
143
+ *
144
+ * The caller is the frame loop; the sequence per frame is
145
+ * `beginFrame` → `markSimulationEnd` → (`addRender` / `addOverlay`) → `endFrame`.
146
+ * Calling them out of order throws rather than producing a plausible-looking split.
147
+ */
148
+ declare class FrameBudget {
149
+ #private;
150
+ readonly reportEvery: number;
151
+ readonly hitchMs: number;
152
+ constructor(options?: IFrameBudgetOptions);
153
+ /**
154
+ * @param timestampMs the frame timestamp the host handed the callback — the presented-frame
155
+ * clock, which is not the same as `nowMs` and is what the interval between frames comes from.
156
+ * @param nowMs the monotonic clock at callback entry.
157
+ */
158
+ beginFrame(timestampMs: number, nowMs: number): void;
159
+ /** The boundary between the fixed-step simulation and everything the render phase does. */
160
+ markSimulationEnd(nowMs: number, substeps: number): void;
161
+ addRender(ms: number): void;
162
+ addOverlay(ms: number): void;
163
+ /**
164
+ * Closes the frame and returns its phase split, or `undefined` when the frame was a hitch and
165
+ * therefore excluded — a 27-second startup stall is not a frame time and must not enter a
166
+ * percentile anybody is asked to act on.
167
+ *
168
+ * `wantSample` is false when the caller is going to discard the split, which is the default
169
+ * shipping configuration. The object is small but it escapes this method, so V8 cannot scalar-
170
+ * replace it, and building one per frame is one dead allocation per frame in every game. The
171
+ * window meters below are pushed either way: turning the sample off must not turn measurement
172
+ * off, and `window()` reads the same numbers whichever way this is called.
173
+ */
174
+ endFrame(nowMs: number, wantSample?: boolean): IFramePhaseSample | undefined;
175
+ /** Reads the window in progress without disturbing it. */
176
+ window(): IFrameBudgetWindow;
177
+ }
178
+
179
+ /** The output name shared by the scene pass and temporal consumers. */
180
+ declare const VELOCITY_OUTPUT_NAME = "velocity";
181
+ /**
182
+ * The pass surface needed to add and read the shared screen-space output.
183
+ *
184
+ * Keeping this structural lets the same seam work with Three's `PassNode` and a native adapter
185
+ * without making the game depend on either implementation.
186
+ */
187
+ interface IVelocityRenderPass {
188
+ getMRT(): MRTNode | null;
189
+ getTextureNode(name?: string): Node;
190
+ setMRT(value: MRTNode | null): unknown;
191
+ }
192
+ /** The symbol read by the patched Three.js instance accessor. */
193
+ declare const VELOCITY_PREVIOUS_INSTANCE_MATRICES: unique symbol;
194
+ /** The symbol read by the patched Three.js velocity accessor for rigid history. */
195
+ declare const VELOCITY_PREVIOUS_WORLD_MATRIX: unique symbol;
196
+ /** The symbol read by the patched Three.js skinning accessor for bone history. */
197
+ declare const VELOCITY_PREVIOUS_BONE_MATRICES: unique symbol;
198
+ /**
199
+ * Adds the velocity output to a scene pass without creating its texture until it is read.
200
+ *
201
+ * The existing outputs remain intact. A pass with no prior MRT receives the normal colour output
202
+ * as well, so adding history does not remove the frame's visible colour.
203
+ * @situation add a velocity target before a temporal stage consumes a scene pass
204
+ * @constraint call only when a temporal stage is active
205
+ */
206
+ declare function ensureVelocityOutput(pass: IVelocityRenderPass): MRTNode;
207
+ /**
208
+ * Returns the velocity texture node after ensuring the pass writes it.
209
+ * @situation read screen-space motion for a temporal stage
210
+ */
211
+ declare function velocityTexture(pass: IVelocityRenderPass): Node;
212
+ /**
213
+ * Gives a composed TSL graph the velocity source used by temporal nodes.
214
+ * Plain game values are returned unchanged, which keeps the chain's diagnostic seam lightweight.
215
+ * @situation hand the provisioned velocity source through a composed temporal graph
216
+ */
217
+ declare function withVelocityContext<T>(node: T, source: Node): T;
218
+ /**
219
+ * Captures instance matrices at the framework's pre-render boundary and enables previous data on
220
+ * every renderable in the graph. The previous snapshot is never the array the game writes next.
221
+ * @situation retain previous transforms for animated and instanced renderables
222
+ * @constraint call `update()` after gameplay writes and `commit()` after the render
223
+ */
224
+ declare class VelocityTracker {
225
+ #private;
226
+ /** Schedule the committed snapshot that the current colour and velocity frame must read. */
227
+ update(root: Object3D): void;
228
+ /** Commit transforms after the renderer has consumed the scheduled velocity frame. */
229
+ commit(root: Object3D): void;
230
+ /** Restore caller-owned flags and release all retained frame snapshots. */
231
+ clear(): void;
232
+ private enable;
233
+ private captureWorld;
234
+ private captureBones;
235
+ private captureInstance;
236
+ private captureBatch;
237
+ private commitInstance;
238
+ private restore;
239
+ }
240
+ /**
241
+ * Reads the scheduled previous instance/sub-draw array for conformance tests and renderer adapters.
242
+ * @situation inspect the previous instance frame at a renderer adapter boundary
243
+ */
244
+ declare function readVelocityPreviousMatrices(object: Object3D): Float32Array | undefined;
245
+ /**
246
+ * Reads the scheduled previous world matrix for conformance tests and renderer adapters.
247
+ * @situation inspect the previous rigid transform at a renderer adapter boundary
248
+ */
249
+ declare function readVelocityPreviousWorldMatrix(object: Object3D): Matrix4 | undefined;
250
+ /**
251
+ * Reads the scheduled previous bone array for conformance tests and renderer adapters.
252
+ * @situation inspect the previous skinned pose at a renderer adapter boundary
253
+ */
254
+ declare function readVelocityPreviousBoneMatrices(object: Object3D): Float32Array | undefined;
255
+
256
+ /** The marker shared by render-chain logs, playtests, and native diagnostics. */
257
+ declare const RENDER_CHAIN_MARKER = "TN_RENDER_CHAIN";
258
+ /** Canonical order for the stages a game may request. */
259
+ declare const RENDER_CHAIN_STAGE_ORDER: readonly ["probeVolume", "ambientOcclusion", "ssgi", "godRays", "ssr", "denoise", "temporalReproject", "taa", "traa", "motionBlur", "sharpen", "bloom", "vignette", "lensDistortion", "sparkle", "gradualBackground"];
260
+ type RenderChainStageName = (typeof RENDER_CHAIN_STAGE_ORDER)[number];
261
+ type RenderChainTier = "high" | "medium" | "low" | "off";
262
+ type RenderChainTierRequest = RenderChainTier | "auto";
263
+ type RenderChainSource = "pinned" | "auto";
264
+ type RenderChainVelocitySource = "mrt" | "per-object" | null;
265
+ interface IRenderChainVelocityMeasurement {
266
+ /** Monotonic frame number derived from the stage's completed velocity result. */
267
+ readonly frame: number;
268
+ /** Share of result pixels whose temporal history was rejected in that frame. */
269
+ readonly rejectionFraction: number;
270
+ }
271
+ interface IRenderChainVelocityResult {
272
+ /** Monotonic frame number from the active temporal stage's completed result. */
273
+ readonly frame: number;
274
+ /** One binary value per result pixel: one means the temporal history was rejected. */
275
+ readonly rejectionMask: ArrayLike<number>;
276
+ }
277
+ interface IRenderChainRenderer {
278
+ readonly kind: RendererKind;
279
+ readonly raw: unknown;
280
+ clearOutputNode?(): void;
281
+ /** Internal renderer seam used to turn on core-owned previous-frame bookkeeping for active temporal stages. */
282
+ setRenderChainVelocityEnabled?(enabled: boolean): void;
283
+ /** Install a graph and identify the authored world pass that follows the rendered scene root. */
284
+ setOutputNode(node: unknown, worldPass?: unknown): void;
285
+ }
286
+ /** Quality values are algorithm parameters, not appearance values. Templates own the latter. */
287
+ declare const RENDER_CHAIN_TIERS: {
288
+ readonly high: {
289
+ readonly denoiseIterations: 3;
290
+ readonly sliceCount: 3;
291
+ readonly stepCount: 16;
292
+ };
293
+ readonly low: {
294
+ readonly denoiseIterations: 1;
295
+ readonly sliceCount: 1;
296
+ readonly stepCount: 12;
297
+ };
298
+ readonly medium: {
299
+ readonly denoiseIterations: 2;
300
+ readonly sliceCount: 2;
301
+ readonly stepCount: 8;
302
+ };
303
+ readonly off: {
304
+ readonly denoiseIterations: 0;
305
+ readonly sliceCount: 0;
306
+ readonly stepCount: 0;
307
+ };
308
+ };
309
+ interface IRenderChainVelocityRequest {
310
+ /** The scene pass that owns the shared velocity output. */
311
+ pass?: IVelocityRenderPass;
312
+ /** Treat the renderer's MRT velocity output as provisioned. */
313
+ mrt?: boolean;
314
+ /** Treat objects carrying `userData.useVelocity === true` as provisioned. */
315
+ objectFlags?: boolean;
316
+ /** Alias accepted by integrations that call this route per-object velocity. */
317
+ perObject?: boolean;
318
+ /**
319
+ * Compatibility source for completed temporal measurements. The stage reader wins when it
320
+ * returns a result; this callback is consulted when an integration cannot expose that reader.
321
+ */
322
+ rejectionMeasurement?: () => IRenderChainVelocityMeasurement | undefined;
323
+ /** Explicit route, useful for a native host whose MRT is not introspectable from JavaScript. */
324
+ source?: Exclude<RenderChainVelocitySource, null>;
325
+ }
326
+ interface IRenderChainRequest {
327
+ /** Stages are sorted into {@link RENDER_CHAIN_STAGE_ORDER}; an empty list is a no-op. */
328
+ stages?: readonly string[];
329
+ /** A fixed tier pins quality; `auto` enables the measured frame-budget ladder. */
330
+ tier?: RenderChainTierRequest;
331
+ velocity?: IRenderChainVelocityRequest;
332
+ }
333
+ interface IRenderChainStageContext {
334
+ readonly tier: RenderChainTier;
335
+ readonly velocity: IRenderChainVelocityReport;
336
+ /** TSL source handed to temporal stages when the request owns a scene pass. */
337
+ readonly velocityNode?: Node;
338
+ readonly quality: (typeof RENDER_CHAIN_TIERS)[RenderChainTier];
339
+ }
340
+ interface IRenderChainStage {
341
+ readonly name: RenderChainStageName;
342
+ readonly build: (input: unknown, context: IRenderChainStageContext) => unknown;
343
+ /** Stages below this tier are named as dropped instead of silently changing the graph. */
344
+ readonly minimumTier?: RenderChainTier;
345
+ /** Return a reason to drop the stage on this target, or true when it is available. */
346
+ readonly available?: (context: IRenderChainStageContext) => boolean | string;
347
+ /** Read the completed velocity result after rendering; the chain derives its rejection fraction. */
348
+ readonly readVelocityResult?: (node: unknown) => IRenderChainVelocityResult | undefined;
349
+ /** Defaults from the canonical stage name for the temporal stages. */
350
+ readonly requiresVelocity?: boolean;
351
+ }
352
+ interface IRenderChainDroppedStage {
353
+ readonly name: RenderChainStageName;
354
+ readonly reason: string;
355
+ }
356
+ interface IRenderChainVelocityReport {
357
+ readonly provisioned: boolean;
358
+ readonly required: boolean;
359
+ readonly source: RenderChainVelocitySource;
360
+ readonly rejectionFraction?: number;
361
+ readonly measurementFrame?: number;
362
+ }
363
+ interface IRenderChainApplied {
364
+ readonly dropped: readonly IRenderChainDroppedStage[];
365
+ readonly requested: readonly RenderChainStageName[];
366
+ readonly source: RenderChainSource;
367
+ readonly stages: readonly RenderChainStageName[];
368
+ readonly tier: RenderChainTier;
369
+ readonly velocity: IRenderChainVelocityReport;
370
+ }
371
+ interface IRenderChainMarker {
372
+ readonly applied: IRenderChainApplied;
373
+ readonly marker: typeof RENDER_CHAIN_MARKER;
374
+ }
375
+ interface IRenderChainBudgetWindow {
376
+ readonly phases: {
377
+ readonly render: Pick<IFrameBudgetWindow["phases"]["render"], "p95">;
378
+ };
379
+ }
380
+ interface IRenderChainOptions {
381
+ readonly renderer: IRenderChainRenderer;
382
+ readonly input?: unknown;
383
+ readonly report?: (line: string) => void;
384
+ readonly request?: IRenderChainRequest;
385
+ readonly stages?: readonly IRenderChainStage[];
386
+ readonly targetFps?: number;
387
+ /** Number of consecutive over-budget windows before the automatic ladder steps down. */
388
+ readonly dwellWindows?: number;
389
+ /** Optional scene-like traversal for detecting `userData.useVelocity` flags. */
390
+ readonly scene?: {
391
+ traverse(callback: (object: {
392
+ userData?: Record<string, unknown>;
393
+ }) => void): void;
394
+ };
395
+ }
396
+ /**
397
+ * Read the last chain marker associated with a renderer or its raw renderer.
398
+ * @situation expose the render tier and dropped-stage reasons to a playtest
399
+ * @situation inspect whether a temporal pass received velocity
400
+ * @constraint an absent value means no chain was installed and must fail a chain assertion
401
+ * @example const chain = readRenderChainReport(ctx.renderer);
402
+ */
403
+ declare function readRenderChainReport(renderer: unknown): IRenderChainMarker | undefined;
404
+ /** JSON-safe observation used by the browser and native playtest bridges. */
405
+ declare function readRenderChainObservation(renderer: unknown): IRenderChainMarker["applied"] | undefined;
406
+ /**
407
+ * Compose game-provided nodes through one ordered, measured, honest render seam.
408
+ *
409
+ * The chain owns only stage ordering, availability, velocity provisioning and quality selection.
410
+ * Every stage factory is supplied by the game/template, so this module never chooses a material,
411
+ * colour, light, shader or post-processing look.
412
+ */
413
+ declare class RenderChain {
414
+ #private;
415
+ constructor(renderer: IRenderChainRenderer, options?: Omit<IRenderChainOptions, "renderer">);
416
+ constructor(options: IRenderChainOptions);
417
+ get applied(): IRenderChainApplied;
418
+ get disposed(): boolean;
419
+ /** Rebuild and install the current tier. */
420
+ apply(): IRenderChainApplied;
421
+ /** Samples the active temporal stage's completed velocity result after rendering. */
422
+ observeFrame(): IRenderChainApplied;
423
+ /** Feed the chain the same render-window evidence used by the frame budget. */
424
+ observeFrameBudget(window: IRenderChainBudgetWindow): RenderChainTier;
425
+ dispose(): void;
426
+ }
427
+
428
+ type RendererKind = "webgpu" | "webgl2";
429
+ /**
430
+ * Put transient meshes through the renderer's first-use shader path during loading.
431
+ *
432
+ * A prewarmed mesh stays in the requested subtree with zero opacity. Do not hide transient effects
433
+ * with `.visible = false`: that defers the pipeline and creates one long frame the first time the
434
+ * effect appears, never again that session. Ancestors above the requested object and unrelated
435
+ * siblings keep their visibility. If the requested subtree is under a hidden ancestor,
436
+ * `compileAsync()` compiles that subtree once as a standalone input instead of revealing the
437
+ * ancestor or its siblings.
438
+ */
439
+ declare function prewarm(object: Object3D | readonly Object3D[]): void;
440
+ interface IRendererLike {
441
+ readonly domElement: HTMLCanvasElement;
442
+ readonly kind: RendererKind;
443
+ readonly raw: unknown;
444
+ /**
445
+ * The underlying renderer's statistics (`render.drawCalls`, `render.triangles`, …).
446
+ *
447
+ * Throws when the running renderer has none — the same fail-closed shape as `setOutputNode` —
448
+ * because a game that cannot count its own draws cannot apply a draw-count lever on evidence.
449
+ */
450
+ get info(): unknown;
451
+ /**
452
+ * Builds and compiles a scene's pipelines before anything draws it.
453
+ *
454
+ * On a phone each distinct shader is compiled the first time something using it is drawn, which
455
+ * happens inside a frame the player is watching: 2,500 ms of a 2,882 ms Pixel 8 cold start sits
456
+ * between the bundle finishing and the first frame reaching the display. Calling this during
457
+ * load moves that cost somewhere the player is already waiting.
458
+ *
459
+ * It is on the wrapper for one reason: without it a game must cast through `.raw` to warm up,
460
+ * and a game that cannot warm up without a cast will not warm up.
461
+ */
462
+ compileAsync(scene: Object3D, camera: Camera): Promise<void>;
463
+ compute(node: unknown): void;
464
+ /**
465
+ * Copies one GPU storage attribute back to the CPU, asynchronously.
466
+ *
467
+ * It is on the wrapper for the same reason `compute` is: the call is WebGPU-only and a game that
468
+ * must cast through `.raw` to read its own simulation will either not read it or read it wrong.
469
+ * The copy is asynchronous by nature — the caller gets the bytes some frames after the frame
470
+ * that produced them, and `GPUReadback` is what turns that latency into a reported number
471
+ * instead of a silent one.
472
+ */
473
+ readback(attribute: unknown): Promise<ArrayBuffer>;
474
+ render(scene: Object3D, camera: Camera): void;
475
+ /** Draws after the world without clearing or passing through the world's output pipeline. */
476
+ renderOverlay(scene: Object3D, camera: Camera): void;
477
+ /** Removes the output pipeline installed by a render-chain. */
478
+ clearOutputNode?(): void;
479
+ /** Creates the core-owned chain seam without making generated render source import the package. */
480
+ createRenderChain?: (options: Omit<IRenderChainOptions, "renderer">) => RenderChain;
481
+ /** Feeds automatic render-chain tiers the completed frame-budget window. */
482
+ observeRenderChainBudget?: (window: IRenderChainBudgetWindow) => void;
483
+ /** Samples render-chain telemetry after the renderer completes a frame. */
484
+ observeRenderChainFrame?: () => void;
485
+ /** Whether the active chain requested core-owned per-object velocity history. */
486
+ renderChainUsesPerObjectVelocity?: () => boolean;
487
+ /** Internal callback used by RenderChain; games should request velocity through the chain. */
488
+ setRenderChainVelocityEnabled?: (enabled: boolean) => void;
489
+ /** Installs a graph; pass the authored world pass when the graph contains auxiliary passes. */
490
+ setOutputNode(node: unknown, worldPass?: unknown): void;
491
+ setSize(width: number, height: number, updateStyle?: boolean): void;
492
+ /**
493
+ * The GPU time the last resolved frame actually cost, in milliseconds, or `undefined` when the
494
+ * adapter has no `timestamp-query` and there is nothing to report.
495
+ *
496
+ * Every GPU number in this repository's performance record before this was wall-clock algebra:
497
+ * ablate scene content, difference a blocking device poll in a diagnostic build that never
498
+ * ships. This is the measurement itself. Resolving is asynchronous and off the frame path — the
499
+ * caller reads whatever the last resolve produced.
500
+ */
501
+ gpuFrameMs(): number | undefined;
502
+ /** Starts a resolve of the GPU timestamps for the frames drawn since the last call. */
503
+ resolveGpuFrame(): void;
504
+ /**
505
+ * Moves the drawing-buffer scale, re-applying it immediately. The adaptive scaler is the only
506
+ * caller; a pinned game never reaches this, which is what makes "pinned" mean pinned.
507
+ */
508
+ setResolutionScale(scale: number, scaleSource: "auto" | "auto-pinned"): void;
509
+ /**
510
+ * What this renderer is actually drawing at. Read once per frame-budget window so every fps
511
+ * number is self-describing; a record and a tree once disagreed about the scale for a whole
512
+ * session because nothing in the measurement could say which one produced it.
513
+ */
514
+ surface(): IFrameSurfaceState;
515
+ dispose(): void;
516
+ }
517
+ interface IRendererPlatformSource {
518
+ createCanvas(): HTMLCanvasElement;
519
+ hasWebGPU(): boolean;
520
+ observeResize(canvas: HTMLCanvasElement, resize: () => void): () => void;
521
+ readSize(canvas: HTMLCanvasElement): readonly [width: number, height: number];
522
+ }
523
+ interface IRendererOptions {
524
+ /** Requests multisample antialiasing from the renderer. Defaults to true. */
525
+ antialias?: boolean;
526
+ canvas?: HTMLCanvasElement;
527
+ preferWebGPU?: boolean;
528
+ /** CSS-pixel multiplier for the drawing buffer. The default is intentional DPR 1. */
529
+ resolutionScale?: number;
530
+ /** Whether the game pinned that scale or the engine chose it. Reported, never inferred. */
531
+ scaleSource?: "pinned" | "auto" | "auto-pinned";
532
+ /**
533
+ * Physical pixels per logical (CSS) pixel of the canvas this draws into. Default 1.
534
+ *
535
+ * Native reports a real `devicePixelRatio` and a canvas measured in logical pixels, so the
536
+ * buffer is `logical × pixelRatio × resolutionScale` — arithmetic that reproduces the physical
537
+ * surface exactly and moves no tuned game's frame. Web keeps the default of 1 and its
538
+ * intentional DPR-1 buffer; unifying web onto real device density is a separate decision with
539
+ * its own visuals gate, and this option is where it would be made.
540
+ */
541
+ pixelRatio?: number;
542
+ source?: IRendererPlatformSource;
543
+ webgpuFactory?: (canvas: HTMLCanvasElement, options: Readonly<{
544
+ antialias: boolean;
545
+ }>) => Promise<unknown> | unknown;
546
+ webgl2Factory?: (canvas: HTMLCanvasElement, options: Readonly<{
547
+ antialias: boolean;
548
+ }>) => unknown;
549
+ }
550
+
551
+ interface IViewportSize {
552
+ readonly aspect: number;
553
+ readonly height: number;
554
+ readonly width: number;
555
+ }
556
+ interface IViewportInsets {
557
+ readonly bottom: number;
558
+ readonly left: number;
559
+ readonly right: number;
560
+ readonly top: number;
561
+ }
562
+ interface IViewportSafeArea extends IViewportInsets {
563
+ /** The safe rectangle in drawable pixel coordinates, with y measured from the top edge. */
564
+ readonly height: number;
565
+ readonly source: "full-drawable-fallback" | "measured";
566
+ readonly width: number;
567
+ readonly x: number;
568
+ readonly y: number;
569
+ }
570
+ interface IViewportOptions {
571
+ readonly camera: Camera;
572
+ readonly renderer: IRendererLike;
573
+ readonly source?: IViewportPlatformSource;
574
+ }
575
+ type ViewportResizeHandler = (size: IViewportSize) => void;
576
+ interface IViewportPlatformSource {
577
+ observeResize(canvas: HTMLCanvasElement, resize: () => void): () => void;
578
+ readSize(canvas: HTMLCanvasElement): IViewportSize;
579
+ readSafeArea?(canvas: HTMLCanvasElement, size: IViewportSize): IViewportInsets | undefined;
580
+ }
581
+ declare class Viewport {
582
+ #private;
583
+ readonly camera: Camera;
584
+ readonly renderer: IRendererLike;
585
+ constructor(options: IViewportOptions);
586
+ get size(): IViewportSize;
587
+ get safeArea(): IViewportSafeArea;
588
+ projectPosition(screen: Vector2, z?: number, target?: Vector3): Vector3;
589
+ unprojectPosition(world: Vector3, target?: Vector2): Vector2;
590
+ onResize(handler: ViewportResizeHandler): () => void;
591
+ resize(): void;
592
+ dispose(): void;
593
+ }
594
+
595
+ /** A Godot-shaped render surface that is independent of the world camera and post pipeline. */
596
+ declare class CanvasLayer {
597
+ #private;
598
+ readonly scene: Scene<three.Object3DEventMap>;
599
+ readonly camera: OrthographicCamera;
600
+ /** Declares that this layer covers the framebuffer, allowing the world pass to be skipped. */
601
+ opaque: boolean;
602
+ constructor(viewport: Pick<Viewport, "onResize" | "size">);
603
+ dispose(): void;
604
+ /** Subscribe to drawable-size changes after this layer has updated its camera. */
605
+ onResize(handler: (size: IViewportSize) => void): () => void;
606
+ }
607
+
608
+ export { type RenderChainTier as A, type RenderChainTierRequest as B, CanvasLayer as C, type RenderChainVelocitySource as D, VELOCITY_PREVIOUS_BONE_MATRICES as E, FRAME_BUDGET_MARKER as F, VELOCITY_PREVIOUS_INSTANCE_MATRICES as G, VELOCITY_PREVIOUS_WORLD_MATRIX as H, type IRendererLike as I, VelocityTracker as J, ensureVelocityOutput as K, prewarm as L, readRenderChainObservation as M, readRenderChainReport as N, readVelocityPreviousBoneMatrices as O, readVelocityPreviousMatrices as P, readVelocityPreviousWorldMatrix as Q, RENDER_CHAIN_MARKER as R, velocityTexture as S, withVelocityContext as T, Viewport as U, VELOCITY_OUTPUT_NAME as V, type IRendererOptions as W, type IViewportOptions as X, FRAME_BUDGET_PHASES as a, FRAME_HITCH_MARKER as b, FrameBudget as c, type FrameBudgetPhase as d, type IFrameBudgetOptions as e, type IFrameBudgetSummary as f, type IFrameBudgetWindow as g, type IFramePhaseSample as h, type IRenderChainApplied as i, type IRenderChainBudgetWindow as j, type IRenderChainDroppedStage as k, type IRenderChainOptions as l, type IRenderChainRenderer as m, type IRenderChainRequest as n, type IRenderChainStage as o, type IRenderChainStageContext as p, type IRenderChainVelocityMeasurement as q, type IRenderChainVelocityReport as r, type IRenderChainVelocityRequest as s, type IRenderChainVelocityResult as t, type IVelocityRenderPass as u, RENDER_CHAIN_STAGE_ORDER as v, RENDER_CHAIN_TIERS as w, RenderChain as x, type RenderChainSource as y, type RenderChainStageName as z };