@meimu/mutracer 0.1.0 → 0.1.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 +61 -16
- package/dist/assets/bvh.worker.js +1 -1
- package/dist/mutracer.umd.js +240 -232
- package/docs/progressive-rendering.md +24 -8
- package/docs/threejs.md +3 -2
- package/lib/Display.d.ts +1 -1
- package/lib/Display.js +4 -2
- package/lib/Display.js.map +1 -1
- package/lib/Render.d.ts +9 -0
- package/lib/Render.js +84 -13
- package/lib/Render.js.map +1 -1
- package/lib/RenderLoop.d.ts +5 -2
- package/lib/RenderLoop.js +47 -15
- package/lib/RenderLoop.js.map +1 -1
- package/lib/oidn.d.ts +1 -1
- package/lib/oidnBundledWeights.cdn.d.ts +2 -2
- package/lib/renderer/WGPURenderingPipeline.js +4 -2
- package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
- package/lib/renderer/committedScene.js +11 -6
- package/lib/renderer/committedScene.js.map +1 -1
- package/lib/renderer/glsl/rayTrace.frag.js +4 -0
- package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
- package/lib/renderer/util.d.ts +2 -2
- package/lib/renderer/util.js +3 -3
- package/lib/renderer/util.js.map +1 -1
- package/lib/renderer/wgsl/rayTrace.compute.js +6 -2
- package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
- package/lib/util/fromTHREE.d.ts +5 -0
- package/lib/util/fromTHREE.js +64 -16
- package/lib/util/fromTHREE.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -14,8 +14,9 @@ loop.start();
|
|
|
14
14
|
Each tick updates syncs and commits engine inputs, then calls select, refines the
|
|
15
15
|
selected render and presents it. Active syncs select preview by default. Isolated
|
|
16
16
|
edits start final immediately; consecutive changes without explicit sync activity
|
|
17
|
-
hold preview for idleMs (default 150 ms). When interaction ends, final starts
|
|
18
|
-
|
|
17
|
+
hold preview for idleMs (default 150 ms). When interaction ends, final starts after
|
|
18
|
+
the current preview completes; its fallback remains visible until final completes a sample.
|
|
19
|
+
With intermediate denoising enabled, it stays visible until the first filtered final image is ready.
|
|
19
20
|
|
|
20
21
|
```ts
|
|
21
22
|
loop.select = ({changing}) => changing || slider.dragging ? 'preview' : 'final';
|
|
@@ -24,20 +25,28 @@ loop.on('sample', ({samples, frameTimeMs}) => updateSamples(samples));
|
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
Loop status is previewing when the displayed target is preview, including fallback
|
|
27
|
-
presentation
|
|
28
|
+
presentation and the wait for the first complete final image. Starting to trace
|
|
29
|
+
final tiles does not change that status until final is actually presented.
|
|
30
|
+
Preview samples never emit loop sample events or count toward loop
|
|
28
31
|
samples. Intermediate final filtering reports converging; only terminal filtering
|
|
29
32
|
reports denoising. After finishing, unchanged ticks update syncs and the engine
|
|
30
33
|
without GPU submissions. Display or render settings edits resume necessary work.
|
|
31
34
|
|
|
35
|
+
During interaction, the loop finishes the current preview sample before applying
|
|
36
|
+
the latest scene/camera edits, keeping previews responsive during continuous input.
|
|
37
|
+
|
|
32
38
|
start pauses on hidden pages; stop preserves images. Use tick with your own frame
|
|
33
39
|
loop, or start, which ignores manual tick calls with one warning. loop.converge
|
|
34
40
|
pauses interactive ticks, converges/presents final and resumes if still started.
|
|
35
41
|
|
|
36
42
|
coalesceSceneUpdates defaults to false. When enabled, preview inference defers
|
|
37
|
-
engine commits while syncs keep collecting edits.
|
|
43
|
+
engine commits while syncs keep collecting edits. The last presented image stays
|
|
44
|
+
visible until the next filtered preview is ready. Completed inference is presented
|
|
38
45
|
before the latest camera/scene is committed together. Interaction ending during
|
|
39
46
|
inference waits for that result before final starts. With it disabled, changes
|
|
40
|
-
immediately invalidate old inference.
|
|
47
|
+
immediately invalidate old inference. Once final is available, unused fallback
|
|
48
|
+
inference does not delay it or switch presentation back to preview.
|
|
49
|
+
Render.denoising includes intermediate
|
|
41
50
|
inference without changing status semantics. Denoising does not enable preview;
|
|
42
51
|
preview: false always disables it.
|
|
43
52
|
|
|
@@ -54,7 +63,8 @@ engine.scene = scene;
|
|
|
54
63
|
engine.camera = camera;
|
|
55
64
|
const preview = new Render(engine, {
|
|
56
65
|
resolutionScale: .5,
|
|
57
|
-
|
|
66
|
+
timeBudgetMs: 80,
|
|
67
|
+
adaptiveResolution: { targetFrameMs: 80, minScale: .125 },
|
|
58
68
|
bounces: 2, bevel: false, dispersionWavelengths: 1,
|
|
59
69
|
directLightCandidates: 2,
|
|
60
70
|
});
|
|
@@ -106,9 +116,11 @@ usable.
|
|
|
106
116
|
fallback and denoiser. `transmissiveOutputAlphaMin` belongs to Render because
|
|
107
117
|
it is evaluated along individual paths, so changing it restarts that render.
|
|
108
118
|
|
|
109
|
-
RenderLoop creates a half-resolution preview with adaptive scale .
|
|
110
|
-
|
|
119
|
+
RenderLoop creates a half-resolution preview with adaptive scale .125–.5,
|
|
120
|
+
80 ms budget, 2 bounces, no bevel, one wavelength and two light candidates;
|
|
111
121
|
other settings use Render defaults. Pass preview settings, a Render or false.
|
|
122
|
+
Preview resolution drops when interaction is slow and recovers gradually when
|
|
123
|
+
performance improves. Final resolution stays independent of preview adaptation.
|
|
112
124
|
Pass final settings or a Render; newly created finals use preview as fallback.
|
|
113
125
|
Supplied Render/Sync objects must belong to this engine. loop.dispose releases
|
|
114
126
|
only renders it created; dispose supplied renders, syncs and controls yourself.
|
|
@@ -118,6 +130,10 @@ provide both fixed width and height at construction; fixed dimensions cannot be
|
|
|
118
130
|
changed through `resolutionScale`. `width` and `height` report the actual GPU
|
|
119
131
|
image, including WebGPU's 16-pixel alignment. See [API options](api.md).
|
|
120
132
|
|
|
133
|
+
Keep the display canvas at the viewport's drawing-buffer size and set
|
|
134
|
+
`resolutionScale` on each render independently. Lowering final's scale does not
|
|
135
|
+
lower preview's scale; resizing the display canvas affects both.
|
|
136
|
+
|
|
121
137
|
Call `render.setDenoiser({ type: 'jnlm', schedule: 'converged' })` with JNLM or an OIDN
|
|
122
138
|
configuration. The schedules are `converged` (default), `progressive` (first
|
|
123
139
|
sample, then 4, 8, 16, … with a ten-second limit), and `every-sample`. Same-engine
|
package/docs/threejs.md
CHANGED
|
@@ -193,8 +193,9 @@ Construction subscribes to control events. The first update converts and writes
|
|
|
193
193
|
sources to the engine. `scene` exposes the retained converted scene, or undefined
|
|
194
194
|
when no source was supplied. A camera-only sync preserves a MuTracer-built scene.
|
|
195
195
|
`active` covers start/end interaction, camera movement, damping and changing
|
|
196
|
-
skinned/morph poses.
|
|
197
|
-
|
|
196
|
+
skinned/morph poses. OrbitControls damping stays consistent across render frame
|
|
197
|
+
rates, and final refinement resumes when motion settles. Use
|
|
198
|
+
`updateControls: false` when your application already updates the controls.
|
|
198
199
|
|
|
199
200
|
`fromTHREEScene(scene, options?)` and `fromTHREECamera(camera, options?)` remain
|
|
200
201
|
available for one-shot conversions. Each call reruns conversion hooks and creates fresh default objects; custom
|
package/lib/Display.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export default class Display {
|
|
|
65
65
|
addPostProcess(pass: PostProcess): () => void;
|
|
66
66
|
private warnPostProcess;
|
|
67
67
|
private compose;
|
|
68
|
-
/** Presents a complete
|
|
68
|
+
/** Presents a complete image or fallback. Keeps fallback visible until the first intermediate filter finishes. Does not trace. */
|
|
69
69
|
present(render: Render): Render | undefined;
|
|
70
70
|
/** Encodes this render at its actual dimensions using these settings; leaves the display canvas unchanged. */
|
|
71
71
|
toBlob(render: Render, options?: BlobOptions): Promise<Blob>;
|
package/lib/Display.js
CHANGED
|
@@ -86,11 +86,13 @@ export default class Display {
|
|
|
86
86
|
}
|
|
87
87
|
return target;
|
|
88
88
|
}
|
|
89
|
-
/** Presents a complete
|
|
89
|
+
/** Presents a complete image or fallback. Keeps fallback visible until the first intermediate filter finishes. Does not trace. */
|
|
90
90
|
present(render) {
|
|
91
91
|
if (render._engine !== this.engine)
|
|
92
92
|
throw new Error('Render belongs to another engine.');
|
|
93
|
-
const
|
|
93
|
+
const ready = this.channel === 'final' && render.fallback ? render._presentable : render._complete;
|
|
94
|
+
const fallback = render.fallback;
|
|
95
|
+
const selected = ready ? render : fallback && (this.channel === 'final' ? fallback._presentable : fallback._complete) ? fallback : undefined;
|
|
94
96
|
if (!selected)
|
|
95
97
|
return;
|
|
96
98
|
const target = this.compose(selected);
|
package/lib/Display.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Display.js","sourceRoot":"","sources":["../src/Display.ts"],"names":[],"mappings":"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","sourcesContent":["import {createCanvas} from './util/canvas.js';\nimport type Engine from './Engine.js';\nimport type Render from './Render.js';\nimport type { ToneMapping, DisplayChannel } from './renderer/RenderingPipeline.js';\nimport type { ColorRGB } from './types.js';\nimport { displaySettingsKey, applyDisplaySettings } from './settings/display.js';\n/** Display image channels. Default final. */\nexport type {DisplayChannel} from './renderer/RenderingPipeline.js';\n/** Image encoding options for Display.toBlob. */\nexport interface BlobOptions {\n /** Browser image MIME type. Default image/png. */ type?: string;\n /** Encoder quality in 0–1. Default undefined uses the browser default. */ quality?: number;\n}\n/** Optional shared display settings; defaults match Display. */\nexport interface DisplaySettings {\n /** Tone mapping transform. Default agx. */ toneMapping?: ToneMapping;\n /** Linear exposure multiplier. Default 1. */ toneMappingExposure?: number;\n /** Contrast multiplier. Default 1. */ contrast?: number;\n /** Saturation multiplier. Default 1. */ saturation?: number;\n /** White balance temperature shift in -1–1. Default 0. */ temperature?: number;\n /** Green/magenta tint in -1–1. Default 0. */ tint?: number;\n /** Vignette strength in 0–1. Default 0. */ vignette?: number;\n /** Bloom intensity; 0 disables it. Default 0. */ bloomIntensity?: number;\n /** Bloom luminance threshold. Default 1. */ bloomThreshold?: number;\n /** Bloom radius in 0–1. Default 0.6. */ bloomRadius?: number;\n /** Extended-range WebGPU presentation, ignored on WebGL. Default false. */ hdrOutputEnabled?: boolean;\n /** Display image channel. Default final. */ channel?: DisplayChannel;\n /** Linear shadow-catcher color. Default black. */ shadowCatcherColor?: ColorRGB;\n /** Shadow-catcher opacity multiplier. Default 1. */ shadowCatcherAlpha?: number;\n}\n/** Borrowed WebGPU images for one synchronous display pass. */\nexport interface PostProcessContext {\n /** Engine device; may change after recovery. */ device: GPUDevice;\n /** Record commands here; the library finishes and submits it. */ encoder: GPUCommandEncoder;\n /** Read-only composed display image, top row first; no additional color conversion. */ input: GPUTexture;\n /** Fully write this image. Same format and dimensions as input; supports render, storage and copy writes. */ output: GPUTexture;\n /** Actual render width. */ width: number;\n /** Actual render height. */ height: number;\n /** Selected complete render, including a selected fallback. */ render: Render;\n}\n/** Synchronous display pass. Images and encoder are borrowed only during the call; do not retain or destroy them. */\nexport type PostProcess = (context: PostProcessContext) => void;\n/** Canvas presentation and shared display settings; obtained from engine.display. */\nexport default class Display {\n /** Display canvas. Default the supplied Engine canvas, or a new canvas (OffscreenCanvas without document). */ readonly canvas: HTMLCanvasElement | OffscreenCanvas;\n /** Tone mapping transform. Default agx. */ toneMapping: ToneMapping = 'agx';\n /** Linear exposure multiplier. Default 1. */ toneMappingExposure = 1;\n /** Contrast multiplier. Default 1. */ contrast = 1;\n /** Saturation multiplier. Default 1. */ saturation = 1;\n /** White balance temperature shift in -1–1. Default 0. */ temperature = 0;\n /** Green/magenta tint in -1–1. Default 0. */ tint = 0;\n /** Vignette strength in 0–1. Default 0. */ vignette = 0;\n /** Bloom intensity; 0 disables it. Default 0. */ bloomIntensity = 0;\n /** Bloom luminance threshold. Default 1. */ bloomThreshold = 1;\n /** Bloom radius in 0–1. Default 0.6. */ bloomRadius = 0.6;\n /** Extended-range WebGPU presentation, ignored on WebGL. Default false. */ hdrOutputEnabled = false;\n /** Display image channel. Default final. */ channel: DisplayChannel = 'final';\n /** Linear shadow-catcher color. Default black. */ shadowCatcherColor: ColorRGB = [0, 0, 0];\n /** Shadow-catcher opacity multiplier. Default 1. */ shadowCatcherAlpha = 1;\n private constructor(private readonly engine: Engine, canvas: HTMLCanvasElement | OffscreenCanvas) { this.canvas = canvas; }\n /** @internal */ static _create(engine: Engine, canvas: HTMLCanvasElement | OffscreenCanvas) { return new Display(engine, canvas); }\n /** Resizes the drawing buffer; scale-based renders resize at their next refine. */\n setSize(width: number, height: number) {\n if (!Number.isFinite(width) || !Number.isFinite(height) || width < 1 || height < 1) throw new Error('Display dimensions must be positive.');\n width = Math.round(width); height = Math.round(height);\n if (this.canvas.width !== width) this.canvas.width = width;\n if (this.canvas.height !== height) this.canvas.height = height;\n }\n private readonly settings = new WeakMap<Render, string>();\n /** @internal */ _configure(render: Render) {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const target = render._output;\n if (!target) return;\n const key = displaySettingsKey(this);\n const previous = this.settings.get(render);\n if (previous !== key) {\n this.settings.set(render, key);\n if (previous !== undefined) render._displayChanged();\n }\n target.activate();\n applyDisplaySettings(this, target);\n }\n private readonly _postProcesses: {pass: PostProcess}[] = [];\n private _warnedPostProcess = false;\n /** Registers a display pass in order and returns an idempotent removal function. Default no passes. WebGPU only; WebGL skips passes with one warning. Runs after composition on every present/toBlob, without tracing or invalidating denoising. A thrown error discards the whole chain, presents the original image and emits engine error. */\n addPostProcess(pass: PostProcess): () => void {\n const entry = {pass};\n this._postProcesses.push(entry);\n if (this.engine.backend === 'webgl') this.warnPostProcess();\n return () => { const index = this._postProcesses.indexOf(entry); if (index >= 0) this._postProcesses.splice(index, 1); };\n }\n private warnPostProcess() {\n if (!this._warnedPostProcess) { this._warnedPostProcess = true; console.warn('Display post-processing requires WebGPU; passes are ignored on WebGL.'); }\n }\n private compose(render: Render) {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const target = render._output;\n if (!target?.presentationComplete) return;\n this._configure(render);\n target.drawPostProcess();\n if (this._postProcesses.length && this.engine.backend === 'webgl') this.warnPostProcess();\n try { target.applyPostProcesses?.(this._postProcesses.map(entry => entry.pass), render); }\n catch (error) { this.engine._notifyError(error); }\n return target;\n }\n /** Presents a complete render or its fallback. Does not trace; leaves canvas unchanged if neither is ready. */\n present(render: Render): Render | undefined {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const selected = render._complete ? render : render.fallback?._complete ? render.fallback : undefined;\n if (!selected) return;\n const target = this.compose(selected);\n if (!target) return;\n this.engine._present(target);\n return selected;\n }\n /** Encodes this render at its actual dimensions using these settings; leaves the display canvas unchanged. */\n async toBlob(render: Render, options: BlobOptions = {}): Promise<Blob> {\n const target = this.compose(render);\n if (!target) throw new Error('Render has no complete image.');\n const image = await target.readPresentation();\n const canvas = createCanvas(image.width, image.height, 'convertToBlob' in this.canvas);\n const context = canvas.getContext('2d') as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null;\n if (!context) throw new Error('The image encoding context is not available.');\n const bytes = new Uint8ClampedArray(image.data.length);\n for (let i = 0; i < bytes.length; i++) bytes[i] = Math.round(Math.min(1, Math.max(0, image.data[i])) * 255);\n context.putImageData(new ImageData(bytes, image.width, image.height), 0, 0);\n if ('convertToBlob' in canvas) return canvas.convertToBlob({type: options.type ?? 'image/png', quality: options.quality});\n return new Promise((resolve, reject) => canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('The image could not be encoded')), options.type ?? 'image/png', options.quality));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"Display.js","sourceRoot":"","sources":["../src/Display.ts"],"names":[],"mappings":"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","sourcesContent":["import {createCanvas} from './util/canvas.js';\nimport type Engine from './Engine.js';\nimport type Render from './Render.js';\nimport type { ToneMapping, DisplayChannel } from './renderer/RenderingPipeline.js';\nimport type { ColorRGB } from './types.js';\nimport { displaySettingsKey, applyDisplaySettings } from './settings/display.js';\n/** Display image channels. Default final. */\nexport type {DisplayChannel} from './renderer/RenderingPipeline.js';\n/** Image encoding options for Display.toBlob. */\nexport interface BlobOptions {\n /** Browser image MIME type. Default image/png. */ type?: string;\n /** Encoder quality in 0–1. Default undefined uses the browser default. */ quality?: number;\n}\n/** Optional shared display settings; defaults match Display. */\nexport interface DisplaySettings {\n /** Tone mapping transform. Default agx. */ toneMapping?: ToneMapping;\n /** Linear exposure multiplier. Default 1. */ toneMappingExposure?: number;\n /** Contrast multiplier. Default 1. */ contrast?: number;\n /** Saturation multiplier. Default 1. */ saturation?: number;\n /** White balance temperature shift in -1–1. Default 0. */ temperature?: number;\n /** Green/magenta tint in -1–1. Default 0. */ tint?: number;\n /** Vignette strength in 0–1. Default 0. */ vignette?: number;\n /** Bloom intensity; 0 disables it. Default 0. */ bloomIntensity?: number;\n /** Bloom luminance threshold. Default 1. */ bloomThreshold?: number;\n /** Bloom radius in 0–1. Default 0.6. */ bloomRadius?: number;\n /** Extended-range WebGPU presentation, ignored on WebGL. Default false. */ hdrOutputEnabled?: boolean;\n /** Display image channel. Default final. */ channel?: DisplayChannel;\n /** Linear shadow-catcher color. Default black. */ shadowCatcherColor?: ColorRGB;\n /** Shadow-catcher opacity multiplier. Default 1. */ shadowCatcherAlpha?: number;\n}\n/** Borrowed WebGPU images for one synchronous display pass. */\nexport interface PostProcessContext {\n /** Engine device; may change after recovery. */ device: GPUDevice;\n /** Record commands here; the library finishes and submits it. */ encoder: GPUCommandEncoder;\n /** Read-only composed display image, top row first; no additional color conversion. */ input: GPUTexture;\n /** Fully write this image. Same format and dimensions as input; supports render, storage and copy writes. */ output: GPUTexture;\n /** Actual render width. */ width: number;\n /** Actual render height. */ height: number;\n /** Selected complete render, including a selected fallback. */ render: Render;\n}\n/** Synchronous display pass. Images and encoder are borrowed only during the call; do not retain or destroy them. */\nexport type PostProcess = (context: PostProcessContext) => void;\n/** Canvas presentation and shared display settings; obtained from engine.display. */\nexport default class Display {\n /** Display canvas. Default the supplied Engine canvas, or a new canvas (OffscreenCanvas without document). */ readonly canvas: HTMLCanvasElement | OffscreenCanvas;\n /** Tone mapping transform. Default agx. */ toneMapping: ToneMapping = 'agx';\n /** Linear exposure multiplier. Default 1. */ toneMappingExposure = 1;\n /** Contrast multiplier. Default 1. */ contrast = 1;\n /** Saturation multiplier. Default 1. */ saturation = 1;\n /** White balance temperature shift in -1–1. Default 0. */ temperature = 0;\n /** Green/magenta tint in -1–1. Default 0. */ tint = 0;\n /** Vignette strength in 0–1. Default 0. */ vignette = 0;\n /** Bloom intensity; 0 disables it. Default 0. */ bloomIntensity = 0;\n /** Bloom luminance threshold. Default 1. */ bloomThreshold = 1;\n /** Bloom radius in 0–1. Default 0.6. */ bloomRadius = 0.6;\n /** Extended-range WebGPU presentation, ignored on WebGL. Default false. */ hdrOutputEnabled = false;\n /** Display image channel. Default final. */ channel: DisplayChannel = 'final';\n /** Linear shadow-catcher color. Default black. */ shadowCatcherColor: ColorRGB = [0, 0, 0];\n /** Shadow-catcher opacity multiplier. Default 1. */ shadowCatcherAlpha = 1;\n private constructor(private readonly engine: Engine, canvas: HTMLCanvasElement | OffscreenCanvas) { this.canvas = canvas; }\n /** @internal */ static _create(engine: Engine, canvas: HTMLCanvasElement | OffscreenCanvas) { return new Display(engine, canvas); }\n /** Resizes the drawing buffer; scale-based renders resize at their next refine. */\n setSize(width: number, height: number) {\n if (!Number.isFinite(width) || !Number.isFinite(height) || width < 1 || height < 1) throw new Error('Display dimensions must be positive.');\n width = Math.round(width); height = Math.round(height);\n if (this.canvas.width !== width) this.canvas.width = width;\n if (this.canvas.height !== height) this.canvas.height = height;\n }\n private readonly settings = new WeakMap<Render, string>();\n /** @internal */ _configure(render: Render) {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const target = render._output;\n if (!target) return;\n const key = displaySettingsKey(this);\n const previous = this.settings.get(render);\n if (previous !== key) {\n this.settings.set(render, key);\n if (previous !== undefined) render._displayChanged();\n }\n target.activate();\n applyDisplaySettings(this, target);\n }\n private readonly _postProcesses: {pass: PostProcess}[] = [];\n private _warnedPostProcess = false;\n /** Registers a display pass in order and returns an idempotent removal function. Default no passes. WebGPU only; WebGL skips passes with one warning. Runs after composition on every present/toBlob, without tracing or invalidating denoising. A thrown error discards the whole chain, presents the original image and emits engine error. */\n addPostProcess(pass: PostProcess): () => void {\n const entry = {pass};\n this._postProcesses.push(entry);\n if (this.engine.backend === 'webgl') this.warnPostProcess();\n return () => { const index = this._postProcesses.indexOf(entry); if (index >= 0) this._postProcesses.splice(index, 1); };\n }\n private warnPostProcess() {\n if (!this._warnedPostProcess) { this._warnedPostProcess = true; console.warn('Display post-processing requires WebGPU; passes are ignored on WebGL.'); }\n }\n private compose(render: Render) {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const target = render._output;\n if (!target?.presentationComplete) return;\n this._configure(render);\n target.drawPostProcess();\n if (this._postProcesses.length && this.engine.backend === 'webgl') this.warnPostProcess();\n try { target.applyPostProcesses?.(this._postProcesses.map(entry => entry.pass), render); }\n catch (error) { this.engine._notifyError(error); }\n return target;\n }\n /** Presents a complete image or fallback. Keeps fallback visible until the first intermediate filter finishes. Does not trace. */\n present(render: Render): Render | undefined {\n if (render._engine !== this.engine) throw new Error('Render belongs to another engine.');\n const ready = this.channel === 'final' && render.fallback ? render._presentable : render._complete;\n const fallback = render.fallback;\n const selected = ready ? render : fallback && (this.channel === 'final' ? fallback._presentable : fallback._complete) ? fallback : undefined;\n if (!selected) return;\n const target = this.compose(selected);\n if (!target) return;\n this.engine._present(target);\n return selected;\n }\n /** Encodes this render at its actual dimensions using these settings; leaves the display canvas unchanged. */\n async toBlob(render: Render, options: BlobOptions = {}): Promise<Blob> {\n const target = this.compose(render);\n if (!target) throw new Error('Render has no complete image.');\n const image = await target.readPresentation();\n const canvas = createCanvas(image.width, image.height, 'convertToBlob' in this.canvas);\n const context = canvas.getContext('2d') as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null;\n if (!context) throw new Error('The image encoding context is not available.');\n const bytes = new Uint8ClampedArray(image.data.length);\n for (let i = 0; i < bytes.length; i++) bytes[i] = Math.round(Math.min(1, Math.max(0, image.data[i])) * 255);\n context.putImageData(new ImageData(bytes, image.width, image.height), 0, 0);\n if ('convertToBlob' in canvas) return canvas.convertToBlob({type: options.type ?? 'image/png', quality: options.quality});\n return new Promise((resolve, reject) => canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('The image could not be encoded')), options.type ?? 'image/png', options.quality));\n }\n}\n"]}
|
package/lib/Render.d.ts
CHANGED
|
@@ -197,6 +197,12 @@ export default class Render {
|
|
|
197
197
|
private _adaptiveScale;
|
|
198
198
|
private _lastScale;
|
|
199
199
|
private _frameTime;
|
|
200
|
+
private _sampleStart?;
|
|
201
|
+
private _frameStart?;
|
|
202
|
+
private _sampleScale;
|
|
203
|
+
private _frameScale;
|
|
204
|
+
private _adaptiveFrames;
|
|
205
|
+
private _adaptiveFastFrames;
|
|
200
206
|
private _events;
|
|
201
207
|
/** Owning engine. */ get engine(): Engine;
|
|
202
208
|
/** Whether inference is queued or running, including intermediate updates. Default false. */ get denoising(): boolean;
|
|
@@ -225,6 +231,9 @@ export default class Render {
|
|
|
225
231
|
private _size;
|
|
226
232
|
private _settings;
|
|
227
233
|
private _synchronize;
|
|
234
|
+
private _trace;
|
|
235
|
+
private _adaptResolution;
|
|
236
|
+
private _cancelAdaptiveFrames;
|
|
228
237
|
/** Accumulates within this frame's budget. Rejects calls during converge or after disposal. */
|
|
229
238
|
refine(): RenderStatus;
|
|
230
239
|
private _refine;
|
package/lib/Render.js
CHANGED
|
@@ -3,6 +3,7 @@ import { cameraSnapshot } from './renderer/cameraSnapshot.js';
|
|
|
3
3
|
import { collectTraceSettings, imageSettingsKey } from './settings/render.js';
|
|
4
4
|
import { DEFAULT_JNLM_OPTIONS, createJNLMDenoiser } from './renderer/jnlm.js';
|
|
5
5
|
import { nextPreviewScale } from './renderer/util.js';
|
|
6
|
+
import { scheduleFrame } from './util/frame.js';
|
|
6
7
|
let oidnModule;
|
|
7
8
|
const loadOIDN = () => oidnModule ??= import('./renderer/oidnNetwork.js');
|
|
8
9
|
function validateDenoiserOptions(options) {
|
|
@@ -121,6 +122,8 @@ export default class Render {
|
|
|
121
122
|
throw new Error('Fixed-size renders cannot use resolutionScale.');
|
|
122
123
|
if (!Number.isFinite(value) || value <= 0)
|
|
123
124
|
throw new Error('resolutionScale must be positive.');
|
|
125
|
+
if (value !== this._resolutionScale)
|
|
126
|
+
this._cancelAdaptiveFrames();
|
|
124
127
|
this._resolutionScale = value;
|
|
125
128
|
}
|
|
126
129
|
_fallback = null;
|
|
@@ -170,6 +173,12 @@ export default class Render {
|
|
|
170
173
|
_adaptiveScale = 1;
|
|
171
174
|
_lastScale = 1;
|
|
172
175
|
_frameTime = 0;
|
|
176
|
+
_sampleStart;
|
|
177
|
+
_frameStart;
|
|
178
|
+
_sampleScale = 1;
|
|
179
|
+
_frameScale = 1;
|
|
180
|
+
_adaptiveFrames = new Set();
|
|
181
|
+
_adaptiveFastFrames = 0;
|
|
173
182
|
_events = new Events();
|
|
174
183
|
/** Owning engine. */ get engine() { return this._engine; }
|
|
175
184
|
/** Whether inference is queued or running, including intermediate updates. Default false. */ get denoising() { return !!this._denoisePending; }
|
|
@@ -220,6 +229,7 @@ export default class Render {
|
|
|
220
229
|
}
|
|
221
230
|
get _current() { const inputs = this._engine._inputs; return inputs.scene === this._input.scene && inputs.camera === this._input.camera && !this._disposed; }
|
|
222
231
|
/** @internal */ get _complete() { return this._current && !!this._target?.presentationComplete && !this._target.isFirstFrame() && this.samples > 0; }
|
|
232
|
+
/** @internal */ get _presentable() { return this._complete && (!this._denoisePending || this._denoisedSamples > 0 || (this._schedule() === 'converged' && this.maxSamples > 1)); }
|
|
223
233
|
/** @internal */ get _output() { if (this._disposed)
|
|
224
234
|
throw new Error('Render is disposed.'); return this._target; }
|
|
225
235
|
_validate() {
|
|
@@ -259,12 +269,65 @@ export default class Render {
|
|
|
259
269
|
this._offlineCamera = false;
|
|
260
270
|
this._cancelDenoise();
|
|
261
271
|
target.restartSamples();
|
|
272
|
+
this._sampleStart = this._frameStart = undefined;
|
|
273
|
+
this._frameTime = 0;
|
|
262
274
|
target.setSize(size.width, size.height);
|
|
263
275
|
this._input = { scene: inputs.scene, camera: inputs.camera, settings: key };
|
|
264
276
|
}
|
|
265
|
-
target.setOnSampleRendered((samples, isFirstFrame, frameTimeMs) => {
|
|
277
|
+
target.setOnSampleRendered((samples, isFirstFrame, frameTimeMs) => {
|
|
278
|
+
// A preview's latency includes every tile, not just its last dispatch.
|
|
279
|
+
this._frameTime = Math.max(frameTimeMs, this._sampleStart === undefined ? 0 : performance.now() - this._sampleStart);
|
|
280
|
+
this._frameStart = this._sampleStart;
|
|
281
|
+
this._frameScale = this._sampleScale;
|
|
282
|
+
this._sampleStart = undefined;
|
|
283
|
+
this._events.emit('sample', { samples, isFirstFrame, frameTimeMs });
|
|
284
|
+
});
|
|
266
285
|
return target;
|
|
267
286
|
}
|
|
287
|
+
_trace(target, limit) {
|
|
288
|
+
if (this._sampleStart === undefined) {
|
|
289
|
+
this._sampleStart = performance.now();
|
|
290
|
+
this._sampleScale = this._adaptiveScale;
|
|
291
|
+
}
|
|
292
|
+
const compiling = target.trace(this._engine._inputs.value, limit);
|
|
293
|
+
if (compiling)
|
|
294
|
+
this._sampleStart = undefined;
|
|
295
|
+
return compiling;
|
|
296
|
+
}
|
|
297
|
+
_adaptResolution() {
|
|
298
|
+
if (!this._busy && this.adaptiveResolution && this._frameTime > 0 && this.samples > 0) {
|
|
299
|
+
const { targetFrameMs, minScale } = this.adaptiveResolution;
|
|
300
|
+
const start = this._frameStart, time = this._frameTime, scale = this._frameScale, maxScale = this._resolutionScale;
|
|
301
|
+
const { width, height } = this._engine.display.canvas;
|
|
302
|
+
this._frameTime = 0;
|
|
303
|
+
// Observe the following display frame too: GPU submissions and WebGL
|
|
304
|
+
// filters can return long before their work reaches the screen.
|
|
305
|
+
const cancel = scheduleFrame(() => {
|
|
306
|
+
this._adaptiveFrames.delete(cancel);
|
|
307
|
+
const adaptive = this.adaptiveResolution, canvas = this._engine.display.canvas;
|
|
308
|
+
if (this._disposed || this._busy || !adaptive || adaptive.targetFrameMs !== targetFrameMs || adaptive.minScale !== minScale ||
|
|
309
|
+
this._resolutionScale !== maxScale || canvas.width !== width || canvas.height !== height)
|
|
310
|
+
return;
|
|
311
|
+
const elapsed = Math.max(time, start === undefined ? 0 : performance.now() - start);
|
|
312
|
+
const next = nextPreviewScale(scale, elapsed, targetFrameMs, maxScale, this._adaptiveScale, minScale);
|
|
313
|
+
if (next > this._adaptiveScale) {
|
|
314
|
+
if (++this._adaptiveFastFrames < 4)
|
|
315
|
+
return;
|
|
316
|
+
this._adaptiveScale = Math.min(next, this._adaptiveScale * 1.25);
|
|
317
|
+
}
|
|
318
|
+
else
|
|
319
|
+
this._adaptiveScale = next;
|
|
320
|
+
this._adaptiveFastFrames = 0;
|
|
321
|
+
});
|
|
322
|
+
this._adaptiveFrames.add(cancel);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
_cancelAdaptiveFrames() {
|
|
326
|
+
for (const cancel of this._adaptiveFrames)
|
|
327
|
+
cancel();
|
|
328
|
+
this._adaptiveFrames.clear();
|
|
329
|
+
this._adaptiveFastFrames = 0;
|
|
330
|
+
}
|
|
268
331
|
/** Accumulates within this frame's budget. Rejects calls during converge or after disposal. */
|
|
269
332
|
refine() {
|
|
270
333
|
if (this._busy)
|
|
@@ -305,10 +368,10 @@ export default class Render {
|
|
|
305
368
|
target.activate();
|
|
306
369
|
if (this._denoisePending)
|
|
307
370
|
return this._status = this.converged ? 'denoising' : 'converging';
|
|
308
|
-
target.setPreserveDenoisedOutput?.(!!this._filter && this._schedule()
|
|
371
|
+
target.setPreserveDenoisedOutput?.(!!this._filter && this._schedule() !== 'converged');
|
|
309
372
|
const limit = this._nextSampleTarget();
|
|
310
373
|
if (this.samples < limit) {
|
|
311
|
-
if (
|
|
374
|
+
if (this._trace(target, limit))
|
|
312
375
|
return this._status = 'compiling';
|
|
313
376
|
}
|
|
314
377
|
this._maybeDenoise(false, limit);
|
|
@@ -316,8 +379,7 @@ export default class Render {
|
|
|
316
379
|
return this._status = this.converged ? 'denoising' : 'converging';
|
|
317
380
|
if (this._filterPending && this.converged)
|
|
318
381
|
return this._status = 'preparing';
|
|
319
|
-
|
|
320
|
-
this._adaptiveScale = nextPreviewScale(this._adaptiveScale, this._frameTime, this.adaptiveResolution.targetFrameMs, this._resolutionScale, this.adaptiveResolution.minScale);
|
|
382
|
+
this._adaptResolution();
|
|
321
383
|
return this._status = this.converged ? 'finished' : 'converging';
|
|
322
384
|
}
|
|
323
385
|
_refineOne() {
|
|
@@ -329,8 +391,10 @@ export default class Render {
|
|
|
329
391
|
return;
|
|
330
392
|
target.activate();
|
|
331
393
|
if (this.samples < 1)
|
|
332
|
-
this._status =
|
|
394
|
+
this._status = this._trace(target, 1) ? 'compiling' : 'converging';
|
|
333
395
|
this._maybeDenoise();
|
|
396
|
+
if (!this._denoisePending)
|
|
397
|
+
this._adaptResolution();
|
|
334
398
|
}
|
|
335
399
|
_schedule() { return this._denoiserOptions?.schedule ?? 'converged'; }
|
|
336
400
|
_nextSampleTarget() {
|
|
@@ -422,6 +486,7 @@ export default class Render {
|
|
|
422
486
|
/** @internal */ _inputsChanged(resetTiming = false) { this._cancelDenoise(); if (resetTiming)
|
|
423
487
|
this._target?.invalidateTiming?.(); }
|
|
424
488
|
/** @internal */ _deviceLost(recovering) {
|
|
489
|
+
this._cancelAdaptiveFrames();
|
|
425
490
|
this._cancelDenoise();
|
|
426
491
|
this._filterRevision++;
|
|
427
492
|
this._filter = undefined;
|
|
@@ -443,7 +508,7 @@ export default class Render {
|
|
|
443
508
|
this._engine.display._configure(this);
|
|
444
509
|
target.activate();
|
|
445
510
|
target.drawPostProcess();
|
|
446
|
-
const revision = ++this._denoiseRevision, samples = this.samples;
|
|
511
|
+
const revision = ++this._denoiseRevision, samples = this.samples, frameStart = this._frameStart;
|
|
447
512
|
const controller = this._denoiseController = new AbortController();
|
|
448
513
|
// One shared scratch lease across all renders; queued stale jobs do no GPU work.
|
|
449
514
|
const task = this._engine._enqueueDenoise(async () => {
|
|
@@ -467,15 +532,20 @@ export default class Render {
|
|
|
467
532
|
target.drawPostProcess();
|
|
468
533
|
this._denoisedSamples = samples;
|
|
469
534
|
this._lastDenoiseTime = performance.now();
|
|
535
|
+
if (frameStart !== undefined)
|
|
536
|
+
this._frameTime = Math.max(this._frameTime, this._lastDenoiseTime - frameStart);
|
|
470
537
|
}
|
|
471
538
|
});
|
|
472
539
|
this._denoisePending = task;
|
|
473
540
|
void task.catch(error => { if (revision === this._denoiseRevision && !this._disposed)
|
|
474
|
-
this._engine._notifyError(error); }).finally(() => {
|
|
475
|
-
this.
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
541
|
+
this._engine._notifyError(error); }).finally(() => {
|
|
542
|
+
if (this._denoisePending === task) {
|
|
543
|
+
this._denoiseController = undefined;
|
|
544
|
+
this._denoisePending = undefined;
|
|
545
|
+
this._status = this.converged ? 'finished' : 'converging';
|
|
546
|
+
this._adaptResolution();
|
|
547
|
+
}
|
|
548
|
+
});
|
|
479
549
|
}
|
|
480
550
|
/** Reads native RGBA float32, top row first. Default hdr. Custom denoised output is resolved linear HDR ray color before display background/catcher composition; built-ins retain their native display-space output. Unavailable denoised output falls back to hdr. */
|
|
481
551
|
async readPixels(kind = 'hdr') {
|
|
@@ -502,7 +572,7 @@ export default class Render {
|
|
|
502
572
|
}
|
|
503
573
|
/** Discards only this render's accumulation and pending denoising. */
|
|
504
574
|
restart() { if (this._disposed)
|
|
505
|
-
throw new Error('Render is disposed.'); this._cancelDenoise(); this._target?.restartSamples(); this._status = 'converging'; }
|
|
575
|
+
throw new Error('Render is disposed.'); this._cancelDenoise(); this._cancelAdaptiveFrames(); this._sampleStart = this._frameStart = undefined; this._frameTime = 0; this._target?.restartSamples(); this._status = 'converging'; }
|
|
506
576
|
/** Releases only this render's image resources and denoiser. */
|
|
507
577
|
dispose() {
|
|
508
578
|
if (this._disposed)
|
|
@@ -511,6 +581,7 @@ export default class Render {
|
|
|
511
581
|
this._events.clear();
|
|
512
582
|
this._filterRevision++;
|
|
513
583
|
this._cancelDenoise();
|
|
584
|
+
this._cancelAdaptiveFrames();
|
|
514
585
|
if (this._target)
|
|
515
586
|
this._engine._releaseTarget(this._target);
|
|
516
587
|
if (this._filter && this._filter.kind !== 'custom')
|