@vitreajs/vitrea 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -220,6 +220,12 @@ function occlusionAlphaUnderPolicy(nominal, occlusion, lift = INCREASED_OCCLUSIO
220
220
  return 1;
221
221
  }
222
222
  }
223
+ function occlusionLiftForPolicy(policy, profile = DEFAULT_MATERIAL_PROFILE) {
224
+ if (policy.occlusion !== "increased")
225
+ return profile.increasedOcclusionLift;
226
+ const levels = profile.increasedOcclusionLiftByPolicy;
227
+ return policy.ambientTint === "reduced" ? levels?.increaseContrast ?? profile.increasedOcclusionLift : levels?.reduceTransparency ?? profile.increasedOcclusionLift;
228
+ }
223
229
  var DEFAULT_MATERIAL_PROFILE = {
224
230
  optics: {
225
231
  // σ = 8 for the regular variant, which keeps this package's blur and
@@ -1152,8 +1158,26 @@ function rejectRetiredOuterShadowLeaves(patch) {
1152
1158
  throw new Error(`outerShadow.${leaf} was retired by W14 G1 (claims \xA75.62) and is replaced by ${replacement}. Applying this patch would have rendered the default shadow while recording itself as configured. It is refused rather than mapped: a single span-flat amplitude is the material the measurement retired.`);
1153
1159
  }
1154
1160
  }
1161
+ function rejectMixedBackdropToneArity(anchorX, thin, thick) {
1162
+ for (const [name, row] of [
1163
+ ["backdropToneAnchorX", anchorX],
1164
+ ["backdropToneResponseThin", thin],
1165
+ ["backdropToneResponseThick", thick]
1166
+ ]) {
1167
+ if (Array.isArray(row) && (row.length === 3 || row.length === 4))
1168
+ continue;
1169
+ throw new Error(`The backdrop tone response's ${name} is ${JSON.stringify(row) ?? String(row)}, which is not an array of three or four knots. The curve has exactly those two forms, and anything else is read as a different one by each tier: the CPU curve and the CSS mirror take the four-knot branch for any length but three, where the shader takes the three-knot branch for any length but four.`);
1170
+ }
1171
+ if (anchorX.length === thin.length && thin.length === thick.length)
1172
+ return;
1173
+ throw new Error(`The backdrop tone response's three rows resolved to different knot counts \u2014 backdropToneAnchorX ${anchorX.length}, backdropToneResponseThin ${thin.length}, backdropToneResponseThick ${thick.length}. They are one curve's knots and that curve's levels at its two thickness ends, and the shader reads the knot count off the anchors alone, so a mixed triplet renders as NaN on the CPU curve and as a fabricated segment on the GPU. A patch moving the response to a new knot count has to name all three rows.`);
1174
+ }
1155
1175
  function withMaterialOverrides(base, patch) {
1156
1176
  rejectRetiredOuterShadowLeaves(patch.outerShadow);
1177
+ const backdropToneAbscissa = patch.backdropToneAbscissa === void 0 ? base.backdropToneAbscissa : patch.backdropToneAbscissa;
1178
+ if (backdropToneAbscissa !== void 0 && backdropToneAbscissa !== "source" && (backdropToneAbscissa === null || typeof backdropToneAbscissa !== "object" || backdropToneAbscissa.kind !== "silhouette" || Object.keys(backdropToneAbscissa).some((key) => key !== "kind"))) {
1179
+ throw new TypeError("backdropToneAbscissa must be source or { kind: silhouette }");
1180
+ }
1157
1181
  const optics = {};
1158
1182
  for (const variant of MATERIAL_VARIANTS) {
1159
1183
  optics[variant] = { ...base.optics[variant], ...patch.optics?.[variant] };
@@ -1162,6 +1186,10 @@ function withMaterialOverrides(base, patch) {
1162
1186
  for (const rung of REFRACTION_LADDER) {
1163
1187
  refractionScale[rung] = patch.refractionScale?.[rung] ?? base.refractionScale[rung];
1164
1188
  }
1189
+ const backdropToneAnchorX = patch.backdropToneAnchorX ?? base.backdropToneAnchorX;
1190
+ const backdropToneResponseThin = patch.backdropToneResponseThin ?? base.backdropToneResponseThin;
1191
+ const backdropToneResponseThick = patch.backdropToneResponseThick ?? base.backdropToneResponseThick;
1192
+ rejectMixedBackdropToneArity(backdropToneAnchorX, backdropToneResponseThin, backdropToneResponseThick);
1165
1193
  return {
1166
1194
  optics,
1167
1195
  adaptiveTintDark: patch.adaptiveTintDark ?? base.adaptiveTintDark,
@@ -1207,6 +1235,10 @@ function withMaterialOverrides(base, patch) {
1207
1235
  lensOvalizationSpanMax: patch.lensOvalizationSpanMax ?? base.lensOvalizationSpanMax,
1208
1236
  reducedTransparencyFrost: patch.reducedTransparencyFrost ?? base.reducedTransparencyFrost,
1209
1237
  increasedOcclusionLift: patch.increasedOcclusionLift ?? base.increasedOcclusionLift,
1238
+ ...(patch.increasedOcclusionLiftByPolicy ?? base.increasedOcclusionLiftByPolicy) === void 0 ? {} : { increasedOcclusionLiftByPolicy: {
1239
+ ...base.increasedOcclusionLiftByPolicy,
1240
+ ...patch.increasedOcclusionLiftByPolicy
1241
+ } },
1210
1242
  strongBorderRim: { ...base.strongBorderRim, ...patch.strongBorderRim },
1211
1243
  reducedTintAdaptation: patch.reducedTintAdaptation ?? base.reducedTintAdaptation,
1212
1244
  tintShadeDark: patch.tintShadeDark ?? base.tintShadeDark,
@@ -1227,9 +1259,10 @@ function withMaterialOverrides(base, patch) {
1227
1259
  rimCollapsed: patch.rimCollapsed ?? base.rimCollapsed,
1228
1260
  rimCollapsedTinted: patch.rimCollapsedTinted ?? base.rimCollapsedTinted,
1229
1261
  rimTintChroma: patch.rimTintChroma ?? base.rimTintChroma,
1230
- backdropToneAnchorX: patch.backdropToneAnchorX ?? base.backdropToneAnchorX,
1231
- backdropToneResponseThin: patch.backdropToneResponseThin ?? base.backdropToneResponseThin,
1232
- backdropToneResponseThick: patch.backdropToneResponseThick ?? base.backdropToneResponseThick,
1262
+ ...backdropToneAbscissa === void 0 ? {} : { backdropToneAbscissa },
1263
+ backdropToneAnchorX,
1264
+ backdropToneResponseThin,
1265
+ backdropToneResponseThick,
1233
1266
  backdropToneResponseStrength: patch.backdropToneResponseStrength ?? base.backdropToneResponseStrength,
1234
1267
  outerShadow: { ...base.outerShadow, ...patch.outerShadow },
1235
1268
  lightDirection: patch.lightDirection ?? base.lightDirection,
@@ -1249,7 +1282,7 @@ function opticsUnderPolicy(optics, policy, profile = DEFAULT_MATERIAL_PROFILE) {
1249
1282
  }
1250
1283
  next = {
1251
1284
  ...next,
1252
- tintAlpha: occlusionAlphaUnderPolicy(next.tintAlpha, policy.occlusion, profile.increasedOcclusionLift)
1285
+ tintAlpha: occlusionAlphaUnderPolicy(next.tintAlpha, policy.occlusion, occlusionLiftForPolicy(policy, profile))
1253
1286
  };
1254
1287
  if (policy.border === "strong") {
1255
1288
  next = {
@@ -1373,19 +1406,45 @@ function backdropToneResponse(encodedInput, thickness, profile = DEFAULT_MATERIA
1373
1406
  const xs = profile.backdropToneAnchorX;
1374
1407
  const f = smoothstep(0, 1, thickness);
1375
1408
  const ys = [0, 1, 2].map((i) => (profile.backdropToneResponseThin[i] ?? 0) + ((profile.backdropToneResponseThick[i] ?? 0) - (profile.backdropToneResponseThin[i] ?? 0)) * f);
1376
- const x = Math.min(xs[2], Math.max(xs[0], encodedInput));
1377
- const h0 = xs[1] - xs[0];
1378
- const h1 = xs[2] - xs[1];
1379
- const d0 = (ys[1] - ys[0]) / h0;
1380
- const d1 = (ys[2] - ys[1]) / h1;
1381
- const m1 = d0 * d1 <= 0 ? 0 : 2 * d0 * d1 / (d0 + d1);
1382
- const seg = x <= xs[1] ? 0 : 1;
1383
- const h = seg === 0 ? h0 : h1;
1384
- const t = (x - (seg === 0 ? xs[0] : xs[1])) / h;
1385
- const y0 = seg === 0 ? ys[0] : ys[1];
1386
- const y1 = seg === 0 ? ys[1] : ys[2];
1387
- const s0 = seg === 0 ? d0 : m1;
1388
- const s1 = seg === 0 ? m1 : d1;
1409
+ let x, h, t, y0, y1, s0, s1;
1410
+ if (xs.length === 3) {
1411
+ x = Math.min(xs[2], Math.max(xs[0], encodedInput));
1412
+ const h0 = xs[1] - xs[0];
1413
+ const h1 = xs[2] - xs[1];
1414
+ const d0 = (ys[1] - ys[0]) / h0;
1415
+ const d1 = (ys[2] - ys[1]) / h1;
1416
+ const m1 = d0 * d1 <= 0 ? 0 : 2 * d0 * d1 / (d0 + d1);
1417
+ const seg = x <= xs[1] ? 0 : 1;
1418
+ h = seg === 0 ? h0 : h1;
1419
+ t = (x - (seg === 0 ? xs[0] : xs[1])) / h;
1420
+ y0 = seg === 0 ? ys[0] : ys[1];
1421
+ y1 = seg === 0 ? ys[1] : ys[2];
1422
+ s0 = seg === 0 ? d0 : m1;
1423
+ s1 = seg === 0 ? m1 : d1;
1424
+ } else {
1425
+ const ys4 = [
1426
+ ys[0],
1427
+ ys[1],
1428
+ ys[2],
1429
+ profile.backdropToneResponseThin[3] + (profile.backdropToneResponseThick[3] - profile.backdropToneResponseThin[3]) * f
1430
+ ];
1431
+ x = Math.min(xs[3], Math.max(xs[0], encodedInput));
1432
+ const h0 = xs[1] - xs[0], h1 = xs[2] - xs[1], h2 = xs[3] - xs[2];
1433
+ const d0 = (ys4[1] - ys4[0]) / h0;
1434
+ const d1 = (ys4[2] - ys4[1]) / h1;
1435
+ const d2 = (ys4[3] - ys4[2]) / h2;
1436
+ const m1 = d0 * d1 <= 0 ? 0 : 2 * d0 * d1 / (d0 + d1);
1437
+ const m2 = d1 * d2 <= 0 ? 0 : 2 * d1 * d2 / (d1 + d2);
1438
+ const seg = x <= xs[1] ? 0 : x <= xs[2] ? 1 : 2;
1439
+ const hs = [h0, h1, h2];
1440
+ const slopes = [d0, m1, m2, d2];
1441
+ h = hs[seg];
1442
+ t = (x - xs[seg]) / h;
1443
+ y0 = ys4[seg];
1444
+ y1 = ys4[seg + 1];
1445
+ s0 = slopes[seg];
1446
+ s1 = slopes[seg + 1];
1447
+ }
1389
1448
  return y0 * (1 + 2 * t) * (1 - t) * (1 - t) + s0 * h * t * (1 - t) * (1 - t) + y1 * t * t * (3 - 2 * t) + s1 * h * t * t * (t - 1) + levelFar;
1390
1449
  }
1391
1450
  function backdropToneSolveWeight(encodedInput, profile = DEFAULT_MATERIAL_PROFILE) {
@@ -3386,7 +3445,7 @@ var WGSL_HIGHLIGHT_PASS = `struct HighlightUniforms {
3386
3445
  glow : vec4f,
3387
3446
  /// highlight colour, linear light (xyz), unused (w)
3388
3447
  colour : vec4f,
3389
- /// fieldSize.xy, fieldUpsampled (z), unused (w)
3448
+ /// fieldSize.xy, fieldUpsampled (z), local tone field present (w, W28)
3390
3449
  flags : vec4f,
3391
3450
  /// backdrop tone adaptation (W7), exactly as the optics pass takes it:
3392
3451
  /// backdropToneLow, backdropToneHigh, the size bias already divided by the
@@ -3416,6 +3475,7 @@ var WGSL_HIGHLIGHT_PASS = `struct HighlightUniforms {
3416
3475
  /// bound here for the same reason the optics pass binds it: a highlight is the
3417
3476
  /// material catching light, and a material that is not there catches none.
3418
3477
  @group(0) @binding(4) var presenceTexture : texture_2d<f32>;
3478
+ @group(0) @binding(5) var localToneTexture : texture_2d<f32>;
3419
3479
 
3420
3480
  const TAU = 6.283185307179586;
3421
3481
 
@@ -3445,6 +3505,16 @@ fn fs_highlight(in : FullscreenOut) -> @location(0) vec4f {
3445
3505
  aux = textureLoad(auxTexture, texel, 0);
3446
3506
  presence = textureLoad(presenceTexture, texel, 0);
3447
3507
  }
3508
+ var toneStrength = hu.toneAdapt.w;
3509
+ var toneLevel = hu.toneLevel.x;
3510
+ if (hu.flags.w > 0.5) {
3511
+ if (hu.flags.z > 0.5) {
3512
+ toneLevel = textureSampleLevel(localToneTexture, fieldSampler, fieldUv, 0.0).w;
3513
+ } else {
3514
+ toneLevel = textureLoad(localToneTexture, vec2i(fieldUv * hu.flags.xy), 0).w;
3515
+ }
3516
+ }
3517
+ if (toneLevel < 0.0) { toneStrength = 0.0; }
3448
3518
  let mat = clamp(presence.x, 0.0, 1.0);
3449
3519
  let d = field.x;
3450
3520
  let normal = field.yz;
@@ -3493,14 +3563,14 @@ fn fs_highlight(in : FullscreenOut) -> @location(0) vec4f {
3493
3563
  );
3494
3564
  let sizeK = clamp(spanT * spanT * (3.0 - 2.0 * spanT) * hu.toneLevel.w, 0.0, 1.0);
3495
3565
  var toneAdapt = 0.0;
3496
- if (hu.toneAdapt.w > 0.0) {
3497
- let toneX = hu.toneLevel.x + hu.toneAdapt.z * sizeK;
3566
+ if (toneStrength > 0.0) {
3567
+ let toneX = toneLevel + hu.toneAdapt.z * sizeK;
3498
3568
  let toneT = clamp(
3499
3569
  (toneX - hu.toneAdapt.x) / max(hu.toneAdapt.y - hu.toneAdapt.x, 1e-6),
3500
3570
  0.0,
3501
3571
  1.0,
3502
3572
  );
3503
- toneAdapt = clamp(hu.toneAdapt.w, 0.0, 1.0) * (1.0 - toneT * toneT * (3.0 - 2.0 * toneT));
3573
+ toneAdapt = clamp(toneStrength, 0.0, 1.0) * (1.0 - toneT * toneT * (3.0 - 2.0 * toneT));
3504
3574
  }
3505
3575
 
3506
3576
  /*
@@ -3660,6 +3730,12 @@ var WGSL_OPTICS_PASS = `struct OpticsUniforms {
3660
3730
  /// (z) and (w) carry its reference level and minimum tint contrast.
3661
3731
  /// Texture draws keep those lanes zero, so their input bytes do not move.
3662
3732
  heavyTap : vec4f,
3733
+ /// Decision Log 18's optional fourth response knot: encoded x, thin y,
3734
+ /// thick z, and a length gate w. Appended so every three-knot field keeps its
3735
+ /// byte offset and its original shader branch.
3736
+ toneExtra : vec4f,
3737
+ /// W28: whether the per-surface reference field is present (x).
3738
+ localTone : vec4f,
3663
3739
  };
3664
3740
 
3665
3741
  @group(0) @binding(0) var<uniform> ou : OpticsUniforms;
@@ -3682,6 +3758,7 @@ var WGSL_OPTICS_PASS = `struct OpticsUniforms {
3682
3758
  /// channel, unioned in the field pass like every other per-surface scalar. One
3683
3759
  /// channel, in the field pass's fourth target \u2014 see 'wgsl/field.ts'.
3684
3760
  @group(0) @binding(9) var presenceTexture : texture_2d<f32>;
3761
+ @group(0) @binding(10) var localToneTexture : texture_2d<f32>;
3685
3762
 
3686
3763
  /// One encoded sRGB channel from a linear one \u2014 the space the backdrop tone
3687
3764
  /// response's anchors live in (W9). Mirrors material.ts's 'linearToSrgbChannel'.
@@ -3698,6 +3775,35 @@ fn tone_response(x : f32, sizeK : f32, levelFar : f32) -> f32 {
3698
3775
  let f = sizeK * sizeK * (3.0 - 2.0 * sizeK);
3699
3776
  let ys = mix(ou.toneRowThin.xyz, ou.toneRowThick.xyz, vec3f(f));
3700
3777
  let xs = ou.toneAnchor.xyz;
3778
+ if (ou.toneExtra.w > 0.5) {
3779
+ let ys4 = vec4f(ys, mix(ou.toneExtra.y, ou.toneExtra.z, f));
3780
+ let xs4 = vec4f(xs, ou.toneExtra.x);
3781
+ let xc4 = clamp(x, xs4.x, xs4.w);
3782
+ let h0 = max(xs4.y - xs4.x, 1e-4);
3783
+ let h1 = max(xs4.z - xs4.y, 1e-4);
3784
+ let h2 = max(xs4.w - xs4.z, 1e-4);
3785
+ let d0 = (ys4.y - ys4.x) / h0;
3786
+ let d1 = (ys4.z - ys4.y) / h1;
3787
+ let d2 = (ys4.w - ys4.z) / h2;
3788
+ var m1 = 0.0; var m2 = 0.0;
3789
+ if (d0 * d1 > 0.0) { m1 = 2.0 * d0 * d1 / (d0 + d1); }
3790
+ if (d1 * d2 > 0.0) { m2 = 2.0 * d1 * d2 / (d1 + d2); }
3791
+ var h = h0; var t = (xc4 - xs4.x) / h0;
3792
+ var y0 = ys4.x; var y1 = ys4.y; var s0 = d0; var s1 = m1;
3793
+ if (xc4 > xs4.y) {
3794
+ h = h1; t = (xc4 - xs4.y) / h1;
3795
+ y0 = ys4.y; y1 = ys4.z; s0 = m1; s1 = m2;
3796
+ }
3797
+ if (xc4 > xs4.z) {
3798
+ h = h2; t = (xc4 - xs4.z) / h2;
3799
+ y0 = ys4.z; y1 = ys4.w; s0 = m2; s1 = d2;
3800
+ }
3801
+ return y0 * (1.0 + 2.0 * t) * (1.0 - t) * (1.0 - t)
3802
+ + s0 * h * t * (1.0 - t) * (1.0 - t)
3803
+ + y1 * t * t * (3.0 - 2.0 * t)
3804
+ + s1 * h * t * t * (t - 1.0)
3805
+ + levelFar;
3806
+ }
3701
3807
  let xc = clamp(x, xs.x, xs.z);
3702
3808
  let h0 = max(xs.y - xs.x, 1e-4);
3703
3809
  let h1 = max(xs.z - xs.y, 1e-4);
@@ -3977,6 +4083,27 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
3977
4083
  presence = textureLoad(presenceTexture, texel, 0);
3978
4084
  }
3979
4085
 
4086
+ // The complete reference changes together: response, collapse colour and
4087
+ // compensation, nominal composition, opacity solve, and the tint/rim readouts.
4088
+ // The source branch retains the old uniforms and arithmetic exactly.
4089
+ var toneStrength = ou.toneAdapt.w;
4090
+ var toneColour = ou.toneColour;
4091
+ var toneLinearMean = ou.toneAnchor.w;
4092
+ if (ou.localTone.x > 0.5) {
4093
+ if (ou.flags.w > 0.5) {
4094
+ toneColour = textureSampleLevel(localToneTexture, fieldSampler, in.uv, 0.0);
4095
+ } else {
4096
+ toneColour = textureLoad(localToneTexture, vec2i(in.uv * ou.flags.yz), 0);
4097
+ }
4098
+ toneLinearMean = dot(toneColour.rgb, vec3f(0.2126, 0.7152, 0.0722));
4099
+ // A fully transparent mask measured no colour and has no local authority.
4100
+ if (toneColour.w < 0.0) {
4101
+ toneStrength = 0.0;
4102
+ toneColour = ou.toneColour;
4103
+ toneLinearMean = ou.toneAnchor.w;
4104
+ }
4105
+ }
4106
+
3980
4107
  let d = field.x;
3981
4108
  let normal = field.yz;
3982
4109
  let coverage = field.w;
@@ -4338,13 +4465,13 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4338
4465
  */
4339
4466
  var toneAdapt = 0.0;
4340
4467
  if (ou.toneAdapt.w > 0.0) {
4341
- let toneX = ou.toneColour.w + ou.toneAdapt.z * sizeK;
4468
+ let toneX = toneColour.w + ou.toneAdapt.z * sizeK;
4342
4469
  let toneT = clamp(
4343
4470
  (toneX - ou.toneAdapt.x) / max(ou.toneAdapt.y - ou.toneAdapt.x, 1e-6),
4344
4471
  0.0,
4345
4472
  1.0,
4346
4473
  );
4347
- toneAdapt = clamp(ou.toneAdapt.w, 0.0, 1.0) * (1.0 - toneT * toneT * (3.0 - 2.0 * toneT));
4474
+ toneAdapt = clamp(toneStrength, 0.0, 1.0) * (1.0 - toneT * toneT * (3.0 - 2.0 * toneT));
4348
4475
  }
4349
4476
  /*
4350
4477
  * Both the colour and the alpha move, together and not separately. What the
@@ -4384,9 +4511,9 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4384
4511
  */
4385
4512
  var solvedNeutral = neutral;
4386
4513
  var solvedAlpha = sizedAlpha;
4387
- if (ou.toneAdapt.w > 0.0 && ou.toneRowThin.w > 0.0 &&
4514
+ if (toneStrength > 0.0 && ou.toneRowThin.w > 0.0 &&
4388
4515
  sizedAlpha > 1e-3 && toneAdapt < 0.995) {
4389
- let encodedInput = srgb_encode(ou.toneColour.w);
4516
+ let encodedInput = srgb_encode(toneColour.w);
4390
4517
  let anchor = max(ou.toneAnchor.x, 1e-4);
4391
4518
  let authority =
4392
4519
  smoothstep(anchor * 0.5, anchor, encodedInput) * clamp(ou.toneRowThin.w, 0.0, 1.0);
@@ -4394,10 +4521,10 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4394
4521
  let response = tone_response(encodedInput, sizeK, toneLevelFar);
4395
4522
  // The collapse's mean pull is toward L(toneColour.rgb) \u2014 the LINEAR
4396
4523
  // mean, which toneAnchor.w carries \u2014 not toward the encoded level.
4397
- let preCollapse = (response - toneAdapt * ou.toneAnchor.w) / (1.0 - toneAdapt);
4524
+ let preCollapse = (response - toneAdapt * toneLinearMean) / (1.0 - toneAdapt);
4398
4525
  let neutralLuma = dot(neutral, vec3f(0.2126, 0.7152, 0.0722));
4399
- let nominal = (1.0 - sizedAlpha) * ou.toneAnchor.w + sizedAlpha * neutralLuma;
4400
- let shift = (preCollapse - nominal) / sizedAlpha * authority * ou.toneAdapt.w;
4526
+ let nominal = (1.0 - sizedAlpha) * toneLinearMean + sizedAlpha * neutralLuma;
4527
+ let shift = (preCollapse - nominal) / sizedAlpha * authority * toneStrength;
4401
4528
  solvedNeutral = clamp(neutral + vec3f(shift), vec3f(0.0), vec3f(1.0));
4402
4529
  /*
4403
4530
  * The light attractor needs OPACITY. The light scheme's neutral is
@@ -4410,14 +4537,14 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4410
4537
  * collapse's own axis with its own fitted constants.
4411
4538
  */
4412
4539
  let solvedLuma = dot(solvedNeutral, vec3f(0.2126, 0.7152, 0.0722));
4413
- let achieved = (1.0 - sizedAlpha) * ou.toneAnchor.w + sizedAlpha * solvedLuma;
4414
- if (preCollapse > achieved + 1e-4 && solvedLuma > ou.toneAnchor.w + 1e-3) {
4540
+ let achieved = (1.0 - sizedAlpha) * toneLinearMean + sizedAlpha * solvedLuma;
4541
+ if (preCollapse > achieved + 1e-4 && solvedLuma > toneLinearMean + 1e-3) {
4415
4542
  let alphaTarget = clamp(
4416
- (preCollapse - ou.toneAnchor.w) / (solvedLuma - ou.toneAnchor.w),
4543
+ (preCollapse - toneLinearMean) / (solvedLuma - toneLinearMean),
4417
4544
  sizedAlpha,
4418
4545
  1.0,
4419
4546
  );
4420
- solvedAlpha = mix(sizedAlpha, alphaTarget, authority * ou.toneAdapt.w);
4547
+ solvedAlpha = mix(sizedAlpha, alphaTarget, authority * toneStrength);
4421
4548
  }
4422
4549
  }
4423
4550
  }
@@ -4450,7 +4577,7 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4450
4577
  * lerped toward it would be a black surface rather than a transmitting one.
4451
4578
  * There the CSS tier's own 'backdrop-filter' is what carries the transmission.
4452
4579
  */
4453
- var toneTarget = ou.toneColour.rgb;
4580
+ var toneTarget = toneColour.rgb;
4454
4581
  if (ou.flags.x > 0.5 || domMaterial) {
4455
4582
  toneTarget = mix(toneTarget, backdrop, clamp(ou.toneRowThick.w, 0.0, 1.0));
4456
4583
  }
@@ -4608,7 +4735,7 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4608
4735
  // the composite; as a layer it is the layer over the tone the host measured
4609
4736
  // for the group \u2014 zero where nothing was measured, the same reference-level
4610
4737
  // convention the CSS tier's 'materialLuminance' takes (within its 0.02).
4611
- let u = bodyAlpha * dot(colour, vec3f(0.2126, 0.7152, 0.0722)) + (1.0 - bodyAlpha) * ou.toneColour.w;
4738
+ let u = bodyAlpha * dot(colour, vec3f(0.2126, 0.7152, 0.0722)) + (1.0 - bodyAlpha) * toneColour.w;
4612
4739
  let grip = clamp(ou.seed.w, 0.0, 1.0) * clamp(ou.tone.z, 0.0, 1.0) *
4613
4740
  (1.0 - toneAdapt * (1.0 - clamp(ou.rim.w, 0.0, 1.0)));
4614
4741
  let shade = mix(1.0, clamp(mix(ou.tone.x, ou.tone.y, clamp(u, 0.0, 1.0)), 0.0, 1.0), grip);
@@ -4725,7 +4852,7 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4725
4852
  * author tint layer composites with sixty lines above.
4726
4853
  */
4727
4854
  let rimLuma = materialAlpha * dot(materialColour, vec3f(0.2126, 0.7152, 0.0722))
4728
- + (1.0 - materialAlpha) * ou.toneColour.w;
4855
+ + (1.0 - materialAlpha) * toneColour.w;
4729
4856
  let rimAmplitude = ou.rim.y + ou.rimLaw.x * rimLuma;
4730
4857
  let rimCollapsed = mix(ou.rimLaw.y, ou.rimLaw.z, tintK);
4731
4858
  /*
@@ -4899,6 +5026,112 @@ fn vs_fullscreen(@builtin(vertex_index) index : u32) -> FullscreenOut {
4899
5026
  }
4900
5027
  `;
4901
5028
 
5029
+ // ../renderer-webgpu/dist/wgsl/silhouette-tone.js
5030
+ var silhouetteReductionModule = () => `${WGSL_PRELUDE}
5031
+ struct ToneShape { rect : vec4f, radius : vec4f };
5032
+ struct ToneStat { colour : vec4f, stats : vec4f };
5033
+ struct ToneUniforms { viewport : vec4f, fit : vec4f };
5034
+ @group(0) @binding(0) var<uniform> u : ToneUniforms;
5035
+ @group(0) @binding(1) var source : texture_2d<f32>;
5036
+ @group(0) @binding(3) var<storage, read> shapes : array<ToneShape>;
5037
+ @group(0) @binding(4) var<storage, read_write> tones : array<ToneStat>;
5038
+ var<workgroup> sums : array<vec4f, 256>;
5039
+ var<workgroup> counts : array<f32, 256>;
5040
+ var<workgroup> weights : array<f32, 256>;
5041
+ struct RawTone { value : vec4f, alpha : f32 };
5042
+
5043
+ fn raw_tone(texel : vec2i) -> RawTone {
5044
+ let dims = vec2i(textureDimensions(source));
5045
+ let rgba = textureLoad(source, clamp(texel, vec2i(0), dims - 1), 0);
5046
+ let rgb = rgba.rgb / max(rgba.a, 1e-6);
5047
+ return RawTone(vec4f(rgb, dot(linear_to_srgb(rgb), vec3f(0.2126, 0.7152, 0.0722))) * rgba.a, rgba.a);
5048
+ }
5049
+
5050
+ @compute @workgroup_size(256)
5051
+ fn reduce_tone(@builtin(local_invocation_index) lane : u32,
5052
+ @builtin(workgroup_id) group : vec3u) {
5053
+ let shape = shapes[group.x];
5054
+ // G0 clips to the capture raster. Clip BEFORE deriving the iteration area so
5055
+ // offscreen geometry cannot create unbounded work or overflow its u32 product.
5056
+ // In-bounds rectangles retain exactly the same lane assignment and arithmetic.
5057
+ let lo = vec2i(clamp(floor(shape.rect.xy), vec2f(0.0), u.viewport.xy));
5058
+ let hi = vec2i(clamp(ceil(shape.rect.xy + shape.rect.zw), vec2f(0.0), u.viewport.xy));
5059
+ let extent = vec2u(max(hi - lo, vec2i(0)));
5060
+ let half = shape.rect.zw * 0.5;
5061
+ let centre = shape.rect.xy + half;
5062
+ let radius = min(shape.radius.x, min(half.x, half.y));
5063
+ var sum = vec4f(0.0);
5064
+ var count = 0.0;
5065
+ var weight = 0.0;
5066
+ for (var i = lane; i < extent.x * extent.y; i += 256u) {
5067
+ let pixel = lo + vec2i(vec2u(i % extent.x, i / extent.x));
5068
+ let p = vec2f(pixel) + 0.5;
5069
+ let q = abs(p - centre) - half + radius;
5070
+ let distance = length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - radius;
5071
+ if (distance > 0.0) { continue; }
5072
+ let uv = (p / u.viewport.xy) * u.fit.xy + u.fit.zw;
5073
+ // Interpolate encoded and linear statistics separately. Encoding after
5074
+ // bilinear filtering would reintroduce a Jensen gap on a scaled source.
5075
+ let at = uv * vec2f(textureDimensions(source)) - 0.5;
5076
+ let base = vec2i(floor(at));
5077
+ let f = fract(at);
5078
+ let a = raw_tone(base);
5079
+ let b = raw_tone(base + vec2i(1, 0));
5080
+ let c = raw_tone(base + vec2i(0, 1));
5081
+ let d = raw_tone(base + vec2i(1, 1));
5082
+ sum += mix(mix(a.value, b.value, f.x), mix(c.value, d.value, f.x), f.y);
5083
+ weight += mix(mix(a.alpha, b.alpha, f.x), mix(c.alpha, d.alpha, f.x), f.y);
5084
+ count += 1.0;
5085
+ }
5086
+ sums[lane] = sum;
5087
+ counts[lane] = count;
5088
+ weights[lane] = weight;
5089
+ workgroupBarrier();
5090
+ for (var stride = 128u; stride > 0u; stride /= 2u) {
5091
+ if (lane < stride) {
5092
+ sums[lane] += sums[lane + stride];
5093
+ counts[lane] += counts[lane + stride];
5094
+ weights[lane] += weights[lane + stride];
5095
+ }
5096
+ workgroupBarrier();
5097
+ }
5098
+ if (lane == 0u) {
5099
+ let mean = sums[0] / max(weights[0], 1e-6);
5100
+ let level = srgb_to_linear(vec3f(mean.w)).x;
5101
+ tones[group.x].colour = vec4f(mean.rgb, level);
5102
+ tones[group.x].stats = vec4f(mean.w, luminance(mean.rgb), counts[0], weights[0]);
5103
+ }
5104
+ }
5105
+ `;
5106
+ var silhouetteFieldModule = (family) => `${WGSL_PRELUDE}
5107
+ ${WGSL_FIELD_KERNELS}
5108
+ struct FieldUniforms { screen : vec4f, unionP : vec4f, counts : vec4u, fallback : vec4f };
5109
+ struct ToneStat { colour : vec4f, stats : vec4f };
5110
+ @group(0) @binding(0) var<uniform> u : FieldUniforms;
5111
+ @group(0) @binding(1) var<storage, read> instances : array<Instance>;
5112
+ @group(0) @binding(2) var<storage, read> tones : array<ToneStat>;
5113
+ fn member_distance(i : u32, p : vec2f) -> f32 {
5114
+ let s = instances[i];
5115
+ return ${family === "rsupn" ? "sd_rsupn_grad" : "sd_rsup_grad"}(
5116
+ p - s.centre, s.half, s.re, vec4f(s.k0, s.k1, s.k2, s.k3), s.k4).d + s.inset;
5117
+ }
5118
+ fn member_tone(i : u32) -> vec4f {
5119
+ return select(u.fallback, tones[i].colour, tones[i].stats.w > 0.0);
5120
+ }
5121
+ @fragment
5122
+ fn fs_tone(in : FullscreenOut) -> @location(0) vec4f {
5123
+ let p = in.uv * u.screen.xy * u.screen.z;
5124
+ var distance = member_distance(0u, p);
5125
+ var colour = member_tone(0u);
5126
+ for (var i = 1u; i < u.counts.x; i += 1u) {
5127
+ let blend = union_blend(distance, member_distance(i, p), u.unionP.xyz);
5128
+ distance = blend.x;
5129
+ colour = mix(member_tone(i), colour, blend.y);
5130
+ }
5131
+ return colour;
5132
+ }
5133
+ `;
5134
+
4902
5135
  // ../renderer-webgpu/dist/wgsl/index.js
4903
5136
  var withPrelude = (pass) => `${WGSL_PRELUDE}
4904
5137
  ${pass}
@@ -4919,6 +5152,9 @@ function allShaderSource() {
4919
5152
  importPassSource("external"),
4920
5153
  WGSL_DOWNSAMPLE_PASS,
4921
5154
  WGSL_ANALYSIS_PASS,
5155
+ silhouetteReductionModule(),
5156
+ silhouetteFieldModule("rsupn"),
5157
+ silhouetteFieldModule("rsup"),
4922
5158
  WGSL_OPTICS_PASS,
4923
5159
  WGSL_HIGHLIGHT_PASS
4924
5160
  ].join("\n\n");
@@ -5123,7 +5359,7 @@ function createPassRunner(context) {
5123
5359
  };
5124
5360
  },
5125
5361
  opticsPass(encoder, args) {
5126
- const slot = uniformSlot(`optics:${args.resourceId}`, 112);
5362
+ const slot = uniformSlot(`optics:${args.resourceId}`, 120);
5127
5363
  const d = slot.data;
5128
5364
  d[0] = args.viewportDevice[0];
5129
5365
  d[1] = args.viewportDevice[1];
@@ -5237,6 +5473,11 @@ function createPassRunner(context) {
5237
5473
  d[109] = args.domMaterial?.mode ?? 0;
5238
5474
  d[110] = args.domMaterial?.referenceBackdropLuminance ?? 0;
5239
5475
  d[111] = args.domMaterial?.minimumTintContrast ?? 0;
5476
+ d[112] = args.backdropToneAnchorX[3] ?? args.backdropToneAnchorX[2];
5477
+ d[113] = args.backdropToneResponseThin[3] ?? args.backdropToneResponseThin[2];
5478
+ d[114] = args.backdropToneResponseThick[3] ?? args.backdropToneResponseThick[2];
5479
+ d[115] = args.backdropToneAnchorX.length === 4 ? 1 : 0;
5480
+ d[116] = args.localTone === void 0 ? 0 : 1;
5240
5481
  slot.write();
5241
5482
  const chain = args.backdrop?.chain ?? placeholderView;
5242
5483
  const body = args.backdrop?.body ?? placeholderView;
@@ -5263,7 +5504,8 @@ function createPassRunner(context) {
5263
5504
  { binding: 6, resource: context.flatSampler },
5264
5505
  { binding: 7, resource: args.fields.aux2.createView() },
5265
5506
  { binding: 8, resource: heavy },
5266
- { binding: 9, resource: args.fields.presence.createView() }
5507
+ { binding: 9, resource: args.fields.presence.createView() },
5508
+ { binding: 10, resource: args.localTone ?? placeholderView }
5267
5509
  ]
5268
5510
  }));
5269
5511
  pass.draw(3);
@@ -5291,7 +5533,7 @@ function createPassRunner(context) {
5291
5533
  d[16] = args.fields.width;
5292
5534
  d[17] = args.fields.height;
5293
5535
  d[18] = args.fields.upsampled ? 1 : 0;
5294
- d[19] = 0;
5536
+ d[19] = args.localTone === void 0 ? 0 : 1;
5295
5537
  d[20] = args.backdropTone[0];
5296
5538
  d[21] = Math.max(args.backdropTone[1], args.backdropTone[0] + 1e-4);
5297
5539
  d[22] = args.backdropTone[2];
@@ -5321,7 +5563,8 @@ function createPassRunner(context) {
5321
5563
  { binding: 1, resource: args.fields.field.createView() },
5322
5564
  { binding: 2, resource: args.fields.aux.createView() },
5323
5565
  { binding: 3, resource: context.flatSampler },
5324
- { binding: 4, resource: args.fields.presence.createView() }
5566
+ { binding: 4, resource: args.fields.presence.createView() },
5567
+ { binding: 5, resource: args.localTone ?? placeholderView }
5325
5568
  ]
5326
5569
  }));
5327
5570
  pass.draw(3);
@@ -5977,6 +6220,263 @@ function createPyramidStore(context) {
5977
6220
  }
5978
6221
  var ANALYSIS_DISPATCH = { workgroupSize: ANALYSIS_WORKGROUP, workgroups: 1 };
5979
6222
 
6223
+ // ../renderer-webgpu/dist/silhouette-tone.js
6224
+ function packToneShapes(surfaces, dpr) {
6225
+ const data = new Float32Array(surfaces.length * 8);
6226
+ surfaces.forEach((surface, i) => {
6227
+ const [w, h] = surface.shape.channels.size;
6228
+ data.set([
6229
+ (surface.centre[0] - w / 2) * dpr,
6230
+ (surface.centre[1] - h / 2) * dpr,
6231
+ w * dpr,
6232
+ h * dpr,
6233
+ surface.shape.corner.radius * dpr,
6234
+ 0,
6235
+ 0,
6236
+ 0
6237
+ ], i * 8);
6238
+ });
6239
+ return data;
6240
+ }
6241
+ function toneReadingsFromBuffer(values, metadata, fallback) {
6242
+ return metadata.flatMap((meta, i) => {
6243
+ if (values[i * 8 + 7] > 0)
6244
+ return [{
6245
+ ...meta,
6246
+ kind: "silhouette",
6247
+ level: 0,
6248
+ color: [values[i * 8], values[i * 8 + 1], values[i * 8 + 2]],
6249
+ luminance: values[i * 8 + 3],
6250
+ encodedLuminance: values[i * 8 + 4],
6251
+ linearLuminance: values[i * 8 + 5],
6252
+ sampleCount: values[i * 8 + 6]
6253
+ }];
6254
+ if (fallback === void 0 || fallback[3] < 0)
6255
+ return [];
6256
+ const [r, g, b, luminance] = fallback;
6257
+ return [{
6258
+ surfaceId: meta.surfaceId,
6259
+ kind: "source",
6260
+ color: [r, g, b],
6261
+ luminance,
6262
+ encodedLuminance: linearToSrgbChannel(luminance),
6263
+ // The local optics field derives this channel from RGB, including fallback.
6264
+ linearLuminance: relativeLuminance([r, g, b]),
6265
+ sampleCount: 0,
6266
+ level: 0,
6267
+ sourceWidth: 0,
6268
+ sourceHeight: 0,
6269
+ sampledWidth: 0,
6270
+ sampledHeight: 0
6271
+ }];
6272
+ });
6273
+ }
6274
+ function createSilhouetteTonePass(context) {
6275
+ const { device, cache, pool } = context;
6276
+ const createEntry = (count, groupId) => ({
6277
+ groupId,
6278
+ count,
6279
+ shapes: createStorageSlot(device, count * 32, "vitrea:tone-shapes"),
6280
+ instances: createStorageSlot(device, count * 72, "vitrea:tone-instances"),
6281
+ uniform: createUniformSlot(device, 8, "vitrea:tone-uniform"),
6282
+ fieldUniform: createUniformSlot(device, 16, "vitrea:tone-field-uniform"),
6283
+ tones: device.createBuffer({
6284
+ size: count * 32,
6285
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
6286
+ }),
6287
+ readback: device.createBuffer({
6288
+ size: count * 32,
6289
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
6290
+ }),
6291
+ reduced: void 0,
6292
+ encodedReduction: false,
6293
+ observation: void 0,
6294
+ fallback: void 0,
6295
+ encodedFallback: void 0,
6296
+ queued: void 0,
6297
+ pending: void 0
6298
+ });
6299
+ const entries = /* @__PURE__ */ new Map();
6300
+ const destroyEntry = (entry) => {
6301
+ entry.shapes.destroy();
6302
+ entry.instances.destroy();
6303
+ entry.uniform.buffer.destroy();
6304
+ entry.fieldUniform.buffer.destroy();
6305
+ entry.tones.destroy();
6306
+ entry.readback.destroy();
6307
+ };
6308
+ const forget = (resourceId) => {
6309
+ const entry = entries.get(resourceId);
6310
+ if (entry !== void 0)
6311
+ destroyEntry(entry);
6312
+ entries.delete(resourceId);
6313
+ pool.release(`tone-field:${resourceId}`);
6314
+ };
6315
+ return {
6316
+ draw(encoder, args) {
6317
+ const { field, pyramid, fields } = args;
6318
+ let entry = entries.get(field.resourceId);
6319
+ if (entry !== void 0 && entry.count !== field.instanceCount) {
6320
+ forget(field.resourceId);
6321
+ entry = void 0;
6322
+ }
6323
+ if (entry === void 0) {
6324
+ entry = createEntry(field.instanceCount, args.groupId);
6325
+ entries.set(field.resourceId, entry);
6326
+ }
6327
+ const shapeData = packToneShapes(args.surfaces, 1 / field.cssPerDevice);
6328
+ const geometryKey = JSON.stringify([
6329
+ pyramid.sourceId,
6330
+ pyramid.sizeEpoch,
6331
+ pyramid.sourceWidth,
6332
+ pyramid.sourceHeight,
6333
+ pyramid.plan.width,
6334
+ pyramid.plan.height,
6335
+ ...shapeData,
6336
+ ...args.viewportDevice,
6337
+ ...args.fit,
6338
+ ...args.surfaces.map((surface) => surface.nodeId)
6339
+ ]);
6340
+ const previous = entry.reduced;
6341
+ if (previous === void 0 || previous.chain !== pyramid.chain || previous.geometryKey !== geometryKey || previous.builtEpoch !== pyramid.builtEpoch && (!args.liveSource || readbackDue(previous.timeMs, args.timeMs, args.cadenceHz))) {
6342
+ entry.reduced = {
6343
+ chain: pyramid.chain,
6344
+ geometryKey,
6345
+ builtEpoch: pyramid.builtEpoch,
6346
+ timeMs: args.timeMs,
6347
+ metadata: args.surfaces.map((surface) => ({
6348
+ surfaceId: surface.nodeId,
6349
+ sourceWidth: pyramid.sourceWidth,
6350
+ sourceHeight: pyramid.sourceHeight,
6351
+ sampledWidth: pyramid.plan.width,
6352
+ sampledHeight: pyramid.plan.height
6353
+ }))
6354
+ };
6355
+ entry.encodedReduction = true;
6356
+ entry.shapes.write(shapeData, shapeData.length);
6357
+ entry.uniform.data.set([...args.viewportDevice, 0, 0, ...args.fit]);
6358
+ entry.uniform.write();
6359
+ const pipeline = cache.computePipeline("silhouette-reduction", () => ({
6360
+ layout: "auto",
6361
+ compute: {
6362
+ module: cache.module("silhouette-reduction", silhouetteReductionModule),
6363
+ entryPoint: "reduce_tone"
6364
+ }
6365
+ }));
6366
+ const pass = encoder.beginComputePass({ label: "vitrea:silhouette-reduction" });
6367
+ pass.setPipeline(pipeline);
6368
+ pass.setBindGroup(0, device.createBindGroup({
6369
+ layout: pipeline.getBindGroupLayout(0),
6370
+ entries: [
6371
+ { binding: 0, resource: { buffer: entry.uniform.buffer } },
6372
+ { binding: 1, resource: pyramid.chain.createView({ baseMipLevel: 0, mipLevelCount: 1 }) },
6373
+ { binding: 3, resource: { buffer: entry.shapes.buffer } },
6374
+ { binding: 4, resource: { buffer: entry.tones } }
6375
+ ]
6376
+ }));
6377
+ pass.dispatchWorkgroups(field.instanceCount);
6378
+ pass.end();
6379
+ }
6380
+ entry.instances.write(field.instances, field.instanceCount * 18);
6381
+ entry.encodedFallback = args.fallbackTone;
6382
+ if (entry.queued === void 0 && entry.pending === void 0 && entry.observation?.reduction !== entry.reduced) {
6383
+ encoder.copyBufferToBuffer(entry.tones, 0, entry.readback, 0, entry.count * 32);
6384
+ entry.queued = entry.reduced;
6385
+ }
6386
+ const texture = pool.acquire(`tone-field:${field.resourceId}`, {
6387
+ width: fields.width,
6388
+ height: fields.height,
6389
+ format: "rgba16float",
6390
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
6391
+ });
6392
+ const u = entry.fieldUniform;
6393
+ u.data.set([
6394
+ field.rectDevice.width,
6395
+ field.rectDevice.height,
6396
+ field.cssPerDevice,
6397
+ 0,
6398
+ field.union.neckWidth,
6399
+ field.union.maxBulge,
6400
+ field.union.separationThreshold,
6401
+ 0
6402
+ ]);
6403
+ new Uint32Array(u.data.buffer)[8] = field.instanceCount;
6404
+ u.data.set(args.fallbackTone, 12);
6405
+ u.write();
6406
+ const fieldPipeline = cache.renderPipeline(`silhouette-field:${field.family}`, () => ({
6407
+ layout: "auto",
6408
+ vertex: { module: cache.module(`silhouette-field:${field.family}`, () => silhouetteFieldModule(field.family)), entryPoint: "vs_fullscreen" },
6409
+ fragment: {
6410
+ module: cache.module(`silhouette-field:${field.family}`, () => silhouetteFieldModule(field.family)),
6411
+ entryPoint: "fs_tone",
6412
+ targets: [{ format: "rgba16float" }]
6413
+ },
6414
+ primitive: { topology: "triangle-list" }
6415
+ }));
6416
+ const view = texture.createView();
6417
+ const fieldPass = encoder.beginRenderPass({
6418
+ label: "vitrea:silhouette-field",
6419
+ colorAttachments: [{ view, loadOp: "clear", storeOp: "store" }]
6420
+ });
6421
+ fieldPass.setPipeline(fieldPipeline);
6422
+ fieldPass.setBindGroup(0, device.createBindGroup({
6423
+ layout: fieldPipeline.getBindGroupLayout(0),
6424
+ entries: [
6425
+ { binding: 0, resource: { buffer: u.buffer } },
6426
+ { binding: 1, resource: { buffer: entry.instances.buffer } },
6427
+ { binding: 2, resource: { buffer: entry.tones } }
6428
+ ]
6429
+ }));
6430
+ fieldPass.draw(3);
6431
+ fieldPass.end();
6432
+ return view;
6433
+ },
6434
+ afterSubmit() {
6435
+ for (const entry of entries.values()) {
6436
+ entry.encodedReduction = false;
6437
+ if (entry.encodedFallback !== void 0)
6438
+ entry.fallback = entry.encodedFallback;
6439
+ entry.encodedFallback = void 0;
6440
+ const reduction = entry.queued;
6441
+ if (reduction === void 0)
6442
+ continue;
6443
+ entry.queued = void 0;
6444
+ entry.pending = entry.readback.mapAsync(GPUMapMode.READ).then(() => {
6445
+ entry.observation = {
6446
+ reduction,
6447
+ values: new Float32Array(entry.readback.getMappedRange()).slice()
6448
+ };
6449
+ entry.readback.unmap();
6450
+ }).catch(() => {
6451
+ entry.observation = void 0;
6452
+ }).finally(() => {
6453
+ entry.pending = void 0;
6454
+ });
6455
+ }
6456
+ },
6457
+ cancelQueued() {
6458
+ for (const entry of entries.values()) {
6459
+ if (entry.encodedReduction)
6460
+ entry.reduced = void 0;
6461
+ entry.encodedReduction = false;
6462
+ entry.encodedFallback = void 0;
6463
+ entry.queued = void 0;
6464
+ }
6465
+ },
6466
+ async collect() {
6467
+ await Promise.all([...entries.values()].map((entry) => entry.pending));
6468
+ },
6469
+ readings(groupId) {
6470
+ return [...entries.values()].filter((entry) => entry.groupId === groupId).flatMap((entry) => entry.observation === void 0 || entry.observation.reduction !== entry.reduced ? [] : toneReadingsFromBuffer(entry.observation.values, entry.observation.reduction.metadata, entry.fallback));
6471
+ },
6472
+ forget,
6473
+ destroy() {
6474
+ for (const resourceId of entries.keys())
6475
+ forget(resourceId);
6476
+ }
6477
+ };
6478
+ }
6479
+
5980
6480
  // ../renderer-webgpu/dist/renderer.js
5981
6481
  var OPTICS_PASS_ID = "vitrea.optics";
5982
6482
  var HIGHLIGHT_PASS_ID = "vitrea.highlight";
@@ -5998,6 +6498,7 @@ function createWebGPURenderer(options = {}) {
5998
6498
  const groups = /* @__PURE__ */ new Map();
5999
6499
  const adaptation = /* @__PURE__ */ new Map();
6000
6500
  const lastReadbackAt = /* @__PURE__ */ new Map();
6501
+ const hintedTones = /* @__PURE__ */ new Map();
6001
6502
  let accessibility = NOMINAL_MATERIAL_POLICY;
6002
6503
  let material = withMaterialOverrides(DEFAULT_MATERIAL_PROFILE, options.materialProfile ?? {});
6003
6504
  let viewport = options.viewport ?? {
@@ -6009,6 +6510,7 @@ function createWebGPURenderer(options = {}) {
6009
6510
  let builtGeneration;
6010
6511
  let store;
6011
6512
  let runner;
6513
+ let silhouetteTone;
6012
6514
  let framesDrawn = 0;
6013
6515
  let generations = 0;
6014
6516
  let lastFrameTimeMs;
@@ -6032,6 +6534,9 @@ function createWebGPURenderer(options = {}) {
6032
6534
  });
6033
6535
  const dropContext = () => {
6034
6536
  store?.destroy();
6537
+ silhouetteTone?.destroy();
6538
+ silhouetteTone = void 0;
6539
+ hintedTones.clear();
6035
6540
  runner?.destroy();
6036
6541
  context?.destroy();
6037
6542
  store = void 0;
@@ -6063,6 +6568,7 @@ function createWebGPURenderer(options = {}) {
6063
6568
  builtGeneration = generation;
6064
6569
  store = createPyramidStore(context);
6065
6570
  runner = createPassRunner(context);
6571
+ silhouetteTone = createSilhouetteTonePass(context);
6066
6572
  if (superseded !== void 0 && superseded !== generation) {
6067
6573
  for (const provider of providers.values()) {
6068
6574
  if (provider.generation !== generation)
@@ -6223,7 +6729,7 @@ function createWebGPURenderer(options = {}) {
6223
6729
  }
6224
6730
  return { built, unbuilt };
6225
6731
  }
6226
- function drawGroups(encoder, resolution) {
6732
+ function drawGroups(encoder, resolution, timeMs) {
6227
6733
  const { store: pyramids, runner: passes } = ensureContext();
6228
6734
  const active = targets;
6229
6735
  if (active === void 0) {
@@ -6239,7 +6745,11 @@ function createWebGPURenderer(options = {}) {
6239
6745
  let drawn = 0;
6240
6746
  planesDrawn.add(active.plane);
6241
6747
  const resourceOf = (groupId) => groupResourceId(active.plane, groupId);
6242
- const releaseIdle = (groupId) => passes.forget(resourceOf(groupId));
6748
+ const releaseIdle = (groupId) => {
6749
+ passes.forget(resourceOf(groupId));
6750
+ silhouetteTone?.forget(resourceOf(groupId));
6751
+ hintedTones.delete(resourceOf(groupId));
6752
+ };
6243
6753
  for (const entry of groups.values()) {
6244
6754
  const input = entry.input;
6245
6755
  if (input.surfaces.length === 0) {
@@ -6277,7 +6787,7 @@ function createWebGPURenderer(options = {}) {
6277
6787
  rectDevice.x * cssPerDevice,
6278
6788
  rectDevice.y * cssPerDevice
6279
6789
  ]);
6280
- const fields = passes.fieldPass(encoder, {
6790
+ const fieldArgs = {
6281
6791
  resourceId: resourceOf(input.groupId),
6282
6792
  family: governor.knobs.fieldFamily,
6283
6793
  rectDevice,
@@ -6289,19 +6799,62 @@ function createWebGPURenderer(options = {}) {
6289
6799
  instances: packed.data,
6290
6800
  instanceCount: packed.count,
6291
6801
  union
6292
- });
6802
+ };
6803
+ const fields = passes.fieldPass(encoder, fieldArgs);
6293
6804
  const state = stateOf(input, resolution);
6294
6805
  const variant = variantOf(input);
6295
6806
  const sourceId = input.backdropSourceId;
6296
6807
  const pyramid = sourceId === void 0 ? void 0 : pyramids.resources(sourceId);
6297
6808
  const adapt = sourceId === void 0 ? void 0 : adaptationFor(sourceId).values;
6809
+ const localTone = typeof material.backdropToneAbscissa === "object" && input.backdropToneHint !== true && pyramid !== void 0 && sourceId !== void 0 && surfaces.length > 0 ? silhouetteTone?.draw(encoder, {
6810
+ groupId: input.groupId,
6811
+ field: fieldArgs,
6812
+ fields,
6813
+ surfaces,
6814
+ pyramid,
6815
+ viewportDevice,
6816
+ fit: fitFor(sourceId, pyramid),
6817
+ timeMs,
6818
+ cadenceHz: governor.knobs.adaptationCadenceHz,
6819
+ liveSource: providers.get(sourceId)?.kind !== "image" && providers.get(sourceId)?.kind !== "gradient",
6820
+ fallbackTone: [
6821
+ input.backdropTone?.[0] ?? 0,
6822
+ input.backdropTone?.[1] ?? 0,
6823
+ input.backdropTone?.[2] ?? 0,
6824
+ input.backdropTone === void 0 ? -1 : input.backdropToneLevel ?? relativeLuminance(input.backdropTone)
6825
+ ]
6826
+ }) : void 0;
6827
+ if (localTone === void 0)
6828
+ silhouetteTone?.forget(resourceOf(input.groupId));
6829
+ const hintLevel = input.backdropToneLevel ?? (input.backdropTone === void 0 ? void 0 : relativeLuminance(input.backdropTone));
6830
+ if (typeof material.backdropToneAbscissa === "object" && input.backdropToneHint === true && hintLevel !== void 0 && input.backdropTone !== void 0) {
6831
+ const color = input.backdropTone;
6832
+ hintedTones.set(resourceOf(input.groupId), {
6833
+ groupId: input.groupId,
6834
+ readings: surfaces.map((surface) => ({
6835
+ surfaceId: surface.nodeId,
6836
+ kind: "hint",
6837
+ color,
6838
+ encodedLuminance: linearToSrgbChannel(hintLevel),
6839
+ luminance: hintLevel,
6840
+ linearLuminance: input.backdropToneLinearLuminance ?? hintLevel,
6841
+ sampleCount: 0,
6842
+ level: 0,
6843
+ sourceWidth: 0,
6844
+ sourceHeight: 0,
6845
+ sampledWidth: 0,
6846
+ sampledHeight: 0
6847
+ }))
6848
+ });
6849
+ } else
6850
+ hintedTones.delete(resourceOf(input.groupId));
6298
6851
  const nominalOptics = material.optics[variant];
6299
6852
  const optics = opticsUnderPolicy(nominalOptics, policy, material);
6300
6853
  const backdropTone = [
6301
6854
  material.backdropToneLow,
6302
6855
  material.backdropToneHigh,
6303
6856
  backdropToneSizeBiasUnderPolicy(policy, material),
6304
- input.backdropTone === void 0 ? 0 : backdropToneUnderPolicy(policy, material) * material.backdropToneMax
6857
+ input.backdropTone === void 0 && localTone === void 0 ? 0 : backdropToneUnderPolicy(policy, material) * material.backdropToneMax
6305
6858
  ];
6306
6859
  const backdropToneLevel = shadowBackdropLuminance ?? 0;
6307
6860
  const refraction = effectiveRefraction(accessibilityRefractionCap(policy), state.refraction);
@@ -6312,6 +6865,7 @@ function createWebGPURenderer(options = {}) {
6312
6865
  targetFormat: active.format,
6313
6866
  rectDevice,
6314
6867
  fields,
6868
+ ...localTone === void 0 ? {} : { localTone },
6315
6869
  viewportDevice,
6316
6870
  cssPerDevice,
6317
6871
  coverageRampCss: cssPerDevice,
@@ -6535,6 +7089,7 @@ function createWebGPURenderer(options = {}) {
6535
7089
  rectDevice: surfaceRectDevice,
6536
7090
  fieldRectDevice: rectDevice,
6537
7091
  fields,
7092
+ ...localTone === void 0 ? {} : { localTone },
6538
7093
  viewportDevice,
6539
7094
  cssPerDevice,
6540
7095
  sweep: lead.channels.sweep,
@@ -6675,8 +7230,11 @@ function createWebGPURenderer(options = {}) {
6675
7230
  },
6676
7231
  removeGroup(groupId) {
6677
7232
  groups.delete(groupId);
6678
- for (const plane of planesDrawn)
7233
+ for (const plane of planesDrawn) {
6679
7234
  runner?.forget(groupResourceId(plane, groupId));
7235
+ silhouetteTone?.forget(groupResourceId(plane, groupId));
7236
+ hintedTones.delete(groupResourceId(plane, groupId));
7237
+ }
6680
7238
  },
6681
7239
  setAccessibility(policy) {
6682
7240
  accessibility = policy;
@@ -6720,12 +7278,14 @@ function createWebGPURenderer(options = {}) {
6720
7278
  if (args.highlight !== void 0)
6721
7279
  passes.clearPass(encoder, args.highlight);
6722
7280
  }
6723
- result = drawGroups(encoder, args.resolution);
7281
+ result = drawGroups(encoder, args.resolution, args.frame.timeMs);
6724
7282
  args.timing?.resolve(encoder);
6725
7283
  gpu.device.queue.submit([encoder.finish()]);
6726
7284
  pyramids.afterSubmit();
7285
+ silhouetteTone?.afterSubmit();
6727
7286
  } finally {
6728
7287
  pyramids.releaseAcquired();
7288
+ silhouetteTone?.cancelQueued();
6729
7289
  }
6730
7290
  const delta = lastFrameTimeMs === void 0 ? 0 : args.frame.timeMs - lastFrameTimeMs;
6731
7291
  lastFrameTimeMs = args.frame.timeMs;
@@ -6758,6 +7318,7 @@ function createWebGPURenderer(options = {}) {
6758
7318
  } catch (error) {
6759
7319
  pendingEncoder = void 0;
6760
7320
  pyramids.releaseAcquired();
7321
+ silhouetteTone?.cancelQueued();
6761
7322
  throw error;
6762
7323
  }
6763
7324
  },
@@ -6773,12 +7334,14 @@ function createWebGPURenderer(options = {}) {
6773
7334
  passes.clearPass(encoder, targets.optics);
6774
7335
  if (targets.highlight !== void 0)
6775
7336
  passes.clearPass(encoder, targets.highlight);
6776
- drawGroups(encoder, frameContext.resolution);
7337
+ drawGroups(encoder, frameContext.resolution, frameContext.frame.timeMs);
6777
7338
  }
6778
7339
  context.device.queue.submit([encoder.finish()]);
6779
7340
  pyramids.afterSubmit();
7341
+ silhouetteTone?.afterSubmit();
6780
7342
  } finally {
6781
7343
  pyramids.releaseAcquired();
7344
+ silhouetteTone?.cancelQueued();
6782
7345
  }
6783
7346
  const delta = lastFrameTimeMs === void 0 ? 0 : frameContext.frame.timeMs - lastFrameTimeMs;
6784
7347
  lastFrameTimeMs = frameContext.frame.timeMs;
@@ -6790,7 +7353,14 @@ function createWebGPURenderer(options = {}) {
6790
7353
  }
6791
7354
  };
6792
7355
  },
7356
+ backdropToneAbscissae(groupId) {
7357
+ return [
7358
+ ...silhouetteTone?.readings(groupId) ?? [],
7359
+ ...[...hintedTones.values()].filter((entry) => entry.groupId === groupId).flatMap((entry) => entry.readings)
7360
+ ];
7361
+ },
6793
7362
  async collectAdaptation() {
7363
+ await silhouetteTone?.collect();
6794
7364
  if (store === void 0)
6795
7365
  return 0;
6796
7366
  const stats = await store.collectStats();
@@ -6819,6 +7389,6 @@ function createWebGPURenderer(options = {}) {
6819
7389
  };
6820
7390
  }
6821
7391
 
6822
- export { ADAPTIVE_LUMINANCE_HIGH, ADAPTIVE_LUMINANCE_LOW, ADAPTIVE_TINT_DARK, ADAPTIVE_TINT_LIGHT, ANALYSIS_DISPATCH, ANALYSIS_GRID, ANALYSIS_PASS_ID, ANALYSIS_STATS_FLOATS, ANALYSIS_TARGET_EXTENT, ANALYSIS_WORKGROUP, BACKDROP_ALPHA_MODES, BACKDROP_COLOR_SPACES, BACKDROP_KINDS, BACKDROP_PASS_ID, BACKDROP_TONE_HIGH, BACKDROP_TONE_LOW, BACKDROP_TONE_MAX, BACKDROP_TONE_SIZE_BIAS, CANVAS_FORMAT, CHAIN_SIGMA_AT_LEVEL_1, CROSS_CHECK_SHAPE_FLOATS, CROSS_CHECK_WORKGROUP, DEFAULT_MATERIAL_PROFILE, FAMILY_C_CROSS_CHECK, FIELD_PASS_ID, GOVERNOR_LADDER, HIGHLIGHT_PASS_ID, IDENTITY_FIT, IDLE_CHANNELS, INCREASED_OCCLUSION_LIFT, INSTANCE_BYTES, INSTANCE_FLOATS, LENS_AMOUNT_MAX, LENS_AMOUNT_PER_SPAN, LENS_EXTENT_GAIN, LENS_HEIGHT_MAX, LENS_HEIGHT_PER_SPAN, LENS_OVALIZATION, LENS_PROFILE_EXPONENT, LENS_REFRACTION_GAIN, LENS_SIZE_GAIN_MAX, LENS_THICKNESS_REFERENCE, LUMINANCE_WEIGHTS, MATERIAL_OPTICS, MATERIAL_VARIANTS, MAX_CHAIN_LEVELS, MIN_LEVEL_EXTENT, NOMINAL_GOVERNOR, NOMINAL_MATERIAL_POLICY, OPTICS_PASS_ID, OUTER_SHADOW, OUTER_SHADOW_THICK_SPANS, OUTER_SHADOW_THIN_L, OUTER_SHADOW_UNMEASURED_BACKDROP_LUMINANCE, OUTPUT_TEXTURE_FORMAT, PASS_LABEL, REFRACTION_LADDER, REFRACTION_SCALE, RENDERER_ERROR_CODES, RENDERER_PASS_IDS, RendererError, SIZE_OCCLUSION_GAIN, SIZE_SCATTER_FLOOR, SIZE_SCATTER_FLOOR_2X, SIZE_SCATTER_GAIN_FAR_2X, SIZE_SCATTER_GAIN_MAX, SIZE_SCATTER_GAIN_MAX_2X, SIZE_SCATTER_RAMP_REACH_1X_PX, SIZE_SCATTER_RAMP_REACH_2X_PX, SIZE_SCATTER_RAMP_START_FAR_1X, SIZE_SCATTER_RAMP_START_FAR_2X, SIZE_SCATTER_RAMP_START_THICK_1X, SIZE_SCATTER_RAMP_START_THICK_2X, SIZE_SCATTER_RAMP_START_THIN_1X, SIZE_SCATTER_RAMP_START_THIN_2X, SIZE_SCATTER_SPAN_MAX, SIZE_SCATTER_SPAN_MAX_2X, SIZE_SHADOW_GAIN_MAX, SIZE_SPAN_MAX, SIZE_SPAN_MIN, SRGB_ENCODING_EXPONENT, SUPPORTED_APP_TEXTURE_FORMATS, WGSL_ANALYSIS_PASS, WGSL_CROSS_CHECK_PASS, WGSL_DOWNSAMPLE_PASS, WGSL_FIELD_KERNELS, WGSL_FIELD_PASS, WGSL_FIELD_SAMPLE, WGSL_HIGHLIGHT_PASS, WGSL_IMPORT_PASS, WGSL_INSTANCE_STRUCT, WGSL_OPTICS_PASS, WGSL_PRELUDE, WGSL_RSUPN_GRAD, WGSL_RSUP_GRAD, WGSL_SMOOTH_UNION, WORKING_TEXTURE_FORMAT, ZERO_STATS, accessibilityRefractionCap, adaptationStrength, adaptedTintAlpha, adaptedTintColour, adaptiveTint, allShaderSource, alphaNormalisationMode, analysisModule, backdropFit, backdropToneAdaptation, backdropToneResponse, backdropToneSizeBiasUnderPolicy, backdropToneSolveWeight, backdropToneUnderPolicy, bodyBlurPlan, chainModule, clipFieldRectToCanvas, collapsedRimUnderPolicy, coverFit, createAdaptationState, createAppTextureProvider, createCopyProvider, createDeviceHost, createGovernor, createGpuContext, createGradientProvider, createPassRunner, createPipelineCache, createPyramidStore, createRebuildLedger, createStorageSlot, createTexturePool, createTimingCollector, createUniformSlot, createVideoProvider, createWebGPURenderer, crossCheckKernelModule, displayP3ToSrgbLinear, effectiveRefraction, encodeOutput, encodeOutputBytes, fieldModule, fieldPassSource, groupFieldRect, heavyTapSigmaAtScale, highlightModule, importColorMatrix, importModule, importPassSource, isUsablePlacement, lensAmountBasePx, lensDepthPx, lensDirection, lensDisplacementPx, lensExtentPx, lensHeightBasePx, lensMagnitudePx, lensOvalizationAt, lensSizeGain, lensSizeGainFromThickness, linearGradientStops, linearToSrgb, linearToSrgbChannel, occlusionAlphaUnderPolicy, opticsModule, opticsUnderPolicy, outerShadowAlpha, outerShadowFalloff, outerShadowLiftRise, outerShadowOcclusionAt, outerShadowReachPx, outerShadowThickOcclusion, outerShadowThinOcclusion, outerShadowUnderPolicy, packInstances, pipelineKey, placementFit, planPyramid, poolKey, readbackDue, refractionRank, relativeLuminance, rendererError, resolveSurfaces, rimAlongSideFactor, rimAlongSideField, samePlacement, scatterDeepThickness, scatterFloorAtScale, scatterGainAt, scatterGainAtScale, scatterGainFarAtScale, scatterHeavyShareThickAtScale, scatterRampAreaMean, scatterRampReachDevicePx, scatterRampStart, scatterSharpShare, scatterSpanMaxAtScale, scatterThickness, shadowDepthPx, sizeOcclusionAlpha, sizeOcclusionAlphaAt, sizeOuterShadowOcclusion, sizeOuterShadowOcclusionAt, sizeScatterSigma, sizeScatterSigmaAt, sizeShadowDepth, sizeShadowDepthAt, sizeThickness, sizeThicknessUnderPolicy, sizeToneLevelFar, snapRectToDevicePixels, srgbToLinear, srgbToLinearChannel, statsFromBuffer, supportsTimestamps, texelsPerCssPx, tintShade, tintShadeLayer, tintToneAdaptation, tintedMaterialColour, validateAppTexture, withMaterialOverrides };
6823
- //# sourceMappingURL=dist-C43H3RKY.js.map
6824
- //# sourceMappingURL=dist-C43H3RKY.js.map
7392
+ export { ADAPTIVE_LUMINANCE_HIGH, ADAPTIVE_LUMINANCE_LOW, ADAPTIVE_TINT_DARK, ADAPTIVE_TINT_LIGHT, ANALYSIS_DISPATCH, ANALYSIS_GRID, ANALYSIS_PASS_ID, ANALYSIS_STATS_FLOATS, ANALYSIS_TARGET_EXTENT, ANALYSIS_WORKGROUP, BACKDROP_ALPHA_MODES, BACKDROP_COLOR_SPACES, BACKDROP_KINDS, BACKDROP_PASS_ID, BACKDROP_TONE_HIGH, BACKDROP_TONE_LOW, BACKDROP_TONE_MAX, BACKDROP_TONE_SIZE_BIAS, CANVAS_FORMAT, CHAIN_SIGMA_AT_LEVEL_1, CROSS_CHECK_SHAPE_FLOATS, CROSS_CHECK_WORKGROUP, DEFAULT_MATERIAL_PROFILE, FAMILY_C_CROSS_CHECK, FIELD_PASS_ID, GOVERNOR_LADDER, HIGHLIGHT_PASS_ID, IDENTITY_FIT, IDLE_CHANNELS, INCREASED_OCCLUSION_LIFT, INSTANCE_BYTES, INSTANCE_FLOATS, LENS_AMOUNT_MAX, LENS_AMOUNT_PER_SPAN, LENS_EXTENT_GAIN, LENS_HEIGHT_MAX, LENS_HEIGHT_PER_SPAN, LENS_OVALIZATION, LENS_PROFILE_EXPONENT, LENS_REFRACTION_GAIN, LENS_SIZE_GAIN_MAX, LENS_THICKNESS_REFERENCE, LUMINANCE_WEIGHTS, MATERIAL_OPTICS, MATERIAL_VARIANTS, MAX_CHAIN_LEVELS, MIN_LEVEL_EXTENT, NOMINAL_GOVERNOR, NOMINAL_MATERIAL_POLICY, OPTICS_PASS_ID, OUTER_SHADOW, OUTER_SHADOW_THICK_SPANS, OUTER_SHADOW_THIN_L, OUTER_SHADOW_UNMEASURED_BACKDROP_LUMINANCE, OUTPUT_TEXTURE_FORMAT, PASS_LABEL, REFRACTION_LADDER, REFRACTION_SCALE, RENDERER_ERROR_CODES, RENDERER_PASS_IDS, RendererError, SIZE_OCCLUSION_GAIN, SIZE_SCATTER_FLOOR, SIZE_SCATTER_FLOOR_2X, SIZE_SCATTER_GAIN_FAR_2X, SIZE_SCATTER_GAIN_MAX, SIZE_SCATTER_GAIN_MAX_2X, SIZE_SCATTER_RAMP_REACH_1X_PX, SIZE_SCATTER_RAMP_REACH_2X_PX, SIZE_SCATTER_RAMP_START_FAR_1X, SIZE_SCATTER_RAMP_START_FAR_2X, SIZE_SCATTER_RAMP_START_THICK_1X, SIZE_SCATTER_RAMP_START_THICK_2X, SIZE_SCATTER_RAMP_START_THIN_1X, SIZE_SCATTER_RAMP_START_THIN_2X, SIZE_SCATTER_SPAN_MAX, SIZE_SCATTER_SPAN_MAX_2X, SIZE_SHADOW_GAIN_MAX, SIZE_SPAN_MAX, SIZE_SPAN_MIN, SRGB_ENCODING_EXPONENT, SUPPORTED_APP_TEXTURE_FORMATS, WGSL_ANALYSIS_PASS, WGSL_CROSS_CHECK_PASS, WGSL_DOWNSAMPLE_PASS, WGSL_FIELD_KERNELS, WGSL_FIELD_PASS, WGSL_FIELD_SAMPLE, WGSL_HIGHLIGHT_PASS, WGSL_IMPORT_PASS, WGSL_INSTANCE_STRUCT, WGSL_OPTICS_PASS, WGSL_PRELUDE, WGSL_RSUPN_GRAD, WGSL_RSUP_GRAD, WGSL_SMOOTH_UNION, WORKING_TEXTURE_FORMAT, ZERO_STATS, accessibilityRefractionCap, adaptationStrength, adaptedTintAlpha, adaptedTintColour, adaptiveTint, allShaderSource, alphaNormalisationMode, analysisModule, backdropFit, backdropToneAdaptation, backdropToneResponse, backdropToneSizeBiasUnderPolicy, backdropToneSolveWeight, backdropToneUnderPolicy, bodyBlurPlan, chainModule, clipFieldRectToCanvas, collapsedRimUnderPolicy, coverFit, createAdaptationState, createAppTextureProvider, createCopyProvider, createDeviceHost, createGovernor, createGpuContext, createGradientProvider, createPassRunner, createPipelineCache, createPyramidStore, createRebuildLedger, createStorageSlot, createTexturePool, createTimingCollector, createUniformSlot, createVideoProvider, createWebGPURenderer, crossCheckKernelModule, displayP3ToSrgbLinear, effectiveRefraction, encodeOutput, encodeOutputBytes, fieldModule, fieldPassSource, groupFieldRect, heavyTapSigmaAtScale, highlightModule, importColorMatrix, importModule, importPassSource, isUsablePlacement, lensAmountBasePx, lensDepthPx, lensDirection, lensDisplacementPx, lensExtentPx, lensHeightBasePx, lensMagnitudePx, lensOvalizationAt, lensSizeGain, lensSizeGainFromThickness, linearGradientStops, linearToSrgb, linearToSrgbChannel, occlusionAlphaUnderPolicy, occlusionLiftForPolicy, opticsModule, opticsUnderPolicy, outerShadowAlpha, outerShadowFalloff, outerShadowLiftRise, outerShadowOcclusionAt, outerShadowReachPx, outerShadowThickOcclusion, outerShadowThinOcclusion, outerShadowUnderPolicy, packInstances, pipelineKey, placementFit, planPyramid, poolKey, readbackDue, refractionRank, relativeLuminance, rendererError, resolveSurfaces, rimAlongSideFactor, rimAlongSideField, samePlacement, scatterDeepThickness, scatterFloorAtScale, scatterGainAt, scatterGainAtScale, scatterGainFarAtScale, scatterHeavyShareThickAtScale, scatterRampAreaMean, scatterRampReachDevicePx, scatterRampStart, scatterSharpShare, scatterSpanMaxAtScale, scatterThickness, shadowDepthPx, sizeOcclusionAlpha, sizeOcclusionAlphaAt, sizeOuterShadowOcclusion, sizeOuterShadowOcclusionAt, sizeScatterSigma, sizeScatterSigmaAt, sizeShadowDepth, sizeShadowDepthAt, sizeThickness, sizeThicknessUnderPolicy, sizeToneLevelFar, snapRectToDevicePixels, srgbToLinear, srgbToLinearChannel, statsFromBuffer, supportsTimestamps, texelsPerCssPx, tintShade, tintShadeLayer, tintToneAdaptation, tintedMaterialColour, validateAppTexture, withMaterialOverrides };
7393
+ //# sourceMappingURL=dist-VQPRX7BX.js.map
7394
+ //# sourceMappingURL=dist-VQPRX7BX.js.map