@meimu/mutracer 0.1.0 → 0.1.2

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.
Files changed (88) hide show
  1. package/README.md +61 -16
  2. package/dist/assets/bvh.worker.js +1 -1
  3. package/dist/mutracer.umd.js +668 -658
  4. package/docs/materials.md +27 -7
  5. package/docs/progressive-rendering.md +24 -8
  6. package/docs/threejs.md +15 -3
  7. package/docs/viewer.md +53 -0
  8. package/lib/Display.d.ts +1 -1
  9. package/lib/Display.js +4 -2
  10. package/lib/Display.js.map +1 -1
  11. package/lib/Render.d.ts +9 -0
  12. package/lib/Render.js +84 -13
  13. package/lib/Render.js.map +1 -1
  14. package/lib/RenderLoop.d.ts +5 -2
  15. package/lib/RenderLoop.js +47 -15
  16. package/lib/RenderLoop.js.map +1 -1
  17. package/lib/oidn.d.ts +1 -1
  18. package/lib/oidnBundledWeights.cdn.d.ts +2 -2
  19. package/lib/renderer/MaterialTable.d.ts +1 -0
  20. package/lib/renderer/MaterialTable.js +2 -1
  21. package/lib/renderer/MaterialTable.js.map +1 -1
  22. package/lib/renderer/WGPUBloomPass.js +8 -8
  23. package/lib/renderer/WGPUJNLMDenoiser.js +2 -2
  24. package/lib/renderer/WGPURayTraceOutput.js +5 -5
  25. package/lib/renderer/WGPURayTracePass.js +1 -1
  26. package/lib/renderer/WGPURayTracePass.js.map +1 -1
  27. package/lib/renderer/WGPURenderingPipeline.js +8 -6
  28. package/lib/renderer/WGPURenderingPipeline.js.map +1 -1
  29. package/lib/renderer/WebGLJNLMDenoiser.js +2 -2
  30. package/lib/renderer/WebGLRayTracePass.js +3 -1
  31. package/lib/renderer/WebGLRayTracePass.js.map +1 -1
  32. package/lib/renderer/committedScene.js +11 -6
  33. package/lib/renderer/committedScene.js.map +1 -1
  34. package/lib/renderer/glsl/chunks/envMap.glsl.js +5 -5
  35. package/lib/renderer/glsl/chunks/intersect.glsl.js +32 -32
  36. package/lib/renderer/glsl/chunks/materialBuffer.glsl.js +9 -9
  37. package/lib/renderer/glsl/chunks/openPbrBsdf.glsl.js +30 -30
  38. package/lib/renderer/glsl/chunks/rand.glsl.js +2 -2
  39. package/lib/renderer/glsl/chunks/rayTraceCore.glsl.js +4 -4
  40. package/lib/renderer/glsl/chunks/sampleLight.glsl.js +3 -3
  41. package/lib/renderer/glsl/chunks/sampleMaterial.glsl.js +4 -4
  42. package/lib/renderer/glsl/chunks/sampling.glsl.js +3 -3
  43. package/lib/renderer/glsl/chunks/sobol.glsl.js +3 -3
  44. package/lib/renderer/glsl/rayTrace.frag.js +7 -3
  45. package/lib/renderer/glsl/rayTrace.frag.js.map +1 -1
  46. package/lib/renderer/glsl/webglPipeline.frag.js +2 -2
  47. package/lib/renderer/materialComparison.generated.js +5 -0
  48. package/lib/renderer/materialComparison.generated.js.map +1 -1
  49. package/lib/renderer/materialLayout.js +2 -1
  50. package/lib/renderer/materialLayout.js.map +1 -1
  51. package/lib/renderer/prepareMeshBVH.d.ts +4 -2
  52. package/lib/renderer/prepareMeshBVH.js +44 -20
  53. package/lib/renderer/prepareMeshBVH.js.map +1 -1
  54. package/lib/renderer/util.d.ts +2 -2
  55. package/lib/renderer/util.js +3 -3
  56. package/lib/renderer/util.js.map +1 -1
  57. package/lib/renderer/wgsl/chunks/common.wgsl.js +2 -2
  58. package/lib/renderer/wgsl/chunks/envMap.wgsl.js +14 -14
  59. package/lib/renderer/wgsl/chunks/intersect.wgsl.js +85 -88
  60. package/lib/renderer/wgsl/chunks/intersect.wgsl.js.map +1 -1
  61. package/lib/renderer/wgsl/chunks/openPbrBsdf.wgsl.js +34 -34
  62. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.d.ts +1 -1
  63. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js +51 -46
  64. package/lib/renderer/wgsl/chunks/pdm_displacement.wgsl.js.map +1 -1
  65. package/lib/renderer/wgsl/chunks/rand.wgsl.js +2 -2
  66. package/lib/renderer/wgsl/chunks/rayTraceCore.wgsl.js +3 -3
  67. package/lib/renderer/wgsl/chunks/sampleLight.wgsl.js +4 -4
  68. package/lib/renderer/wgsl/chunks/sampleMaterial.wgsl.js +4 -4
  69. package/lib/renderer/wgsl/chunks/sampling.wgsl.js +3 -3
  70. package/lib/renderer/wgsl/chunks/sobol.wgsl.js +3 -3
  71. package/lib/renderer/wgsl/compose.frag.wgsl.js +2 -2
  72. package/lib/renderer/wgsl/customDenoise.wgsl.js +4 -4
  73. package/lib/renderer/wgsl/offlineAdaptive.compute.js +10 -10
  74. package/lib/renderer/wgsl/rayTrace.compute.js +13 -9
  75. package/lib/renderer/wgsl/rayTrace.compute.js.map +1 -1
  76. package/lib/scene/OpenPBRMaterial.d.ts +4 -2
  77. package/lib/scene/OpenPBRMaterial.js +4 -2
  78. package/lib/scene/OpenPBRMaterial.js.map +1 -1
  79. package/lib/scene/openPbrSchema.d.ts +7 -2
  80. package/lib/scene/openPbrSchema.js +2 -1
  81. package/lib/scene/openPbrSchema.js.map +1 -1
  82. package/lib/util/fromTHREE.d.ts +6 -0
  83. package/lib/util/fromTHREE.js +85 -19
  84. package/lib/util/fromTHREE.js.map +1 -1
  85. package/lib/version.d.ts +1 -1
  86. package/lib/version.js +1 -1
  87. package/lib/version.js.map +1 -1
  88. package/package.json +1 -1
