@vitreajs/vitrea 0.17.0 → 0.19.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.
@@ -1174,6 +1174,10 @@ function rejectMixedBackdropToneArity(anchorX, thin, thick) {
1174
1174
  }
1175
1175
  function withMaterialOverrides(base, patch) {
1176
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
+ }
1177
1181
  const optics = {};
1178
1182
  for (const variant of MATERIAL_VARIANTS) {
1179
1183
  optics[variant] = { ...base.optics[variant], ...patch.optics?.[variant] };
@@ -1255,6 +1259,7 @@ function withMaterialOverrides(base, patch) {
1255
1259
  rimCollapsed: patch.rimCollapsed ?? base.rimCollapsed,
1256
1260
  rimCollapsedTinted: patch.rimCollapsedTinted ?? base.rimCollapsedTinted,
1257
1261
  rimTintChroma: patch.rimTintChroma ?? base.rimTintChroma,
1262
+ ...backdropToneAbscissa === void 0 ? {} : { backdropToneAbscissa },
1258
1263
  backdropToneAnchorX,
1259
1264
  backdropToneResponseThin,
1260
1265
  backdropToneResponseThick,
@@ -3440,7 +3445,7 @@ var WGSL_HIGHLIGHT_PASS = `struct HighlightUniforms {
3440
3445
  glow : vec4f,
3441
3446
  /// highlight colour, linear light (xyz), unused (w)
3442
3447
  colour : vec4f,
3443
- /// fieldSize.xy, fieldUpsampled (z), unused (w)
3448
+ /// fieldSize.xy, fieldUpsampled (z), local tone field present (w, W28)
3444
3449
  flags : vec4f,
3445
3450
  /// backdrop tone adaptation (W7), exactly as the optics pass takes it:
3446
3451
  /// backdropToneLow, backdropToneHigh, the size bias already divided by the
@@ -3470,6 +3475,7 @@ var WGSL_HIGHLIGHT_PASS = `struct HighlightUniforms {
3470
3475
  /// bound here for the same reason the optics pass binds it: a highlight is the
3471
3476
  /// material catching light, and a material that is not there catches none.
3472
3477
  @group(0) @binding(4) var presenceTexture : texture_2d<f32>;
3478
+ @group(0) @binding(5) var localToneTexture : texture_2d<f32>;
3473
3479
 
3474
3480
  const TAU = 6.283185307179586;
3475
3481
 
@@ -3499,6 +3505,16 @@ fn fs_highlight(in : FullscreenOut) -> @location(0) vec4f {
3499
3505
  aux = textureLoad(auxTexture, texel, 0);
3500
3506
  presence = textureLoad(presenceTexture, texel, 0);
3501
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; }
3502
3518
  let mat = clamp(presence.x, 0.0, 1.0);
3503
3519
  let d = field.x;
3504
3520
  let normal = field.yz;
@@ -3547,14 +3563,14 @@ fn fs_highlight(in : FullscreenOut) -> @location(0) vec4f {
3547
3563
  );
3548
3564
  let sizeK = clamp(spanT * spanT * (3.0 - 2.0 * spanT) * hu.toneLevel.w, 0.0, 1.0);
3549
3565
  var toneAdapt = 0.0;
3550
- if (hu.toneAdapt.w > 0.0) {
3551
- let toneX = hu.toneLevel.x + hu.toneAdapt.z * sizeK;
3566
+ if (toneStrength > 0.0) {
3567
+ let toneX = toneLevel + hu.toneAdapt.z * sizeK;
3552
3568
  let toneT = clamp(
3553
3569
  (toneX - hu.toneAdapt.x) / max(hu.toneAdapt.y - hu.toneAdapt.x, 1e-6),
3554
3570
  0.0,
3555
3571
  1.0,
3556
3572
  );
3557
- 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));
3558
3574
  }
3559
3575
 
3560
3576
  /*
@@ -3718,6 +3734,8 @@ var WGSL_OPTICS_PASS = `struct OpticsUniforms {
3718
3734
  /// thick z, and a length gate w. Appended so every three-knot field keeps its
3719
3735
  /// byte offset and its original shader branch.
3720
3736
  toneExtra : vec4f,
3737
+ /// W28: whether the per-surface reference field is present (x).
3738
+ localTone : vec4f,
3721
3739
  };
3722
3740
 
3723
3741
  @group(0) @binding(0) var<uniform> ou : OpticsUniforms;
@@ -3740,6 +3758,7 @@ var WGSL_OPTICS_PASS = `struct OpticsUniforms {
3740
3758
  /// channel, unioned in the field pass like every other per-surface scalar. One
3741
3759
  /// channel, in the field pass's fourth target \u2014 see 'wgsl/field.ts'.
3742
3760
  @group(0) @binding(9) var presenceTexture : texture_2d<f32>;
3761
+ @group(0) @binding(10) var localToneTexture : texture_2d<f32>;
3743
3762
 
3744
3763
  /// One encoded sRGB channel from a linear one \u2014 the space the backdrop tone
3745
3764
  /// response's anchors live in (W9). Mirrors material.ts's 'linearToSrgbChannel'.
@@ -4064,6 +4083,27 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4064
4083
  presence = textureLoad(presenceTexture, texel, 0);
4065
4084
  }
4066
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
+
4067
4107
  let d = field.x;
4068
4108
  let normal = field.yz;
4069
4109
  let coverage = field.w;
@@ -4425,13 +4465,13 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4425
4465
  */
4426
4466
  var toneAdapt = 0.0;
4427
4467
  if (ou.toneAdapt.w > 0.0) {
4428
- let toneX = ou.toneColour.w + ou.toneAdapt.z * sizeK;
4468
+ let toneX = toneColour.w + ou.toneAdapt.z * sizeK;
4429
4469
  let toneT = clamp(
4430
4470
  (toneX - ou.toneAdapt.x) / max(ou.toneAdapt.y - ou.toneAdapt.x, 1e-6),
4431
4471
  0.0,
4432
4472
  1.0,
4433
4473
  );
4434
- 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));
4435
4475
  }
