@aardworx/wombat.rendering 0.19.15 → 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/dist/runtime/heapAdapter.d.ts.map +1 -1
- package/dist/runtime/heapAdapter.js +39 -2
- package/dist/runtime/heapAdapter.js.map +1 -1
- package/dist/runtime/heapEffect.d.ts +9 -2
- package/dist/runtime/heapEffect.d.ts.map +1 -1
- package/dist/runtime/heapEffect.js +8 -6
- package/dist/runtime/heapEffect.js.map +1 -1
- package/dist/runtime/heapScene.d.ts.map +1 -1
- package/dist/runtime/heapScene.js +5 -3
- package/dist/runtime/heapScene.js.map +1 -1
- package/package.json +1 -1
- package/src/runtime/heapAdapter.ts +46 -3
- package/src/runtime/heapEffect.ts +23 -9
- package/src/runtime/heapScene.ts +6 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aardworx/wombat.rendering",
|
|
3
|
-
"version": "0.19.
|
|
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",
|
|
@@ -35,7 +35,17 @@ import { ISampler } from "../core/sampler.js";
|
|
|
35
35
|
/** Map shader-defined sampler state (FShade Filter/WrapMode names) to a
|
|
36
36
|
* GPUSamplerDescriptor (heap path). */
|
|
37
37
|
function samplerDescriptorFromState(
|
|
38
|
-
state: {
|
|
38
|
+
state: {
|
|
39
|
+
readonly filter: string;
|
|
40
|
+
readonly addressU: string;
|
|
41
|
+
readonly addressV: string;
|
|
42
|
+
readonly addressW?: string;
|
|
43
|
+
readonly comparison?: string;
|
|
44
|
+
readonly maxAnisotropy?: number;
|
|
45
|
+
readonly minLod?: number;
|
|
46
|
+
readonly maxLod?: number;
|
|
47
|
+
readonly mipLodBias?: number;
|
|
48
|
+
},
|
|
39
49
|
): GPUSamplerDescriptor {
|
|
40
50
|
// FShade WrapMode names. WebGPU has no Border / MirrorOnce — map them to
|
|
41
51
|
// their closest modes (clamp-to-edge / mirror-repeat).
|
|
@@ -66,11 +76,36 @@ function samplerDescriptorFromState(
|
|
|
66
76
|
case "MinLinearMagPoint": mag = "nearest"; break;
|
|
67
77
|
default: break; // unknown → all-linear
|
|
68
78
|
}
|
|
69
|
-
|
|
79
|
+
// Explicit maxAnisotropy overrides the Anisotropic-filter default.
|
|
80
|
+
// WebGPU validation: maxAnisotropy > 1 requires all-linear filtering.
|
|
81
|
+
if (state.maxAnisotropy !== undefined) aniso = state.maxAnisotropy;
|
|
82
|
+
if (min !== "linear" || mag !== "linear" || mip !== "linear") aniso = 1;
|
|
83
|
+
const desc: GPUSamplerDescriptor = {
|
|
70
84
|
magFilter: mag, minFilter: min, mipmapFilter: mip,
|
|
71
85
|
addressModeU: addr(state.addressU), addressModeV: addr(state.addressV),
|
|
72
86
|
maxAnisotropy: aniso,
|
|
73
87
|
};
|
|
88
|
+
if (state.addressW !== undefined) (desc as { addressModeW?: GPUAddressMode }).addressModeW = addr(state.addressW);
|
|
89
|
+
if (state.minLod !== undefined) (desc as { lodMinClamp?: number }).lodMinClamp = state.minLod;
|
|
90
|
+
if (state.maxLod !== undefined) (desc as { lodMaxClamp?: number }).lodMaxClamp = state.maxLod;
|
|
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.
|
|
94
|
+
return desc;
|
|
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
|
+
}
|
|
74
109
|
}
|
|
75
110
|
import type { RenderObject } from "../core/renderObject.js";
|
|
76
111
|
import { asAttributeProvider, asUniformProvider } from "../core/provider.js";
|
|
@@ -371,8 +406,16 @@ export function renderObjectToHeapSpec(
|
|
|
371
406
|
// builder, carried through the IR) overrides the scene's default sampler.
|
|
372
407
|
const stateBinding = schema.samplers.find(b => b.state !== undefined);
|
|
373
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
|
+
}
|
|
374
417
|
distinctSamplerAvals.add(
|
|
375
|
-
AVal.constant(ISampler.fromDescriptor(
|
|
418
|
+
AVal.constant(ISampler.fromDescriptor(desc)) as aval<ISampler>,
|
|
376
419
|
);
|
|
377
420
|
} else {
|
|
378
421
|
ro.samplers.iter((_n, av) => { distinctSamplerAvals.add(av as aval<ISampler>); });
|
|
@@ -52,9 +52,20 @@ export interface HeapSamplerBinding {
|
|
|
52
52
|
readonly name: string;
|
|
53
53
|
/** WGSL type — "sampler" or "sampler_comparison". */
|
|
54
54
|
readonly wgslType: string;
|
|
55
|
-
/** Shader-defined sampler state (filter/address) from the
|
|
56
|
-
* builds the GPUSamplerDescriptor from it, overriding the
|
|
57
|
-
|
|
55
|
+
/** Shader-defined sampler state (filter/address/lod/anisotropy) from the
|
|
56
|
+
* IR; the adapter builds the GPUSamplerDescriptor from it, overriding the
|
|
57
|
+
* scene default. */
|
|
58
|
+
readonly state?: {
|
|
59
|
+
readonly filter: string;
|
|
60
|
+
readonly addressU: string;
|
|
61
|
+
readonly addressV: string;
|
|
62
|
+
readonly addressW?: string;
|
|
63
|
+
readonly comparison?: string;
|
|
64
|
+
readonly maxAnisotropy?: number;
|
|
65
|
+
readonly minLod?: number;
|
|
66
|
+
readonly maxLod?: number;
|
|
67
|
+
readonly mipLodBias?: number;
|
|
68
|
+
} | undefined;
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
export interface HeapEffectSchema {
|
|
@@ -236,15 +247,18 @@ function buildSchema(iface: ProgramInterface): HeapEffectSchema {
|
|
|
236
247
|
const fragmentOutputs = iface.fragmentOutputs.map(o => ({
|
|
237
248
|
name: o.name, location: o.location, wgslType: irTypeToWgsl(o.type),
|
|
238
249
|
}));
|
|
239
|
-
// Texture/sampler bindings post-DCE.
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
// "
|
|
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;
|
|
243
257
|
const textures: HeapTextureBinding[] = iface.textures.map(t => ({
|
|
244
|
-
name: t.name, wgslType: "texture_2d<f32>",
|
|
258
|
+
name: t.name, wgslType: isComparison(t.type) ? "texture_depth_2d" : "texture_2d<f32>",
|
|
245
259
|
}));
|
|
246
260
|
const samplers: HeapSamplerBinding[] = iface.samplers.map(s => ({
|
|
247
|
-
name: s.name, wgslType: "sampler",
|
|
261
|
+
name: s.name, wgslType: isComparison(s.type) ? "sampler_comparison" : "sampler",
|
|
248
262
|
...(s.state !== undefined ? { state: s.state } : {}),
|
|
249
263
|
}));
|
|
250
264
|
return { attributes, uniforms, varyings, fragmentOutputs, textures, samplers };
|
package/src/runtime/heapScene.ts
CHANGED
|
@@ -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 =
|
|
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) {
|