rayzee 8.5.0 → 8.6.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.
Files changed (64) hide show
  1. package/README.md +60 -56
  2. package/dist/{DLSSNeural-BNSJ8V3z.js → NeuralRetouch-CYOJ21Wn.js} +33 -33
  3. package/dist/{DLSSSuperRes-BoEW89Cl.js → NeuralSuperRes-Dc6jRYke.js} +25 -25
  4. package/dist/ToneMapGPU-BVE852iN.js +533 -0
  5. package/dist/rayzee.es.js +27235 -9074
  6. package/dist/rayzee.umd.js +255 -187
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +21 -7
  9. package/src/Color/BakedViews.js +134 -0
  10. package/src/Color/BuiltinViews.js +261 -0
  11. package/src/Color/ColorManagement.js +1130 -0
  12. package/src/Color/ColorSpaces.js +432 -0
  13. package/src/Color/Displays.js +19 -0
  14. package/src/Color/InputColorSpaces.js +243 -0
  15. package/src/Color/LutBake.js +371 -0
  16. package/src/Color/OcioRuntime.js +338 -0
  17. package/src/Color/OcioViews.js +419 -0
  18. package/src/Color/ViewTransforms.js +323 -0
  19. package/src/Color/WorkingMatrix.js +86 -0
  20. package/src/EngineDefaults.js +55 -3
  21. package/src/EngineIssues.js +3 -0
  22. package/src/Passes/AIUpscaler.js +16 -7
  23. package/src/Passes/OIDNDenoiser.js +13 -5
  24. package/src/PathTracerApp.js +227 -7
  25. package/src/Processor/AssetLoader.js +1 -2
  26. package/src/Processor/EmissiveTriangleBuilder.js +34 -2
  27. package/src/Processor/GeometryExtractor.js +151 -138
  28. package/src/Processor/LightSerializer.js +8 -3
  29. package/src/Processor/MaterialPacking.js +168 -0
  30. package/src/Processor/PackedRayBuffer.js +6 -2
  31. package/src/Processor/ProceduralSky.js +4 -0
  32. package/src/Processor/SceneProcessor.js +25 -1
  33. package/src/Processor/SimpleSky.js +8 -0
  34. package/src/Processor/TextureCreator.js +137 -91
  35. package/src/Processor/TextureReadback.js +92 -0
  36. package/src/Processor/ToneMapCPU.js +80 -165
  37. package/src/Processor/ToneMapGPU.js +122 -151
  38. package/src/RenderSettings.js +1 -0
  39. package/src/Stages/Compositor.js +11 -2
  40. package/src/Stages/PathTracer.js +2 -0
  41. package/src/Stages/PathTracerStage.js +1 -1
  42. package/src/TSL/Common.js +15 -0
  43. package/src/TSL/Debugger.js +2 -3
  44. package/src/TSL/EmissiveSampling.js +6 -8
  45. package/src/TSL/ExtendKernel.js +9 -0
  46. package/src/TSL/Fresnel.js +23 -1
  47. package/src/TSL/HitFacet.js +102 -0
  48. package/src/TSL/LightsDirect.js +12 -33
  49. package/src/TSL/LightsSampling.js +15 -7
  50. package/src/TSL/MaterialEvaluation.js +26 -19
  51. package/src/TSL/MaterialProperties.js +74 -37
  52. package/src/TSL/MaterialTransmission.js +2 -3
  53. package/src/TSL/ShadeKernel.js +34 -21
  54. package/src/TSL/ShadowTerminator.js +125 -0
  55. package/src/TSL/Struct.js +9 -0
  56. package/src/TSL/Subsurface.js +2 -3
  57. package/src/index.js +32 -4
  58. package/src/managers/DenoisingManager.js +60 -44
  59. package/src/managers/EnvironmentManager.js +20 -0
  60. package/src/managers/MaterialDataManager.js +118 -208
  61. package/src/managers/UniformManager.js +1 -0
  62. package/src/{dlss/DLSSNeural.js → neural/NeuralRetouch.js} +42 -41
  63. package/src/{dlss/DLSSSuperRes.js → neural/NeuralSuperRes.js} +27 -26
  64. package/dist/ToneMapGPU-DzqSyzTx.js +0 -341