4436
4476
  /*
4437
4477
  * Both the colour and the alpha move, together and not separately. What the
@@ -4471,9 +4511,9 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4471
4511
  */
4472
4512
  var solvedNeutral = neutral;
4473
4513
  var solvedAlpha = sizedAlpha;
4474
- if (ou.toneAdapt.w > 0.0 && ou.toneRowThin.w > 0.0 &&
4514
+ if (toneStrength > 0.0 && ou.toneRowThin.w > 0.0 &&
4475
4515
  sizedAlpha > 1e-3 && toneAdapt < 0.995) {
4476
- let encodedInput = srgb_encode(ou.toneColour.w);
4516
+ let encodedInput = srgb_encode(toneColour.w);
4477
4517
  let anchor = max(ou.toneAnchor.x, 1e-4);
4478
4518
  let authority =
4479
4519
  smoothstep(anchor * 0.5, anchor, encodedInput) * clamp(ou.toneRowThin.w, 0.0, 1.0);
@@ -4481,10 +4521,10 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4481
4521
  let response = tone_response(encodedInput, sizeK, toneLevelFar);
4482
4522
  // The collapse's mean pull is toward L(toneColour.rgb) \u2014 the LINEAR
4483
4523
  // mean, which toneAnchor.w carries \u2014 not toward the encoded level.
4484
- let preCollapse = (response - toneAdapt * ou.toneAnchor.w) / (1.0 - toneAdapt);
4524
+ let preCollapse = (response - toneAdapt * toneLinearMean) / (1.0 - toneAdapt);
4485
4525
  let neutralLuma = dot(neutral, vec3f(0.2126, 0.7152, 0.0722));
4486
- let nominal = (1.0 - sizedAlpha) * ou.toneAnchor.w + sizedAlpha * neutralLuma;
4487
- 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;
4488
4528
  solvedNeutral = clamp(neutral + vec3f(shift), vec3f(0.0), vec3f(1.0));
4489
4529
  /*
4490
4530
  * The light attractor needs OPACITY. The light scheme's neutral is
@@ -4497,14 +4537,14 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4497
4537
  * collapse's own axis with its own fitted constants.
4498
4538
  */
4499
4539
  let solvedLuma = dot(solvedNeutral, vec3f(0.2126, 0.7152, 0.0722));
4500
- let achieved = (1.0 - sizedAlpha) * ou.toneAnchor.w + sizedAlpha * solvedLuma;
4501
- 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) {
4502
4542
  let alphaTarget = clamp(
4503
- (preCollapse - ou.toneAnchor.w) / (solvedLuma - ou.toneAnchor.w),
4543
+ (preCollapse - toneLinearMean) / (solvedLuma - toneLinearMean),
4504
4544
  sizedAlpha,
4505
4545
  1.0,
4506
4546
  );
4507
- solvedAlpha = mix(sizedAlpha, alphaTarget, authority * ou.toneAdapt.w);
4547
+ solvedAlpha = mix(sizedAlpha, alphaTarget, authority * toneStrength);
4508
4548
  }
4509
4549
  }