package/docs/materials.md CHANGED
@@ -198,13 +198,33 @@ start.
198
198
 
199
199
  ## Displacement
200
200
 
201
- `displacementMap` (red channel) with `displacementScale` (local units)
202
- ray-marches the height field in the shader. `displacementStepSize` controls the
203
- ray-marching step (default `0.01`). No extra triangles are generated.
204
- `displacementUvProjection` (default `'none'`) and `displacementUvProjectionScale`
205
- (default `1`) control height-map coordinates.
206
-
207
- The remaining `displacement*` properties are documented in the type declarations.
201
+ Assign a `displacementMap` and adjust `displacementScale` (height) and
202
+ `displacementBias`. The red channel supplies linear height; the offset along
203
+ the normal is `red × displacementScale + displacementBias`. Bias defaults to
204
+ `0`, so black leaves the surface in place and white uses the full height.
205
+ For centered displacement, use a bias of `-displacementScale / 2`. Displacement
206
+ requires WebGPU; WebGL 2 renders the original surface. No extra triangles are
207
+ generated.
208
+
209
+ `displacementScale` defaults to `0.1`; both height and bias use mesh-local
210
+ units. Height `0` removes map variation while bias still offsets the surface;
211
+ set both to `0` or remove the map to disable displacement. The offset follows
212
+ the mesh transform, like Three.js displacement or Blender's Object Space mode:
213
+ scaling an object also scales its displacement.
214
+ Baking a scale into vertex positions requires scaling both `displacementScale`
215
+ and `displacementBias` by the same amount to preserve the result.
216
+
217
+ `displacementStepSize` defaults to `0`, which automatically uses 1/32 of the
218
+ absolute displacement height. A positive value sets a fixed step in mesh-local
219
+ units. Smaller steps capture finer detail at a higher rendering cost; set
220
+ `0.01` explicitly to retain the previous fixed-step default. When baking an
221
+ object scale into vertices, scale an explicit step by the same amount; an
222
+ automatic step follows the height. `displacementUvProjection` (default `'none'`)
223
+ and `displacementUvProjectionScale` (default `1`) control height-map coordinates.
224
+
225
+ Choose the height for your model's local dimensions. The library does not infer
226
+ physical units or resize the model. Changing displacement settings can trigger
227
+ an expensive [full mesh update](updates.md#full-mesh-updates).
208
228
 
209
229
  ## Known limitations
210
230
 
@@ -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 in
18
- that tick and its ready fallback remains visible until final completes a sample.
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. Preview samples never emit loop sample events or count toward loop
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. Completed inference is presented
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. Render.denoising includes intermediate
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
- adaptiveResolution: { targetFrameMs: 20, minScale: .25 },
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 .25–.5,
110
- 20 ms target, 2 bounces, no bevel, one wavelength and two light candidates;
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
@@ -104,6 +104,16 @@ Meshes without UVs get automatic triplanar texture and displacement
104
104
  projection; explicit
105
105
  `mutracerUvProjection` settings take precedence.
106
106
 
107
+ Three.js `displacementScale` and `displacementBias` retain their local-unit
108
+ semantics. Without a displacement map, Three.js's inactive default height of `1`
109
+ is replaced by MuTracer's default `0.1`; other explicitly configured heights
110
+ are preserved. `userData.mutracerOpenPBR.displacementScale` can explicitly set
111
+ any height, including `1`, before a map is assigned; `displacementBias` in the
112
+ same metadata overrides the Three.js bias. The same metadata object's
113
+ `displacementStepSize` overrides marching quality: `0` uses automatic steps,
114
+ and a positive value is a fixed local-unit step. See
115
+ [displacement](materials.md#displacement) for scaling and cost.
116
+
107
117
  ## Skinning and morph targets
108
118
 
109
119
  The initial conversion and each `sync.update()` evaluate morph targets and
@@ -192,9 +202,11 @@ MuTracer transform; source rotation alone does not specify their direction.
192
202
  Construction subscribes to control events. The first update converts and writes
193
203
  sources to the engine. `scene` exposes the retained converted scene, or undefined
194
204
  when no source was supplied. A camera-only sync preserves a MuTracer-built scene.
195
- `active` covers start/end interaction, camera movement, damping and changing
196
- skinned/morph poses. The damping tail ends when movement and zoom fall below
197
- 0.05% of view distance, allowing final refinement in that same tick.
205
+ `active` covers gestures once the camera moves, damping and changing
206
+ skinned/morph poses. Clicking or holding a stationary view preserves accumulated
207
+ samples. OrbitControls damping stays consistent across render frame
208
+ rates, and final refinement resumes when motion settles. Use
209
+ `updateControls: false` when your application already updates the controls.
198
210
 
199
211
  `fromTHREEScene(scene, options?)` and `fromTHREECamera(camera, options?)` remain
200
212
  available for one-shot conversions. Each call reruns conversion hooks and creates fresh default objects; custom
package/docs/viewer.md ADDED
@@ -0,0 +1,53 @@
1
+ # Sharing viewer settings
2
+
3
+ Add settings to a viewer link to override the recipient's saved preferences.
4
+ For example, open the viewer with HDR output enabled:
5
+
6
+ ```text
7
+ https://mutracer.meimu-studio.com/viewer/?hdrOutputEnabled=true
8
+ ```
9
+
10
+ Combine settings with `&`, and use `#model=…` to choose an example:
11
+
12
+ ```text
13
+ https://mutracer.meimu-studio.com/viewer/?hdrOutputEnabled=true&maxSamples=128#model=dragon
14
+ ```
15
+
16
+ The viewer updates the URL as lighting, render and display settings change.
17
+ Copy the address bar to share those settings and the selected example model.
18
+ Settings also work in the hash, such as `#model=dragon&hdrOutputEnabled=true`.
19
+
20
+ ## Options
21
+
22
+ Parameter names match the viewer settings and are case-sensitive:
23
+
24
+ | Group | Parameters |
25
+ | --- | --- |
26
+ | Lighting | `lighting`, `studioPreset`, `studioIntensity`, `studioRotation`, `studioSize`, `studioAmbient`, `studioAmbientColor`, `envId`, `envIntensity`, `envRotation`, `envResolution` |
27
+ | Background and ground | `background`, `backgroundColor`, `groundedHeight`, `groundedRadius`, `groundShadow`, `groundOpacity` |
28
+ | Quality | `maxSamples`, `bounces`, `resolutionScale`, `samplingPattern`, `bvhBuilder`, `clamping`, `previewEnabled`, `previewMaxResolutionScale` |
29
+ | Denoising | `denoise`, `denoiseModel`, `denoiseWhileConverging`, `jnlmStrength`, `jnlmSearchRadius`, `jnlmPatchRadius`, `jnlmAlbedoBandwidth`, `jnlmNormalBandwidth`, `jnlmIterations`, `oidnModel`, `oidnMaxTileSize` |
30
+ | Display | `hdrOutputEnabled`, `toneMapping`, `exposure`, `contrast`, `saturation`, `temperature`, `tint`, `bloomIntensity`, `bloomThreshold`, `bloomRadius`, `vignette`, `output` |
31
+
32
+ Booleans accept `true`/`false` or `1`/`0`. Numbers must be finite and within
33
+ the setting's supported range. Colors use URL-encoded hex, for example
34
+ `backgroundColor=%232a2b30`. Use `null` for automatic `groundedHeight` or
35
+ `groundedRadius`. Choices use the setting's value, for example
36
+ `toneMapping=agx`, `envResolution=2k` or `denoiseModel=jnlm`.
37
+
38
+ Valid query parameters take priority over hash parameters, then saved local
39
+ preferences, then defaults. Unknown parameters and invalid values are ignored.
40
+ Explicit defaults such as `hdrOutputEnabled=false` remain in the link so they
41
+ can override a recipient's different preference. When the viewer updates the
42
+ address, settings move into the hash; unrelated query parameters, including
43
+ `?webgl`, are preserved.
44
+
45
+ ## Caveats
46
+
47
+ HDR output requires WebGPU, an HDR display and a compatible browser. Unsupported
48
+ devices fall back to SDR. `?webgl` forces WebGL 2, which does not support HDR output.
49
+
50
+ Links do not include uploaded models or environments, camera state, material
51
+ edits, object visibility or panel layout. Camera state continues to follow the
52
+ locally saved view for the current model. URL settings are applied on page load;
53
+ reload after editing parameters in the address bar.
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 render or its fallback. Does not trace; leaves canvas unchanged if neither is ready. */
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 render or its fallback. Does not trace; leaves canvas unchanged if neither is ready. */
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 selected = render._complete ? render : render.fallback?._complete ? render.fallback : undefined;
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);
@@ -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) => { this._frameTime = frameTimeMs; this._events.emit('sample', { 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() === 'progressive');
371
+ target.setPreserveDenoisedOutput?.(!!this._filter && this._schedule() !== 'converged');
309
372
  const limit = this._nextSampleTarget();
310
373
  if (this.samples < limit) {
311
- if (target.trace(this._engine._inputs.value, limit))
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
- if (this.adaptiveResolution && this._frameTime > 0 && this.samples > 0)
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 = target.trace(this._engine._inputs.value, 1) ? 'compiling' : 'converging';
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(() => { if (this._denoisePending === task) {
475
- this._denoiseController = undefined;
476
- this._denoisePending = undefined;
477
- this._status = this.converged ? 'finished' : 'converging';
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')