@@ -0,0 +1,1130 @@
1
+ /**
2
+ * Colour management for the engine: what we render in, what we show it as, and what we hand out.
3
+ *
4
+ * With no config loaded this is inert and the engine behaves exactly as it did before OCIO
5
+ * existed — the working space is linear Rec.709, the view transforms are three.js's seven, and
6
+ * nothing converts anything. Every behaviour below only switches on once a host loads a config.
7
+ *
8
+ * ## The working space
9
+ *
10
+ * A config names the space a renderer should work in through its `scene_linear` role. For an ACES
11
+ * config that is ACEScg, whose primaries are not sRGB's — so adopting it changes what every
12
+ * texture, every material tint and every light colour mean, and therefore changes every pixel of
13
+ * every existing render.
14
+ *
15
+ * That is the correct thing to do and it is not a thing to do silently, so it is opt-in:
16
+ * `adoptWorkingSpace: true` on load, or `setWorkingSpace()` later. Loading a config without it
17
+ * gives you the config's *views* over the engine's existing linear Rec.709 render, which is the
18
+ * safe half and the one most people want first.
19
+ */
20
+
21
+ import { LinearSRGBColorSpace, SRGBColorSpace } from 'three';
22
+
23
+ import { ISSUE_CODES } from '../EngineIssues.js';
24
+ import { displayCanvasFit } from './Displays.js';
25
+
26
+ import {
27
+ ensureRuntime, loadConfig as loadOcioConfig, unloadConfig as unloadOcioConfig,
28
+ hasConfig, describeConfig, getConfigInfo, listBuiltinConfigs, resetOcio, getRuntime,
29
+ } from './OcioRuntime.js';
30
+ import {
31
+ convertColor, convertPixelsF32, convertEncodedRGBA8, applyMatrixRGBA8, extractMatrix,
32
+ clearColorCaches, resetConfigHandle, hasColorSpace,
33
+ } from './ColorSpaces.js';
34
+ import {
35
+ VIEW_TRANSFORMS, getViewTransform, listViewTransforms, removeOcioViewTransforms,
36
+ registerWithRenderer, onRegistryChange, getRegistryVersion, resetViewTransforms, FALLBACK_VIEW,
37
+ countTableTransforms, MAX_TABLE_TRANSFORMS, removeViewTransform, addViewTransform,
38
+ } from './ViewTransforms.js';
39
+ import { addOcioView, addAllOcioViews, buildBakedView, disposeOcioViewTextures, forgetOcioView, setOcioIssueLog } from './OcioViews.js';
40
+ import { encodeBakedView, decodeBakedView, configFingerprint } from './BakedViews.js';
41
+ import {
42
+ resolveInputSpace, textureInputSpace, resolveTextureSpace, setInputOverride, clearInputOverrides,
43
+ listFileRules, getInputVersion, bumpInputVersion,
44
+ } from './InputColorSpaces.js';
45
+ import { setWorkingMatrix, convertLinearTriple, convertLinearTriples } from './WorkingMatrix.js';
46
+ import { packHalf, unpackHalf } from './LutBake.js';
47
+
48
+ /** What the engine renders in when no config has been adopted. */
49
+ export const DEFAULT_WORKING_SPACE = 'Linear Rec.709 (sRGB)';
50
+
51
+ /**
52
+ * The instance the rest of the engine reads.
53
+ *
54
+ * A config, a working space and a set of view transforms are process-wide — there is one OCIO
55
+ * runtime and one set of baked tables — so code far from the app (texture processing, the asset
56
+ * loader) asks for the active instance rather than having one threaded through six constructors.
57
+ */
58
+ let active = null;
59
+
60
+ /** The colour management the engine is currently using, or null. */
61
+ export function getActiveColorManagement() {
62
+
63
+ return active;
64
+
65
+ }
66
+
67
+ /** Make an instance the one the engine reads. Normally the app's own, set on construction. */
68
+ export function setActiveColorManagement( cm ) {
69
+
70
+ active = cm;
71
+ // Whatever the previous instance published is not this one's working space.
72
+ cm?._publishWorkingMatrix?.();
73
+
74
+ }
75
+
76
+ /** The canvas colour space for a display: its own when it has one, sRGB otherwise. */
77
+ export function canvasColorSpaceFor( display ) {
78
+
79
+ return displayCanvasFit( display ) ?? 'srgb';
80
+
81
+ }
82
+
83
+ /** How configs in the wild spell linear Rec.709, most specific first. Matched against aliases too. */
84
+ const NATIVE_LINEAR_NAMES = [
85
+ 'Linear Rec.709 (sRGB)', 'lin_rec709_srgb', 'lin_rec709', 'lin_srgb', 'Linear Rec.709',
86
+ 'Utility - Linear - sRGB', 'Utility - Linear - Rec.709', 'Linear BT.709', 'linrec709',
87
+ ];
88
+
89
+ const NATIVE_LINEAR_PATTERN = /^(lin(ear)?[\s_-]*(rec\.?\s*|bt\.?\s*)?709|linear[\s_-]*\(?srgb\)?$|utility - linear - (srgb|rec\.?709))/i;
90
+
91
+ /**
92
+ * The config's own name for linear Rec.709 — the space the engine renders in until a working
93
+ * space is adopted.
94
+ *
95
+ * Aliases count as much as names: Blender calls it "Linear Rec.709" and lists the ACES spelling
96
+ * as an alias, a studio config may only carry `lin_rec709`. Data spaces are never candidates.
97
+ *
98
+ * @returns {?string} the canonical name, or null when the config has nothing that qualifies
99
+ */
100
+ export function findNativeLinearSpace( info ) {
101
+
102
+ if ( ! info ) return null;
103
+
104
+ const spaces = info.colorSpaces.filter( c => ! c.isData );
105
+ const names = c => [ c.name, ...( c.aliases ?? [] ) ];
106
+
107
+ for ( const wanted of NATIVE_LINEAR_NAMES ) {
108
+
109
+ const hit = spaces.find( c => names( c ).some( n => n.toLowerCase() === wanted.toLowerCase() ) );
110
+ if ( hit ) return hit.name;
111
+
112
+ }
113
+
114
+ return spaces.find( c => names( c ).some( n => NATIVE_LINEAR_PATTERN.test( n ) ) )?.name ?? null;
115
+
116
+ }
117
+
118
+ export class ColorManagement {
119
+
120
+ constructor( { issues = null, renderer = null } = {} ) {
121
+
122
+ this._issues = issues;
123
+ this._renderer = renderer;
124
+ this._listeners = new Map();
125
+
126
+ // null throughout means "not colour-managed": the engine default working space, a built-in
127
+ // curve rather than an OCIO view, and saving whatever the view produced.
128
+ this._workingSpace = null;
129
+ this._activeView = null;
130
+ this._exportSpace = null;
131
+ this._context = null;
132
+
133
+ // id → last time it was selected, for evicting tables once the binding ceiling is near.
134
+ this._lastUsed = new Map();
135
+ this._useClock = 0;
136
+ this._lastBuiltin = FALLBACK_VIEW;
137
+ this._fingerprint = null;
138
+
139
+ setOcioIssueLog( issues );
140
+
141
+ // Re-registering on every change, not just at attach: a view baked from a config appears
142
+ // long after the renderer was attached, and three.js only knows the tone mappings its
143
+ // library was told about. Without this the live canvas logs "Unsupported Tone Mapping
144
+ // configuration" and quietly paints something else, while the readback — which reads the
145
+ // registry directly — comes back correct. Two different images from one setting.
146
+ this._offRegistry = onRegistryChange( () => {
147
+
148
+ if ( this._renderer ) registerWithRenderer( this._renderer );
149
+ this._emit( 'transforms' );
150
+
151
+ } );
152
+
153
+ if ( active === null ) active = this;
154
+
155
+ }
156
+
157
+ // ── events ──────────────────────────────────────────────────────────────────────────────
158
+
159
+ /**
160
+ * @param {'config'|'view'|'workingSpace'|'transforms'|'change'} event
161
+ * @returns {function(): void} unsubscribe
162
+ */
163
+ on( event, fn ) {
164
+
165
+ if ( ! this._listeners.has( event ) ) this._listeners.set( event, new Set() );
166
+ this._listeners.get( event ).add( fn );
167
+ return () => this._listeners.get( event )?.delete( fn );
168
+
169
+ }
170
+
171
+ _emit( event, detail = null ) {
172
+
173
+ for ( const fn of this._listeners.get( event ) ?? [] ) fn( detail );
174
+ if ( event !== 'change' ) {
175
+
176
+ for ( const fn of this._listeners.get( 'change' ) ?? [] ) fn( { event, detail } );
177
+
178
+ }
179
+
180
+ }
181
+
182
+ // ── the config ──────────────────────────────────────────────────────────────────────────
183
+
184
+ /** The OCIO module's own configs, which need no files. */
185
+ static async listBuiltinConfigs() {
186
+
187
+ return await listBuiltinConfigs();
188
+
189
+ }
190
+
191
+ /**
192
+ * Load a config and, optionally, start rendering in the space it names.
193
+ *
194
+ * @param {Object} options - as `OcioRuntime.loadConfig`, plus:
195
+ * @param {boolean} [options.adoptWorkingSpace=false] - render in the config's scene_linear
196
+ * @param {boolean} [options.registerViews=true] - bake the default display's views right away
197
+ * @param {string} [options.display] - which display to bake views for; defaults to the config's
198
+ * @returns {Promise<Object>} the config description
199
+ */
200
+ async loadConfig( { adoptWorkingSpace = false, registerViews = true, display = null, lutSize, ...rest } = {} ) {
201
+
202
+ await ensureRuntime();
203
+
204
+ const fingerprint = configFingerprint( rest ).catch( () => null );
205
+ const baked = await this._bakedViewsFrom( rest.builtin ?? rest.id ?? null, fingerprint );
206
+
207
+ let described;
208
+ try {
209
+
210
+ described = await loadOcioConfig( rest );
211
+
212
+ } catch ( error ) {
213
+
214
+ this._issues?.warn(
215
+ ISSUE_CODES.COLOR_CONFIG_LOAD_FAILED,
216
+ `colour config failed to load; the previous one stays in effect: ${error.message}`,
217
+ { builtin: rest.builtin ?? null, configPath: rest.configPath ?? null }
218
+ );
219
+ throw error;
220
+
221
+ }
222
+
223
+ // Only after the new config is in: releasing first would leave a failed load with no views.
224
+ // A view baked from these same files by this same OCIO stays, so it is not baked twice.
225
+ this._releaseViews( new Set( baked.filter( t => t.ocio.baked.ocioVersion === described.ocioVersion ).map( t => t.id ) ) );
226
+ this._fingerprint = fingerprint;
227
+ bumpInputVersion();
228
+ resetConfigHandle();
229
+ this._context = null;
230
+
231
+ const previous = this._workingSpace;
232
+ if ( adoptWorkingSpace ) {
233
+
234
+ this._workingSpace = described.sceneLinear;
235
+
236
+ } else if ( previous && ! hasColorSpace( previous ) ) {
237
+
238
+ // A space adopted from the last config that this one does not have. Keeping the name
239
+ // would make every conversion throw; the scene is still converted into it, so the host
240
+ // has to rebuild — `workingSpace` fires below for exactly that.
241
+ this._workingSpace = null;
242
+
243
+ }
244
+
245
+ if ( ! findNativeLinearSpace( getConfigInfo() ) ) {
246
+
247
+ this._issues?.warn(
248
+ ISSUE_CODES.COLOR_CONFIG_LOAD_FAILED,
249
+ `"${described.id}" names no linear Rec.709 space, so until a working space is adopted its views ` +
250
+ `treat the render as ${described.sceneLinear} — colours will be off by that primaries change`,
251
+ { sceneLinear: described.sceneLinear }
252
+ );
253
+
254
+ }
255
+
256
+ this._publishWorkingMatrix();
257
+ this._emit( 'config', described );
258
+
259
+ if ( registerViews ) {
260
+
261
+ const target = display ?? described.defaultDisplay;
262
+ const { added, skipped } = addAllOcioViews( {
263
+ display: target,
264
+ context: this._context,
265
+ source: this.workingSpace,
266
+ ...( lutSize ? { size: lutSize } : {} ),
267
+ } );
268
+
269
+ if ( added.length ) {
270
+
271
+ const preferred = described.defaultViews[ target ];
272
+ const pick = added.find( t => t.ocio.view === preferred ) ?? added[ 0 ];
273
+ this.setActiveView( pick.id );
274
+
275
+ }
276
+
277
+ this._emit( 'transforms', { added, skipped } );
278
+
279
+ }
280
+
281
+ if ( ( this._workingSpace ?? null ) !== ( previous ?? null ) ) this._emit( 'workingSpace', this.workingSpace );
282
+
283
+ return described;
284
+
285
+ }
286
+
287
+ /**
288
+ * Forget the config; the engine returns to its uncoloured-managed behaviour.
289
+ *
290
+ * ⚠️ With a working space adopted this cannot restore the scene: the environment was converted
291
+ * where it lies and needs this config to convert back. `app.unloadColorConfig()` does it in the
292
+ * right order.
293
+ */
294
+ unloadConfig() {
295
+
296
+ if ( this.workingSpaceAdopted ) {
297
+
298
+ this._issues?.warn(
299
+ ISSUE_CODES.COLOR_CONFIG_LOAD_FAILED,
300
+ `unloaded while rendering in ${this._workingSpace}; the environment stays converted into it — ` +
301
+ 'use app.unloadColorConfig(), which reverts the scene first',
302
+ { workingSpace: this._workingSpace }
303
+ );
304
+
305
+ }
306
+
307
+ this._releaseViews();
308
+ unloadOcioConfig();
309
+ resetConfigHandle();
310
+ clearInputOverrides();
311
+ bumpInputVersion();
312
+
313
+ this._fingerprint = null;
314
+ this._workingSpace = null;
315
+ this._activeView = null;
316
+ this._exportSpace = null;
317
+ this._context = null;
318
+
319
+ setWorkingMatrix( null );
320
+ this._applyRendererOutputColorSpace();
321
+ this._emit( 'config', null );
322
+ this._emit( 'workingSpace', this.workingSpace );
323
+
324
+ }
325
+
326
+ _releaseViews( keep = null ) {
327
+
328
+ disposeOcioViewTextures( keep );
329
+ removeOcioViewTransforms( keep );
330
+ for ( const id of this._lastUsed.keys() ) if ( ! keep?.has( id ) ) this._lastUsed.delete( id );
331
+
332
+ if ( this._renderer && getViewTransform( this._renderer.toneMapping ) === null ) {
333
+
334
+ // Back to the curve the host was on before a config took over — not None, which clamps
335
+ // and looks broken — and with the output transfer switched back on to match.
336
+ this._renderer.toneMapping = getViewTransform( this._lastBuiltin ) ? this._lastBuiltin : FALLBACK_VIEW;
337
+ this._activeView = null;
338
+ this._applyRendererOutputColorSpace();
339
+
340
+ }
341
+
342
+ }
343
+
344
+ /** Registered baked views of config `id` whose files match `fingerprint`. */
345
+ async _bakedViewsFrom( id, fingerprint ) {
346
+
347
+ const candidates = [ ...VIEW_TRANSFORMS.values() ].filter( t => t.ocio?.baked && id !== null && t.ocio.configId === id );
348
+ if ( candidates.length === 0 ) return [];
349
+ const fp = await fingerprint;
350
+ return fp ? candidates.filter( t => t.ocio.baked.fingerprint === fp ) : [];
351
+
352
+ }
353
+
354
+ /**
355
+ * Register a view saved by {@link saveBakedView}. Needs neither the runtime nor its config, so a
356
+ * host can show the view first and load the config when it is needed; loading that config later
357
+ * keeps this entry when its files are the ones it was baked from.
358
+ *
359
+ * @param {ArrayBuffer|Uint8Array} bytes
360
+ * @param {Object} [options]
361
+ * @param {Object} [options.expect] - header fields that must match, e.g. `{ configId, display, view, look }`
362
+ * @returns {Promise<Object>} the registry entry, not yet active
363
+ */
364
+ async loadBakedView( bytes, { expect = null } = {} ) {
365
+
366
+ const baked = await decodeBakedView( bytes );
367
+ for ( const [ key, value ] of Object.entries( expect ?? {} ) ) {
368
+
369
+ if ( ( baked[ key ] ?? null ) !== ( value ?? null ) ) throw new Error( `baked view has ${key} "${baked[ key ]}", expected "${value}"` );
370
+
371
+ }
372
+
373
+ const loaded = describeConfig();
374
+ if ( loaded && ( loaded.id !== baked.configId || ( await this._fingerprint ) !== baked.fingerprint ) ) {
375
+
376
+ throw new Error( `baked view is from "${baked.configId}", not the loaded "${loaded.id}"` );
377
+
378
+ }
379
+
380
+ const existing = [ ...VIEW_TRANSFORMS.values() ].find( t => t.source === 'ocio' && t.ocio.display === baked.display
381
+ && t.ocio.view === baked.view && ( t.ocio.look ?? null ) === ( baked.look ?? null ) );
382
+ if ( ! existing ) this._makeRoomForTable();
383
+
384
+ return addViewTransform( buildBakedView( baked, existing?.id ?? null ) );
385
+
386
+ }
387
+
388
+ /**
389
+ * A registered OCIO view as a file for {@link loadBakedView}.
390
+ * @returns {Promise<Uint8Array>}
391
+ */
392
+ async saveBakedView( id ) {
393
+
394
+ const t = getViewTransform( id );
395
+ if ( t?.source !== 'ocio' ) throw new Error( `no OCIO view with id ${id}` );
396
+
397
+ const config = describeConfig();
398
+ return await encodeBakedView( t, {
399
+ configId: config?.id ?? t.ocio.configId,
400
+ fingerprint: ( config ? await this._fingerprint : null ) ?? t.ocio.baked?.fingerprint ?? null,
401
+ ocioVersion: config?.ocioVersion ?? t.ocio.baked?.ocioVersion ?? null,
402
+ } );
403
+
404
+ }
405
+
406
+ get hasConfig() {
407
+
408
+ return hasConfig();
409
+
410
+ }
411
+
412
+ /** Everything the config offers, or null. */
413
+ describe() {
414
+
415
+ return describeConfig();
416
+
417
+ }
418
+
419
+ // ── the working space ───────────────────────────────────────────────────────────────────
420
+
421
+ /** The space the path tracer's radiance is in. */
422
+ get workingSpace() {
423
+
424
+ if ( this._workingSpace ) return this._workingSpace;
425
+ if ( ! hasConfig() ) return DEFAULT_WORKING_SPACE;
426
+
427
+ // Not adopted means the render is still linear Rec.709 — but it has to be named the way
428
+ // *this* config names it. Hardcoding the ACES spelling broke every view bake on a config
429
+ // that does not happen to carry it as an alias.
430
+ return this.nativeLinearSpace ?? describeConfig()?.sceneLinear ?? DEFAULT_WORKING_SPACE;
431
+
432
+ }
433
+
434
+ /** True once the render is happening in a space the config named. */
435
+ get workingSpaceAdopted() {
436
+
437
+ return this._workingSpace !== null;
438
+
439
+ }
440
+
441
+ /**
442
+ * Change what the engine renders in.
443
+ *
444
+ * Everything already loaded is in the old space, so the caller has to rebuild the scene after
445
+ * this — the `workingSpace` event is the signal. `PathTracerApp` wires that up.
446
+ *
447
+ * @param {?string} name - a config space, or null to go back to linear Rec.709
448
+ */
449
+ setWorkingSpace( name ) {
450
+
451
+ if ( name !== null ) {
452
+
453
+ if ( ! hasConfig() ) throw new Error( 'load an OCIO config before choosing a working space' );
454
+ if ( ! hasColorSpace( name ) ) throw new Error( `config has no colour space "${name}"` );
455
+
456
+ }
457
+
458
+ if ( ( this._workingSpace ?? null ) === ( name ?? null ) ) return;
459
+
460
+ this._workingSpace = name;
461
+ clearColorCaches();
462
+ this._publishWorkingMatrix();
463
+ this._rebakeViews();
464
+ this._emit( 'workingSpace', this.workingSpace );
465
+
466
+ }
467
+
468
+ /**
469
+ * The 3×3 that takes colour from `space` into the working space, or null when the conversion
470
+ * is not a plain matrix. Useful to a caller that wants to do the conversion in a shader.
471
+ */
472
+ matrixToWorking( space ) {
473
+
474
+ if ( ! hasConfig() ) return null;
475
+ return extractMatrix( space, this.workingSpace, { context: this._context } );
476
+
477
+ }
478
+
479
+ // ── the view ────────────────────────────────────────────────────────────────────────────
480
+
481
+ /** Every registered view transform, for a host menu. */
482
+ listViews() {
483
+
484
+ return listViewTransforms();
485
+
486
+ }
487
+
488
+ /** `{ display, view, look, context }` of the active OCIO view, or null for a built-in curve. */
489
+ get activeView() {
490
+
491
+ return this._activeView;
492
+
493
+ }
494
+
495
+ /** Make a registered transform the one the renderer uses. */
496
+ setActiveView( id ) {
497
+
498
+ let t = getViewTransform( id );
499
+ if ( ! t ) throw new Error( `no view transform with id ${id}` );
500
+
501
+ if ( this._isStale( t ) ) t = this._rebakeView( t ) ?? t;
502
+
503
+ this._activeView = t.source === 'ocio' ? { ...t.ocio, id: t.id, name: t.name } : null;
504
+ this._lastUsed.set( id, ++ this._useClock );
505
+ if ( t.source === 'builtin' ) this._lastBuiltin = id;
506
+
507
+ if ( this._renderer ) this._renderer.toneMapping = id;
508
+ this._applyRendererOutputColorSpace();
509
+ this._emit( 'view', t );
510
+ return t;
511
+
512
+ }
513
+
514
+ /**
515
+ * Bake and select one display/view/look combination in one call.
516
+ * @returns {Object} the registry entry
517
+ */
518
+ setView( { display, view, look = null, size } = {} ) {
519
+
520
+ if ( ! hasConfig() ) throw new Error( 'load an OCIO config first' );
521
+
522
+ // Matched on what the artist chose, not on how it was baked: an entry baked under the
523
+ // previous $SHOT or working space is the same view, just stale, and is refreshed in place.
524
+ const existing = [ ...VIEW_TRANSFORMS.values() ].find( t =>
525
+ t.source === 'ocio' && t.ocio.display === display && t.ocio.view === view
526
+ && ( t.ocio.look ?? null ) === ( look ?? null )
527
+ && ( ! size || t.ocio.size === size )
528
+ );
529
+
530
+ if ( ! existing ) this._makeRoomForTable();
531
+
532
+ const entry = existing ?? addOcioView( {
533
+ display, view, look, context: this._context,
534
+ source: this.workingSpace,
535
+ ...( size ? { size } : {} ),
536
+ } );
537
+
538
+ this.setActiveView( entry.id );
539
+ return getViewTransform( entry.id );
540
+
541
+ }
542
+
543
+ /**
544
+ * Free a table slot by dropping the least recently selected OCIO view, if the registry is full.
545
+ *
546
+ * Every display, view and look combination is its own baked table, and the readback can only
547
+ * bind twelve. Without this a user browsing the menus hits an error on roughly their
548
+ * thirteenth choice. The active view is never the one dropped.
549
+ */
550
+ _makeRoomForTable() {
551
+
552
+ if ( countTableTransforms() < MAX_TABLE_TRANSFORMS ) return;
553
+
554
+ const active = this._renderer?.toneMapping ?? this._activeView?.id ?? null;
555
+ const candidates = [ ...VIEW_TRANSFORMS.values() ]
556
+ .filter( t => t.source === 'ocio' && t.id !== active )
557
+ .sort( ( a, b ) => ( this._lastUsed.get( a.id ) ?? 0 ) - ( this._lastUsed.get( b.id ) ?? 0 ) );
558
+
559
+ const victim = candidates[ 0 ];
560
+ if ( ! victim ) return;
561
+
562
+ forgetOcioView( victim.id );
563
+ removeViewTransform( victim.id );
564
+ this._lastUsed.delete( victim.id );
565
+
566
+ }
567
+
568
+ /** Swap the look on the active view, rebaking it. */
569
+ setLook( look ) {
570
+
571
+ if ( ! this._activeView ) throw new Error( 'no OCIO view is active' );
572
+ return this.setView( { ...this._activeView, look } );
573
+
574
+ }
575
+
576
+ /**
577
+ * Set the context variables a config resolves `$VAR` against — how a studio config picks a
578
+ * per-shot LUT. Rebakes every registered view, since their tables were built with the old ones.
579
+ *
580
+ * @param {?Object<string,string>} vars - e.g. `{ SHOT: '010', SEQ: 'abc' }`
581
+ */
582
+ setContext( vars ) {
583
+
584
+ const next = vars && Object.keys( vars ).length ? { ...vars } : null;
585
+ if ( JSON.stringify( next ) === JSON.stringify( this._context ) ) return;
586
+
587
+ this._context = next;
588
+
589
+ if ( hasConfig() ) {
590
+
591
+ // The runtime caches processors per context. Without dropping those, a rebake hands
592
+ // back the table built from the previous $SHOT.
593
+ if ( this._context ) getRuntime()?.invalidateContext( this._context );
594
+ clearColorCaches();
595
+ this._rebakeViews();
596
+
597
+ }
598
+
599
+ this._emit( 'context', this._context );
600
+
601
+ }
602
+
603
+ get context() {
604
+
605
+ return this._context;
606
+
607
+ }
608
+
609
+ /** Whether an OCIO view was baked under a context or working space that is no longer current. */
610
+ _isStale( t ) {
611
+
612
+ // Without a config there is nothing to rebake a baked view against.
613
+ if ( t.source !== 'ocio' || ! hasConfig() ) return false;
614
+ return t.ocio.source !== this.workingSpace
615
+ || JSON.stringify( t.ocio.context ?? null ) !== JSON.stringify( this._context ?? null );
616
+
617
+ }
618
+
619
+ /**
620
+ * Rebake one OCIO view in place under the current context and working space, keeping its id.
621
+ * @returns {?Object} the new entry, or null when the bake failed
622
+ */
623
+ _rebakeView( t ) {
624
+
625
+ try {
626
+
627
+ const rebaked = addOcioView( {
628
+ id: t.id,
629
+ display: t.ocio.display,
630
+ view: t.ocio.view,
631
+ look: t.ocio.look,
632
+ context: this._context,
633
+ source: this.workingSpace,
634
+ size: t.ocio.size,
635
+ minEv: t.ocio.minEv,
636
+ maxEv: t.ocio.maxEv,
637
+ } );
638
+
639
+ // A rebake of the same shape writes new pixels into the texture the shader graph already
640
+ // points at, so the old and new entries share one texture. Disposing it unconditionally
641
+ // would free the one now in use.
642
+ const old = t.table?.texture;
643
+ if ( old && old !== rebaked.table?.texture ) old.dispose?.();
644
+ return rebaked;
645
+
646
+ } catch ( err ) {
647
+
648
+ this._issues?.warn(
649
+ ISSUE_CODES.VIEW_TRANSFORM_BAKE_FAILED,
650
+ `could not rebake "${t.name}": ${err.message}`,
651
+ { id: t.id }
652
+ );
653
+ return null;
654
+
655
+ }
656
+
657
+ }
658
+
659
+ /**
660
+ * Rebake the view on screen now; leave the rest for when they are next chosen.
661
+ *
662
+ * Every registered view was baked under the old context or working space, but only the active
663
+ * one is being looked at — and at ~0.1 s a view, rebaking twelve on every `$SHOT` change was
664
+ * over a second of frozen UI for tables nobody was using. `setActiveView` refreshes a stale
665
+ * one the moment it is selected, so nothing can render from an old table.
666
+ */
667
+ _rebakeViews() {
668
+
669
+ const activeId = this._renderer?.toneMapping ?? this._activeView?.id ?? null;
670
+ const t = activeId === null ? null : getViewTransform( activeId );
671
+ if ( ! t || ! this._isStale( t ) ) return;
672
+
673
+ const rebaked = this._rebakeView( t );
674
+ if ( rebaked ) this._activeView = { ...rebaked.ocio, id: rebaked.id, name: rebaked.name };
675
+
676
+ }
677
+
678
+ // ── the renderer ────────────────────────────────────────────────────────────────────────
679
+
680
+ /** Hand the registry's TSL nodes to a renderer, and remember it for later view changes. */
681
+ attachRenderer( renderer ) {
682
+
683
+ this._renderer = renderer;
684
+ if ( getViewTransform( renderer.toneMapping )?.source === 'builtin' ) this._lastBuiltin = renderer.toneMapping;
685
+ const n = registerWithRenderer( renderer );
686
+ this._applyRendererOutputColorSpace();
687
+ return n;
688
+
689
+ }
690
+
691
+ /**
692
+ * An OCIO view already produced display-encoded colour, so the output pass must not encode it
693
+ * a second time. A built-in curve returns linear and does want the sRGB step.
694
+ */
695
+ _applyRendererOutputColorSpace() {
696
+
697
+ if ( ! this._renderer ) return;
698
+
699
+ const t = getViewTransform( this._renderer.toneMapping );
700
+ this._renderer.outputColorSpace = t?.outputEncoded ? LinearSRGBColorSpace : SRGBColorSpace;
701
+ this._applyCanvasColorSpace( t?.source === 'ocio' ? canvasColorSpaceFor( t.ocio.display ) : 'srgb' );
702
+
703
+ }
704
+
705
+ /**
706
+ * Tell the browser what the canvas holds.
707
+ *
708
+ * three.js configures the WebGPU canvas without a colour space, which means sRGB — so a Display
709
+ * P3 view, whose values are P3-encoded, was shown as if it were sRGB: every saturated colour in
710
+ * the wrong place. Reconfiguring with `display-p3` makes the browser read them right; on an sRGB
711
+ * screen it gamut-maps them instead of misreading them.
712
+ */
713
+ _applyCanvasColorSpace( colorSpace ) {
714
+
715
+ const canvas = this._renderer?.domElement;
716
+ const context = canvas?.getContext?.( 'webgpu' );
717
+ const current = context?.getConfiguration?.();
718
+ if ( ! current ) return;
719
+
720
+ // three.js configures the context again on every resize, without a colour space — so a
721
+ // one-off configure here was undone by the next resize. Wrapping this one context's
722
+ // `configure` makes every later call keep the colour space chosen here.
723
+ if ( ! context.__rayzeeConfigure ) {
724
+
725
+ const configure = context.configure.bind( context );
726
+ context.__rayzeeConfigure = configure;
727
+ context.configure = config => configure( { ...config, colorSpace: context.__rayzeeColorSpace ?? config.colorSpace } );
728
+
729
+ }
730
+
731
+ context.__rayzeeColorSpace = colorSpace;
732
+ if ( current.colorSpace === colorSpace ) return;
733
+
734
+ try {
735
+
736
+ context.configure( { ...current, colorSpace } );
737
+
738
+ } catch ( error ) {
739
+
740
+ this._issues?.warn(
741
+ ISSUE_CODES.VIEW_TRANSFORM_DISPLAY_MISMATCH,
742
+ `the canvas could not be set to ${colorSpace}: ${error.message}`,
743
+ { colorSpace }
744
+ );
745
+
746
+ }
747
+
748
+ }
749
+
750
+ // ── input ───────────────────────────────────────────────────────────────────────────────
751
+
752
+ /** What colour space a file is in, per the config's rules. Null without a config. */
753
+ resolveInput( filePath, options ) {
754
+
755
+ return resolveInputSpace( filePath, options );
756
+
757
+ }
758
+
759
+ /** What colour space a three.js texture is in. Null without a config, or for data textures. */
760
+ textureSpace( texture, filePath ) {
761
+
762
+ return textureInputSpace( texture, filePath );
763
+
764
+ }
765
+
766
+ /** As `textureSpace`, plus how it was decided — `'tag'`, `'override'`, `'rule'` or `'three'`. */
767
+ resolveTexture( texture, filePath ) {
768
+
769
+ return resolveTextureSpace( texture, filePath );
770
+
771
+ }
772
+
773
+ /**
774
+ * What a colour map's colour-space menu should show: the artist's own choice, and what the
775
+ * engine would decide without one.
776
+ *
777
+ * @returns {{ choice: ?string, auto: ?{ colorSpace: string, via: string } }}
778
+ * `choice` is a config space, `'srgb'`, `'linear'`, or null for automatic
779
+ */
780
+ describeTextureSpace( texture ) {
781
+
782
+ const ud = texture?.userData ?? {};
783
+ const choice = ud.ocioColorSpace ?? ud.__rayzeeColorSpaceChoice ?? null;
784
+
785
+ let auto = null;
786
+ const original = ud.__rayzeeOriginalColorSpace ?? texture?.colorSpace;
787
+ if ( hasConfig() ) {
788
+
789
+ const probe = { ...texture, name: texture?.name, image: texture?.image, colorSpace: original, userData: {} };
790
+ auto = resolveTextureSpace( probe );
791
+
792
+ }
793
+
794
+ if ( ! auto ) auto = { colorSpace: original === SRGBColorSpace ? 'sRGB' : 'Linear', via: 'three' };
795
+ return { choice, auto };
796
+
797
+ }
798
+
799
+ /**
800
+ * Everything a converted texture depends on, as one string, for cache keys.
801
+ *
802
+ * The working space alone is not enough: an override or a different config changes what a
803
+ * texture is *read as* without changing what it is converted *into*.
804
+ */
805
+ get inputKey() {
806
+
807
+ if ( ! hasConfig() ) return '-';
808
+ return `${describeConfig()?.id}|${this.workingSpace}|${getInputVersion()}|${JSON.stringify( this._context )}`;
809
+
810
+ }
811
+
812
+ /** Pin a colour space for files matching a path fragment or pattern. */
813
+ overrideInput( match, colorSpace ) {
814
+
815
+ setInputOverride( match, colorSpace );
816
+
817
+ }
818
+
819
+ fileRules() {
820
+
821
+ return listFileRules();
822
+
823
+ }
824
+
825
+ /** Convert a float RGBA buffer into the working space, in place. */
826
+ toWorkingPixels( rgba, from ) {
827
+
828
+ if ( ! hasConfig() || ! from ) return rgba;
829
+ return convertPixelsF32( rgba, from, this.workingSpace, { context: this._context } );
830
+
831
+ }
832
+
833
+ /**
834
+ * The config's name for the space the engine natively works in — linear, sRGB primaries.
835
+ *
836
+ * Needed because the engine's existing pipeline already stores textures sRGB-encoded over
837
+ * Rec.709 primaries. Converting one into an adopted working space is then only a primaries
838
+ * change, and this names the space to measure that change from.
839
+ */
840
+ get nativeLinearSpace() {
841
+
842
+ return findNativeLinearSpace( getConfigInfo() );
843
+
844
+ }
845
+
846
+ /**
847
+ * The 3×3 taking the engine's native linear Rec.709 into the working space, or null when no
848
+ * conversion is needed or the config cannot express it as a matrix.
849
+ */
850
+ /** Recompute the published primaries matrix. Called whenever the config or space changes. */
851
+ _publishWorkingMatrix() {
852
+
853
+ if ( active !== null && active !== this ) return;
854
+ setWorkingMatrix( hasConfig() ? this.primariesMatrixToWorking() : null, this.workingSpace );
855
+
856
+ }
857
+
858
+ primariesMatrixToWorking() {
859
+
860
+ if ( ! hasConfig() || ! this.workingSpaceAdopted ) return null;
861
+
862
+ const from = this.nativeLinearSpace;
863
+ if ( ! from || from === this.workingSpace ) return null;
864
+
865
+ const m = extractMatrix( from, this.workingSpace, { context: this._context } );
866
+ if ( ! m ) return null;
867
+
868
+ const isIdentity = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ].every( ( v, i ) => Math.abs( m[ i ] - v ) < 1e-6 );
869
+ return isIdentity ? null : m;
870
+
871
+ }
872
+
873
+ /**
874
+ * Apply the primaries change to one linear RGB triple, in place.
875
+ *
876
+ * Delegates to `WorkingMatrix`, which is where the hot consumers read it from — this is the
877
+ * same conversion, reachable from the façade.
878
+ *
879
+ * @param {Float32Array|number[]} rgb
880
+ * @returns {boolean} whether anything changed
881
+ */
882
+ convertLinearTriple( rgb ) {
883
+
884
+ return convertLinearTriple( rgb );
885
+
886
+ }
887
+
888
+ /** Apply the primaries change to interleaved linear triples inside a larger buffer. */
889
+ convertLinearTriples( data, stride, offsets ) {
890
+
891
+ return convertLinearTriples( data, stride, offsets );
892
+
893
+ }
894
+
895
+ /**
896
+ * Move a whole texture's pixels into the working space, in place.
897
+ *
898
+ * Handles the three shapes an environment or HDR texture arrives in — float32, float16 and
899
+ * bytes — because an HDRI is usually half-float, and silently skipping it would leave the
900
+ * dominant light source in the wrong space while every surface moved.
901
+ *
902
+ * ⚠️ Unlike textures and materials, this cannot be rebuilt from a pristine source: the engine
903
+ * holds one copy and converts it where it lies. So the space it currently holds is recorded on
904
+ * the texture, and a later change converts *from that*, rather than refusing because it has
905
+ * been touched once. That is what lets the working space be turned back off.
906
+ *
907
+ * @param {import('three').Texture} texture
908
+ * @param {Object} [options]
909
+ * @param {?string} [options.space] - the space it arrived in; omitted means linear Rec.709
910
+ * @returns {boolean} whether anything was changed
911
+ */
912
+ convertTexturePixels( texture, { space = null } = {} ) {
913
+
914
+ if ( ! hasConfig() ) return false;
915
+
916
+ const image = texture?.image;
917
+ const data = image?.data;
918
+ if ( ! data ) return false;
919
+
920
+ const native = this.nativeLinearSpace;
921
+ const from = texture.userData?.__rayzeeColorSpace ?? space ?? native;
922
+ const to = this.workingSpaceAdopted ? this.workingSpace : native;
923
+
924
+ if ( ! from || ! to || from === to ) return false;
925
+
926
+ let changed = false;
927
+
928
+ if ( data instanceof Float32Array ) {
929
+
930
+ convertPixelsF32( data, from, to, { context: this._context } );
931
+ changed = true;
932
+
933
+ } else if ( data instanceof Uint16Array ) {
934
+
935
+ const f32 = unpackHalf( data );
936
+ convertPixelsF32( f32, from, to, { context: this._context } );
937
+ data.set( packHalf( f32 ) );
938
+ changed = true;
939
+
940
+ } else if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) {
941
+
942
+ convertEncodedRGBA8( data, from, to, {
943
+ context: this._context, inputEncoding: 'srgb', outputEncoding: 'srgb',
944
+ } );
945
+ changed = true;
946
+
947
+ } else {
948
+
949
+ this._issues?.warn(
950
+ ISSUE_CODES.VIEW_TRANSFORM_BAKE_FAILED,
951
+ `texture data is ${data.constructor?.name ?? typeof data}, which cannot be colour-converted`,
952
+ { from, to }
953
+ );
954
+ return false;
955
+
956
+ }
957
+
958
+ texture.userData = texture.userData ?? {};
959
+ texture.userData.__rayzeeColorSpace = to;
960
+ texture.needsUpdate = true;
961
+ return changed;
962
+
963
+ }
964
+
965
+ /**
966
+ * Convert an 8-bit RGBA texture into the working space, in place.
967
+ *
968
+ * Two routes, and which one runs matters:
969
+ * - `space` given — the texture is in some named space (ACEScct, LogC, sRGB), and the full
970
+ * OCIO transform runs on the raw bytes. Its own transfer is part of that
971
+ * transform, so nothing is decoded first.
972
+ * - `space` omitted — the texture is already what the engine assumes, and only the primaries
973
+ * change, done directly on the bytes.
974
+ *
975
+ * @param {Uint8Array|Uint8ClampedArray} bytes
976
+ * @param {Object} [options]
977
+ * @param {?string} [options.space] - the texture's own colour space
978
+ * @param {'srgb'|'linear'} [options.encoding='srgb'] - how the bytes are stored, in and out
979
+ * @returns {boolean} whether anything was changed
980
+ */
981
+ convertTextureBytes( bytes, { space = null, encoding = 'srgb' } = {} ) {
982
+
983
+ if ( ! hasConfig() ) return false;
984
+
985
+ if ( space ) {
986
+
987
+ if ( space === this.workingSpace ) return false;
988
+ convertEncodedRGBA8( bytes, space, this.workingSpace, {
989
+ context: this._context,
990
+ inputEncoding: 'raw',
991
+ outputEncoding: encoding,
992
+ } );
993
+ return true;
994
+
995
+ }
996
+
997
+ const m = this.primariesMatrixToWorking();
998
+ if ( ! m ) return false;
999
+
1000
+ applyMatrixRGBA8( bytes, m, encoding );
1001
+ return true;
1002
+
1003
+ }
1004
+
1005
+ /** Convert one colour into the working space. Cached, so repeated tints are free. */
1006
+ toWorkingColor( rgb, from ) {
1007
+
1008
+ if ( ! hasConfig() || ! from ) return [ rgb[ 0 ], rgb[ 1 ], rgb[ 2 ] ];
1009
+ return convertColor( rgb, from, this.workingSpace, { context: this._context } );
1010
+
1011
+ }
1012
+
1013
+ /** Convert one colour between any two named spaces. */
1014
+ convert( rgb, from, to ) {
1015
+
1016
+ return convertColor( rgb, from, to, { context: this._context } );
1017
+
1018
+ }
1019
+
1020
+ // ── export ──────────────────────────────────────────────────────────────────────────────
1021
+
1022
+ /**
1023
+ * The space a saved render should be written in, or null to save what the view produced.
1024
+ *
1025
+ * Setting this is how you deliver an EXR in ACES2065-1 while still grading through an sRGB
1026
+ * view on screen — the two are different questions and a colour-managed pipeline keeps them
1027
+ * apart.
1028
+ */
1029
+ get exportSpace() {
1030
+
1031
+ return this._exportSpace;
1032
+
1033
+ }
1034
+
1035
+ setExportSpace( name ) {
1036
+
1037
+ if ( name !== null && name !== undefined ) {
1038
+
1039
+ if ( ! hasConfig() ) throw new Error( 'load an OCIO config before choosing an export space' );
1040
+ if ( ! hasColorSpace( name ) ) throw new Error( `config has no colour space "${name}"` );
1041
+
1042
+ }
1043
+
1044
+ this._exportSpace = name ?? null;
1045
+ this._emit( 'export', this._exportSpace );
1046
+
1047
+ }
1048
+
1049
+ /**
1050
+ * Convert a scene-referred float buffer for delivery, in place.
1051
+ *
1052
+ * @param {Float32Array} rgba - working-space linear, 4 floats per pixel
1053
+ * @param {?string} [space] - overrides `exportSpace` for this call
1054
+ * @returns {{ rgba: Float32Array, colorSpace: string }}
1055
+ */
1056
+ exportPixels( rgba, space = undefined ) {
1057
+
1058
+ const target = space === undefined ? this._exportSpace : space;
1059
+ if ( ! target || ! hasConfig() ) return { rgba, colorSpace: this.workingSpace };
1060
+
1061
+ convertPixelsF32( rgba, this.workingSpace, target, { context: this._context } );
1062
+ return { rgba, colorSpace: target };
1063
+
1064
+ }
1065
+
1066
+ // ── lifecycle ───────────────────────────────────────────────────────────────────────────
1067
+
1068
+ /** A snapshot a host can render a panel from without calling six getters. */
1069
+ status() {
1070
+
1071
+ const config = describeConfig();
1072
+ const active = this._renderer ? getViewTransform( this._renderer.toneMapping ) : null;
1073
+
1074
+ return {
1075
+ config,
1076
+ workingSpace: this.workingSpace,
1077
+ workingSpaceAdopted: this.workingSpaceAdopted,
1078
+ activeTransform: active ? { id: active.id, name: active.name, source: active.source } : null,
1079
+ activeView: this._activeView,
1080
+ context: this._context,
1081
+ exportSpace: this._exportSpace,
1082
+ views: listViewTransforms(),
1083
+ registryVersion: getRegistryVersion(),
1084
+ bakeError: active?.error ?? null,
1085
+ };
1086
+
1087
+ }
1088
+
1089
+ dispose() {
1090
+
1091
+ this._offRegistry?.();
1092
+ this._listeners.clear();
1093
+ this._releaseViews();
1094
+ setOcioIssueLog( null );
1095
+ this._renderer = null;
1096
+ this._issues = null;
1097
+ if ( active === this ) {
1098
+
1099
+ // The matrix is module state that materials and lights read with no idea whose it is.
1100
+ // Left behind, the next app would convert every colour into a space it never adopted.
1101
+ setWorkingMatrix( null );
1102
+ active = null;
1103
+
1104
+ }
1105
+
1106
+ }
1107
+
1108
+ /** Tear down the shared runtime as well. Tests use this; an app normally should not. */
1109
+ static resetAll() {
1110
+
1111
+ active = null;
1112
+ setWorkingMatrix( null );
1113
+ disposeOcioViewTextures();
1114
+ resetViewTransforms();
1115
+ resetConfigHandle();
1116
+ clearInputOverrides();
1117
+ resetOcio();
1118
+
1119
+ }
1120
+
1121
+ }
1122
+
1123
+ /** Convenience for callers that only want to know whether anything is colour-managed. */
1124
+ export function isColorManaged() {
1125
+
1126
+ return hasConfig();
1127
+
1128
+ }
1129
+
1130
+ export { getConfigInfo };