4510
4550
  }
@@ -4537,7 +4577,7 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4537
4577
  * lerped toward it would be a black surface rather than a transmitting one.
4538
4578
  * There the CSS tier's own 'backdrop-filter' is what carries the transmission.
4539
4579
  */
4540
- var toneTarget = ou.toneColour.rgb;
4580
+ var toneTarget = toneColour.rgb;
4541
4581
  if (ou.flags.x > 0.5 || domMaterial) {
4542
4582
  toneTarget = mix(toneTarget, backdrop, clamp(ou.toneRowThick.w, 0.0, 1.0));
4543
4583
  }
@@ -4695,7 +4735,7 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4695
4735
  // the composite; as a layer it is the layer over the tone the host measured
4696
4736
  // for the group \u2014 zero where nothing was measured, the same reference-level
4697
4737
  // convention the CSS tier's 'materialLuminance' takes (within its 0.02).
4698
- 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;
4699
4739
  let grip = clamp(ou.seed.w, 0.0, 1.0) * clamp(ou.tone.z, 0.0, 1.0) *
4700
4740
  (1.0 - toneAdapt * (1.0 - clamp(ou.rim.w, 0.0, 1.0)));
4701
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);
@@ -4812,7 +4852,7 @@ fn fs_optics(in : FullscreenOut) -> @location(0) vec4f {
4812
4852
  * author tint layer composites with sixty lines above.
4813
4853
  */
4814
4854
  let rimLuma = materialAlpha * dot(materialColour, vec3f(0.2126, 0.7152, 0.0722))
4815
- + (1.0 - materialAlpha) * ou.toneColour.w;
4855
+ + (1.0 - materialAlpha) * toneColour.w;
4816
4856
  let rimAmplitude = ou.rim.y + ou.rimLaw.x * rimLuma;
4817
4857
  let rimCollapsed = mix(ou.rimLaw.y, ou.rimLaw.z, tintK);
4818
4858
  /*
@@ -4986,6 +5026,112 @@ fn vs_fullscreen(@builtin(vertex_index) index : u32) -> FullscreenOut {
4986
5026
  }
4987
5027
  `;
4988
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
+
4989
5135
  // ../renderer-webgpu/dist/wgsl/index.js
4990
5136
  var withPrelude = (pass) => `${WGSL_PRELUDE}
4991
5137
  ${pass}
@@ -5006,6 +5152,9 @@ function allShaderSource() {
5006
5152
  importPassSource("external"),
5007
5153
  WGSL_DOWNSAMPLE_PASS,
5008
5154
  WGSL_ANALYSIS_PASS,
5155
+ silhouetteReductionModule(),
5156
+ silhouetteFieldModule("rsupn"),
5157
+ silhouetteFieldModule("rsup"),
5009
5158
  WGSL_OPTICS_PASS,
5010
5159
  WGSL_HIGHLIGHT_PASS
5011
5160
  ].join("\n\n");
@@ -5210,7 +5359,7 @@ function createPassRunner(context) {
5210
5359
  };
5211
5360
  },
5212
5361
  opticsPass(encoder, args) {
5213
- const slot = uniformSlot(`optics:${args.resourceId}`, 116);
5362
+ const slot = uniformSlot(`optics:${args.resourceId}`, 120);
5214
5363
  const d = slot.data;
5215
5364
  d[0] = args.viewportDevice[0];
5216
5365
  d[1] = args.viewportDevice[1];
@@ -5328,6 +5477,7 @@ function createPassRunner(context) {
5328
5477
  d[113] = args.backdropToneResponseThin[3] ?? args.backdropToneResponseThin[2];
5329
5478
  d[114] = args.backdropToneResponseThick[3] ?? args.backdropToneResponseThick[2];
5330
5479
  d[115] = args.backdropToneAnchorX.length === 4 ? 1 : 0;
5480
+ d[116] = args.localTone === void 0 ? 0 : 1;
5331
5481
  slot.write();
5332
5482
  const chain = args.backdrop?.chain ?? placeholderView;
5333
5483
  const body = args.backdrop?.body ?? placeholderView;
@@ -5354,7 +5504,8 @@ function createPassRunner(context) {
5354
5504
  { binding: 6, resource: context.flatSampler },
5355
5505
  { binding: 7, resource: args.fields.aux2.createView() },
5356
5506
  { binding: 8, resource: heavy },
5357
- { binding: 9, resource: args.fields.presence.createView() }
5507
+ { binding: 9, resource: args.fields.presence.createView() },
5508
+ { binding: 10, resource: args.localTone ?? placeholderView }
5358
5509
  ]
5359
5510
  }));
5360
5511
  pass.draw(3);
@@ -5382,7 +5533,7 @@ function createPassRunner(context) {
5382
5533
  d[16] = args.fields.width;
5383
5534
  d[17] = args.fields.height;
5384
5535
  d[18] = args.fields.upsampled ? 1 : 0;
5385
- d[19] = 0;
5536
+ d[19] = args.localTone === void 0 ? 0 : 1;
5386
5537
  d[20] = args.backdropTone[0];
5387
5538
  d[21] = Math.max(args.backdropTone[1], args.backdropTone[0] + 1e-4);
5388
5539
  d[22] = args.backdropTone[2];
@@ -5412,7 +5563,8 @@ function createPassRunner(context) {
5412
5563
  { binding: 1, resource: args.fields.field.createView() },
5413
5564
  { binding: 2, resource: args.fields.aux.createView() },
5414
5565
  { binding: 3, resource: context.flatSampler },
5415
- { binding: 4, resource: args.fields.presence.createView() }
5566
+ { binding: 4, resource: args.fields.presence.createView() },
5567
+ { binding: 5, resource: args.localTone ?? placeholderView }
5416
5568
  ]
5417
5569
  }));
5418
5570
  pass.draw(3);
@@ -6068,6 +6220,263 @@ function createPyramidStore(context) {
6068
6220
  }
6069
6221
  var ANALYSIS_DISPATCH = { workgroupSize: ANALYSIS_WORKGROUP, workgroups: 1 };
6070
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
+
6071
6480
  // ../renderer-webgpu/dist/renderer.js
6072
6481
  var OPTICS_PASS_ID = "vitrea.optics";
6073
6482
  var HIGHLIGHT_PASS_ID = "vitrea.highlight";
@@ -6089,6 +6498,7 @@ function createWebGPURenderer(options = {}) {
6089
6498
  const groups = /* @__PURE__ */ new Map();
6090
6499
  const adaptation = /* @__PURE__ */ new Map();
6091
6500
  const lastReadbackAt = /* @__PURE__ */ new Map();
6501
+ const hintedTones = /* @__PURE__ */ new Map();
6092
6502
  let accessibility = NOMINAL_MATERIAL_POLICY;
6093
6503
  let material = withMaterialOverrides(DEFAULT_MATERIAL_PROFILE, options.materialProfile ?? {});
6094
6504
  let viewport = options.viewport ?? {
@@ -6100,6 +6510,7 @@ function createWebGPURenderer(options = {}) {
6100
6510
  let builtGeneration;
6101
6511
  let store;
6102
6512
  let runner;
6513
+ let silhouetteTone;
6103
6514
  let framesDrawn = 0;
6104
6515
  let generations = 0;
6105
6516
  let lastFrameTimeMs;
@@ -6123,6 +6534,9 @@ function createWebGPURenderer(options = {}) {
6123
6534
  });
6124
6535
  const dropContext = () => {
6125
6536
  store?.destroy();
6537
+ silhouetteTone?.destroy();
6538
+ silhouetteTone = void 0;
6539
+ hintedTones.clear();
6126
6540
  runner?.destroy();
6127
6541
  context?.destroy();
6128
6542
  store = void 0;
@@ -6154,6 +6568,7 @@ function createWebGPURenderer(options = {}) {
6154
6568
  builtGeneration = generation;
6155
6569
  store = createPyramidStore(context);
6156
6570
  runner = createPassRunner(context);
6571
+ silhouetteTone = createSilhouetteTonePass(context);
6157
6572
  if (superseded !== void 0 && superseded !== generation) {
6158
6573
  for (const provider of providers.values()) {
6159
6574
  if (provider.generation !== generation)
@@ -6314,7 +6729,7 @@ function createWebGPURenderer(options = {}) {
6314
6729
  }
6315
6730
  return { built, unbuilt };
6316
6731
  }
6317
- function drawGroups(encoder, resolution) {
6732
+ function drawGroups(encoder, resolution, timeMs) {
6318
6733
  const { store: pyramids, runner: passes } = ensureContext();
6319
6734
  const active = targets;
6320
6735
  if (active === void 0) {
@@ -6330,7 +6745,11 @@ function createWebGPURenderer(options = {}) {
6330
6745
  let drawn = 0;
6331
6746
  planesDrawn.add(active.plane);
6332
6747
  const resourceOf = (groupId) => groupResourceId(active.plane, groupId);
6333
- 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
+ };
6334
6753
  for (const entry of groups.values()) {
6335
6754
  const input = entry.input;
6336
6755
  if (input.surfaces.length === 0) {
@@ -6368,7 +6787,7 @@ function createWebGPURenderer(options = {}) {
6368
6787
  rectDevice.x * cssPerDevice,
6369
6788
  rectDevice.y * cssPerDevice
6370
6789
  ]);
6371
- const fields = passes.fieldPass(encoder, {
6790
+ const fieldArgs = {
6372
6791
  resourceId: resourceOf(input.groupId),
6373
6792
  family: governor.knobs.fieldFamily,
6374
6793
  rectDevice,
@@ -6380,19 +6799,62 @@ function createWebGPURenderer(options = {}) {
6380
6799
  instances: packed.data,
6381
6800
  instanceCount: packed.count,
6382
6801
  union
6383
- });
6802
+ };
6803
+ const fields = passes.fieldPass(encoder, fieldArgs);
6384
6804
  const state = stateOf(input, resolution);
6385
6805
  const variant = variantOf(input);
6386
6806
  const sourceId = input.backdropSourceId;
6387
6807
  const pyramid = sourceId === void 0 ? void 0 : pyramids.resources(sourceId);
6388
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));
6389
6851
  const nominalOptics = material.optics[variant];
6390
6852
  const optics = opticsUnderPolicy(nominalOptics, policy, material);
6391
6853
  const backdropTone = [
6392
6854
  material.backdropToneLow,
6393
6855
  material.backdropToneHigh,
6394
6856
  backdropToneSizeBiasUnderPolicy(policy, material),
6395
- input.backdropTone === void 0 ? 0 : backdropToneUnderPolicy(policy, material) * material.backdropToneMax
6857
+ input.backdropTone === void 0 && localTone === void 0 ? 0 : backdropToneUnderPolicy(policy, material) * material.backdropToneMax
6396
6858
  ];
6397
6859
  const backdropToneLevel = shadowBackdropLuminance ?? 0;
6398
6860
  const refraction = effectiveRefraction(accessibilityRefractionCap(policy), state.refraction);
@@ -6403,6 +6865,7 @@ function createWebGPURenderer(options = {}) {
6403
6865
  targetFormat: active.format,
6404
6866
  rectDevice,
6405
6867
  fields,
6868
+ ...localTone === void 0 ? {} : { localTone },
6406
6869
  viewportDevice,
6407
6870
  cssPerDevice,
6408
6871
  coverageRampCss: cssPerDevice,
@@ -6626,6 +7089,7 @@ function createWebGPURenderer(options = {}) {
6626
7089
  rectDevice: surfaceRectDevice,
6627
7090
  fieldRectDevice: rectDevice,
6628
7091
  fields,
7092
+ ...localTone === void 0 ? {} : { localTone },
6629
7093
  viewportDevice,
6630
7094
  cssPerDevice,
6631
7095
  sweep: lead.channels.sweep,
@@ -6766,8 +7230,11 @@ function createWebGPURenderer(options = {}) {
6766
7230
  },
6767
7231
  removeGroup(groupId) {
6768
7232
  groups.delete(groupId);
6769
- for (const plane of planesDrawn)
7233
+ for (const plane of planesDrawn) {
6770
7234
  runner?.forget(groupResourceId(plane, groupId));
7235
+ silhouetteTone?.forget(groupResourceId(plane, groupId));
7236
+ hintedTones.delete(groupResourceId(plane, groupId));
7237
+ }
6771
7238
  },
6772
7239
  setAccessibility(policy) {
6773
7240
  accessibility = policy;
@@ -6811,12 +7278,14 @@ function createWebGPURenderer(options = {}) {
6811
7278
  if (args.highlight !== void 0)
6812
7279
  passes.clearPass(encoder, args.highlight);
6813
7280
  }
6814
- result = drawGroups(encoder, args.resolution);
7281
+ result = drawGroups(encoder, args.resolution, args.frame.timeMs);
6815
7282
  args.timing?.resolve(encoder);
6816
7283
  gpu.device.queue.submit([encoder.finish()]);
6817
7284
  pyramids.afterSubmit();
7285
+ silhouetteTone?.afterSubmit();
6818
7286
  } finally {
6819
7287
  pyramids.releaseAcquired();
7288
+ silhouetteTone?.cancelQueued();
6820
7289
  }
6821
7290
  const delta = lastFrameTimeMs === void 0 ? 0 : args.frame.timeMs - lastFrameTimeMs;
6822
7291
  lastFrameTimeMs = args.frame.timeMs;
@@ -6849,6 +7318,7 @@ function createWebGPURenderer(options = {}) {
6849
7318
  } catch (error) {
6850
7319
  pendingEncoder = void 0;
6851
7320
  pyramids.releaseAcquired();
7321
+ silhouetteTone?.cancelQueued();
6852
7322
  throw error;
6853
7323
  }
6854
7324
  },
@@ -6864,12 +7334,14 @@ function createWebGPURenderer(options = {}) {
6864
7334
  passes.clearPass(encoder, targets.optics);
6865
7335
  if (targets.highlight !== void 0)
6866
7336
  passes.clearPass(encoder, targets.highlight);
6867
- drawGroups(encoder, frameContext.resolution);
7337
+ drawGroups(encoder, frameContext.resolution, frameContext.frame.timeMs);
6868
7338
  }
6869
7339
  context.device.queue.submit([encoder.finish()]);
6870
7340
  pyramids.afterSubmit();
7341
+ silhouetteTone?.afterSubmit();
6871
7342
  } finally {
6872
7343
  pyramids.releaseAcquired();
7344
+ silhouetteTone?.cancelQueued();
6873
7345
  }
6874
7346
  const delta = lastFrameTimeMs === void 0 ? 0 : frameContext.frame.timeMs - lastFrameTimeMs;
6875
7347
  lastFrameTimeMs = frameContext.frame.timeMs;
@@ -6881,7 +7353,14 @@ function createWebGPURenderer(options = {}) {
6881
7353
  }
6882
7354
  };
6883
7355
  },
7356
+ backdropToneAbscissae(groupId) {
7357
+ return [
7358
+ ...silhouetteTone?.readings(groupId) ?? [],
7359
+ ...[...hintedTones.values()].filter((entry) => entry.groupId === groupId).flatMap((entry) => entry.readings)
7360
+ ];
7361
+ },
6884
7362
  async collectAdaptation() {
7363
+ await silhouetteTone?.collect();
6885
7364
  if (store === void 0)
6886
7365
  return 0;
6887
7366
  const stats = await store.collectStats();
@@ -6911,5 +7390,5 @@ function createWebGPURenderer(options = {}) {
6911
7390
  }
6912
7391
 
6913
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 };
6914
- //# sourceMappingURL=dist-T7PRMUM4.js.map
6915
- //# sourceMappingURL=dist-T7PRMUM4.js.map
7393
+ //# sourceMappingURL=dist-VQPRX7BX.js.map
7394
+ //# sourceMappingURL=dist-VQPRX7BX.js.map