@aardworx/wombat.rendering 0.19.16 → 0.19.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aardworx/wombat.rendering",
3
- "version": "0.19.16",
3
+ "version": "0.19.17",
4
4
  "description": "WebGPU rendering layer for the Wombat TypeScript stack — RenderObject/RenderTask/runtime + window glue, port of Aardvark.Rendering's lower layers on top of WebGPU and wombat.shader.",
5
5
  "license": "MIT",
6
6
  "author": "krauthaufen",
@@ -88,10 +88,25 @@ function samplerDescriptorFromState(
88
88
  if (state.addressW !== undefined) (desc as { addressModeW?: GPUAddressMode }).addressModeW = addr(state.addressW);
89
89
  if (state.minLod !== undefined) (desc as { lodMinClamp?: number }).lodMinClamp = state.minLod;
90
90
  if (state.maxLod !== undefined) (desc as { lodMaxClamp?: number }).lodMaxClamp = state.maxLod;
91
- // NOT applied: `comparison` (a compare sampler needs a sampler_comparison
92
- // binding separate feature) and `mipLodBias` (no WebGPU counterpart).
91
+ // `comparison` is applied by the caller when the binding is a
92
+ // sampler_comparison (a compare function on a filtering sampler would
93
+ // fail WebGPU validation). `mipLodBias` has no WebGPU counterpart.
93
94
  return desc;
94
95
  }
96
+
97
+ /** FShade ComparisonFunction case name → GPUCompareFunction. */
98
+ function compareFunctionOf(name: string): GPUCompareFunction {
99
+ switch (name) {
100
+ case "Never": return "never";
101
+ case "Less": return "less";
102
+ case "Equal": return "equal";
103
+ case "LessOrEqual": return "less-equal";
104
+ case "Greater": return "greater";
105
+ case "GreaterOrEqual": return "greater-equal";
106
+ case "NotEqual": return "not-equal";
107
+ default: return "always";
108
+ }
109
+ }
95
110
  import type { RenderObject } from "../core/renderObject.js";
96
111
  import { asAttributeProvider, asUniformProvider } from "../core/provider.js";
97
112
  import type { HeapDrawSpec, HeapTextureSet } from "./heapScene.js";
@@ -391,8 +406,16 @@ export function renderObjectToHeapSpec(
391
406
  // builder, carried through the IR) overrides the scene's default sampler.
392
407
  const stateBinding = schema.samplers.find(b => b.state !== undefined);
393
408
  if (stateBinding?.state !== undefined) {
409
+ const desc = samplerDescriptorFromState(stateBinding.state);
410
+ if (stateBinding.wgslType === "sampler_comparison") {
411
+ // Shadow sampler: the binding requires a compare function. WebGPU
412
+ // also forbids anisotropy unless all filters are linear; keep the
413
+ // state's choice (samplerDescriptorFromState already clamps).
414
+ (desc as { compare?: GPUCompareFunction }).compare =
415
+ compareFunctionOf(stateBinding.state.comparison ?? "LessOrEqual");
416
+ }
394
417
  distinctSamplerAvals.add(
395
- AVal.constant(ISampler.fromDescriptor(samplerDescriptorFromState(stateBinding.state))) as aval<ISampler>,
418
+ AVal.constant(ISampler.fromDescriptor(desc)) as aval<ISampler>,
396
419
  );
397
420
  } else {
398
421
  ro.samplers.iter((_n, av) => { distinctSamplerAvals.add(av as aval<ISampler>); });
@@ -247,15 +247,18 @@ function buildSchema(iface: ProgramInterface): HeapEffectSchema {
247
247
  const fragmentOutputs = iface.fragmentOutputs.map(o => ({
248
248
  name: o.name, location: o.location, wgslType: irTypeToWgsl(o.type),
249
249
  }));
250
- // Texture/sampler bindings post-DCE. The IR Type for textures is a
251
- // structured kind we don't fully model here yet — for v1 every
252
- // texture maps to "texture_2d<f32>" and every sampler to plain
253
- // "sampler". Specialise as multi-kind support lands in the DSL.
250
+ // Texture/sampler bindings post-DCE. Comparison (shadow) samplers pair
251
+ // with depth textures: the legalise pass marks both with
252
+ // `comparison: true` — map them to "texture_depth_2d" /
253
+ // "sampler_comparison". Everything else stays the v1 2D-float kind;
254
+ // specialise further as multi-kind support lands in the DSL.
255
+ const isComparison = (t: unknown): boolean =>
256
+ typeof t === "object" && t !== null && (t as { comparison?: boolean }).comparison === true;
254
257
  const textures: HeapTextureBinding[] = iface.textures.map(t => ({
255
- name: t.name, wgslType: "texture_2d<f32>",
258
+ name: t.name, wgslType: isComparison(t.type) ? "texture_depth_2d" : "texture_2d<f32>",
256
259
  }));
257
260
  const samplers: HeapSamplerBinding[] = iface.samplers.map(s => ({
258
- name: s.name, wgslType: "sampler",
261
+ name: s.name, wgslType: isComparison(s.type) ? "sampler_comparison" : "sampler",
259
262
  ...(s.state !== undefined ? { state: s.state } : {}),
260
263
  }));
261
264
  return { attributes, uniforms, varyings, fragmentOutputs, textures, samplers };
@@ -1273,7 +1273,10 @@ export function buildHeapScene(
1273
1273
  interface BglEntry { bgl: GPUBindGroupLayout; pipelineLayout: GPUPipelineLayout }
1274
1274
  const bglCache = new Map<string, BglEntry>();
1275
1275
  function getBgl(layout: BucketLayout, withAtlasArrays: boolean): BglEntry {
1276
- const key = `t${layout.textureBindings.length}|s${layout.samplerBindings.length}|a${withAtlasArrays ? 1 : 0}`;
1276
+ const key =
1277
+ `t${layout.textureBindings.map(t => t.wgslType).join(",")}` +
1278
+ `|s${layout.samplerBindings.map(s => s.wgslType).join(",")}` +
1279
+ `|a${withAtlasArrays ? 1 : 0}`;
1277
1280
  let e = bglCache.get(key);
1278
1281
  if (e !== undefined) return e;
1279
1282
  // Heap data buffers are read by both stages: FS uniform-via-varying
@@ -1294,13 +1297,13 @@ export function buildHeapScene(
1294
1297
  for (const t of layout.textureBindings) {
1295
1298
  entries.push({
1296
1299
  binding: t.binding, visibility: GPUShaderStage.FRAGMENT,
1297
- texture: { sampleType: "float" },
1300
+ texture: { sampleType: t.wgslType === "texture_depth_2d" ? "depth" : "float" },
1298
1301
  });
1299
1302
  }
1300
1303
  for (const s of layout.samplerBindings) {
1301
1304
  entries.push({
1302
1305
  binding: s.binding, visibility: GPUShaderStage.FRAGMENT,
1303
- sampler: { type: "filtering" },
1306
+ sampler: { type: s.wgslType === "sampler_comparison" ? "comparison" : "filtering" },
1304
1307
  });
1305
1308
  }
1306
1309
  if (withAtlasArrays) {