@aardworx/wombat.rendering 0.21.10 → 0.21.12
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/resources/preparedRenderObject.d.ts +5 -0
- package/dist/resources/preparedRenderObject.d.ts.map +1 -1
- package/dist/resources/preparedRenderObject.js +37 -5
- package/dist/resources/preparedRenderObject.js.map +1 -1
- package/dist/runtime/heapEligibility.d.ts.map +1 -1
- package/dist/runtime/heapEligibility.js +3 -1
- package/dist/runtime/heapEligibility.js.map +1 -1
- package/dist/runtime/heapScene.d.ts.map +1 -1
- package/dist/runtime/heapScene.js +13 -0
- package/dist/runtime/heapScene.js.map +1 -1
- package/package.json +1 -1
- package/src/resources/preparedRenderObject.ts +46 -5
- package/src/runtime/heapEligibility.ts +3 -1
- package/src/runtime/heapScene.ts +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aardworx/wombat.rendering",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.12",
|
|
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",
|
|
@@ -72,6 +72,11 @@ interface VertexBindingInfo {
|
|
|
72
72
|
readonly slot: number;
|
|
73
73
|
readonly format: GPUVertexFormat;
|
|
74
74
|
readonly byteSize: number;
|
|
75
|
+
/** Byte offset to apply at `setVertexBuffer` time (the group's
|
|
76
|
+
* common base — per-attribute layout offsets are RELATIVE to it).
|
|
77
|
+
* Undefined on legacy construction paths → fall back to the
|
|
78
|
+
* view's own offset. */
|
|
79
|
+
readonly baseOffset?: number;
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
function vertexBindingsFor(iface: ProgramInterface): VertexBindingInfo[] {
|
|
@@ -527,7 +532,7 @@ export class PreparedRenderObject {
|
|
|
527
532
|
if (res === undefined) throw new Error(`PreparedRenderObject.record: missing vertex buffer for "${vb.name}"`);
|
|
528
533
|
const view = this.vertexViews.get(vb.name);
|
|
529
534
|
const buf = res.getValue(token);
|
|
530
|
-
const offset = view !== undefined ? BufferView.offsetOf(view) : 0;
|
|
535
|
+
const offset = vb.baseOffset ?? (view !== undefined ? BufferView.offsetOf(view) : 0);
|
|
531
536
|
if (offset > 0) {
|
|
532
537
|
// size omitted → WebGPU uses (buffer.size − offset).
|
|
533
538
|
pass.setVertexBuffer(vb.slot, buf, offset);
|
|
@@ -602,6 +607,8 @@ export function prepareRenderObject(
|
|
|
602
607
|
readonly stepMode: GPUVertexStepMode;
|
|
603
608
|
readonly stride: number;
|
|
604
609
|
readonly offset: number;
|
|
610
|
+
/** Tight byte size of one element (for the slot-fit rule). */
|
|
611
|
+
readonly byteSizeOf: number;
|
|
605
612
|
readonly format: GPUVertexFormat;
|
|
606
613
|
readonly view: BufferView;
|
|
607
614
|
}
|
|
@@ -631,6 +638,7 @@ export function prepareRenderObject(
|
|
|
631
638
|
name: vb.name,
|
|
632
639
|
shaderLocation: vb.slot,
|
|
633
640
|
stepMode, stride, offset,
|
|
641
|
+
byteSizeOf: view.elementType.byteSize,
|
|
634
642
|
format, view,
|
|
635
643
|
});
|
|
636
644
|
}
|
|
@@ -645,6 +653,8 @@ export function prepareRenderObject(
|
|
|
645
653
|
readonly stride: number;
|
|
646
654
|
readonly stepMode: GPUVertexStepMode;
|
|
647
655
|
readonly view: BufferView;
|
|
656
|
+
/** Byte offset the slot is BOUND at (setVertexBuffer). */
|
|
657
|
+
readonly base: number;
|
|
648
658
|
readonly bufferRes: AdaptiveResource<GPUBuffer>;
|
|
649
659
|
readonly members: ResolvedBinding[];
|
|
650
660
|
}
|
|
@@ -659,9 +669,19 @@ export function prepareRenderObject(
|
|
|
659
669
|
let nextSlot = 0;
|
|
660
670
|
const vertexBuffers = new Map<string, AdaptiveResource<GPUBuffer>>();
|
|
661
671
|
const vertexViews = new Map<string, BufferView>();
|
|
662
|
-
|
|
672
|
+
// WebGPU validates `attribute.offset + formatSize <= arrayStride`
|
|
673
|
+
// (stride > 0), so a view whose offset does not fit RELATIVE to the
|
|
674
|
+
// group's base must live in its OWN slot, bound with a buffer
|
|
675
|
+
// offset at `setVertexBuffer` time — the strided-views idiom
|
|
676
|
+
// (SegA = P[i] at offset 0, SegB = P[i+1] at offset 12, stride 12)
|
|
677
|
+
// depends on this. Group key therefore includes the byte offset the
|
|
678
|
+
// slot is BOUND at (`base`); attributes whose offset equals the
|
|
679
|
+
// base share the slot with relative offset 0, interleaved layouts
|
|
680
|
+
// (offsets 0/16/32/48 within stride 64) join the base-0 group as
|
|
681
|
+
// before via the fit rule below.
|
|
682
|
+
const addToGroup = (r: ResolvedBinding, base: number): void => {
|
|
663
683
|
const bufId = idOf(r.view.buffer as unknown as object);
|
|
664
|
-
const key = `${bufId}|${r.stride}|${r.stepMode}`;
|
|
684
|
+
const key = `${bufId}|${r.stride}|${r.stepMode}|${base}`;
|
|
665
685
|
let group = vbGroups.get(key);
|
|
666
686
|
if (!group) {
|
|
667
687
|
const bufferRes = prepareAdaptiveBuffer(device, r.view.buffer, {
|
|
@@ -670,13 +690,33 @@ export function prepareRenderObject(
|
|
|
670
690
|
});
|
|
671
691
|
group = {
|
|
672
692
|
key, slot: nextSlot++, stride: r.stride, stepMode: r.stepMode,
|
|
673
|
-
view: r.view, bufferRes, members: [],
|
|
693
|
+
view: r.view, base, bufferRes, members: [],
|
|
674
694
|
};
|
|
675
695
|
vbGroups.set(key, group);
|
|
676
696
|
}
|
|
677
697
|
group.members.push(r);
|
|
678
698
|
vertexBuffers.set(r.name, group.bufferRes);
|
|
679
699
|
vertexViews.set(r.name, r.view);
|
|
700
|
+
};
|
|
701
|
+
const fitsAt = (r: ResolvedBinding, base: number): boolean =>
|
|
702
|
+
r.stride === 0
|
|
703
|
+
? r.offset === base
|
|
704
|
+
: r.offset >= base && r.offset - base + r.byteSizeOf <= r.stride;
|
|
705
|
+
for (const r of resolved) {
|
|
706
|
+
// Prefer an existing group of this (buffer, stride, stepMode)
|
|
707
|
+
// whose base the attribute's offset fits relative to; else open a
|
|
708
|
+
// group based at the attribute's own offset (relative offset 0 —
|
|
709
|
+
// always valid).
|
|
710
|
+
const bufId = idOf(r.view.buffer as unknown as object);
|
|
711
|
+
let placed = false;
|
|
712
|
+
for (const g of vbGroups.values()) {
|
|
713
|
+
if (g.key.startsWith(`${bufId}|${r.stride}|${r.stepMode}|`) && fitsAt(r, g.base)) {
|
|
714
|
+
addToGroup(r, g.base);
|
|
715
|
+
placed = true;
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (!placed) addToGroup(r, r.offset);
|
|
680
720
|
}
|
|
681
721
|
const vertexLayouts: GPUVertexBufferLayout[] = [];
|
|
682
722
|
for (const g of vbGroups.values()) {
|
|
@@ -685,7 +725,7 @@ export function prepareRenderObject(
|
|
|
685
725
|
stepMode: g.stepMode,
|
|
686
726
|
attributes: g.members.map((m) => ({
|
|
687
727
|
shaderLocation: m.shaderLocation,
|
|
688
|
-
offset: m.offset,
|
|
728
|
+
offset: m.offset - g.base,
|
|
689
729
|
format: m.format,
|
|
690
730
|
})),
|
|
691
731
|
};
|
|
@@ -709,6 +749,7 @@ export function prepareRenderObject(
|
|
|
709
749
|
slot: g.slot,
|
|
710
750
|
format: head.format,
|
|
711
751
|
byteSize: 0,
|
|
752
|
+
baseOffset: g.base,
|
|
712
753
|
});
|
|
713
754
|
}
|
|
714
755
|
vertexBindings = drawTimeBindings;
|
|
@@ -224,7 +224,9 @@ export function isHeapEligible(ro: RenderObject): aval<boolean> {
|
|
|
224
224
|
for (const av of textures) if (!isHeapServableTexture(av.getValue(token))) return false;
|
|
225
225
|
for (const av of samplers) if (!isHeapServableSampler(av.getValue(token))) return false;
|
|
226
226
|
const dc = ro.drawCall.getValue(token);
|
|
227
|
-
|
|
227
|
+
// 0 is heap-legal (the scan kernel emits indexCount × 0 = nothing,
|
|
228
|
+
// matching what legacy draws) — only negative counts are malformed.
|
|
229
|
+
if (dc.instanceCount < 0) return false;
|
|
228
230
|
if (dc.firstInstance !== 0) return false;
|
|
229
231
|
if (dc.kind === "indexed") {
|
|
230
232
|
if (ro.indices === undefined) return false;
|
package/src/runtime/heapScene.ts
CHANGED
|
@@ -1732,7 +1732,18 @@ export function buildHeapScene(
|
|
|
1732
1732
|
// `colorTargets` here silently dropped blends/write-masks from every
|
|
1733
1733
|
// heap pipeline: transparent heap leaves painted opaque, and the OIT
|
|
1734
1734
|
// pick passes' write-mask-0 "Colors" leaked color onto the composite.
|
|
1735
|
+
// Locations the family's fragment shader actually writes — a
|
|
1736
|
+
// signature target the shader has NO output for must set
|
|
1737
|
+
// writeMask 0 (WebGPU: non-zero writeMask without a fragment
|
|
1738
|
+
// output is a validation error; e.g. a non-picking leaf in a
|
|
1739
|
+
// Colors+pickId pass writes only Colors).
|
|
1740
|
+
const writtenLocations = new Set<number>();
|
|
1741
|
+
for (const e of fam.schema.effects) {
|
|
1742
|
+
const sc = fam.schema.perEffectSchema.get(e)!;
|
|
1743
|
+
for (const fo of sc.fragmentOutputs) writtenLocations.add(fo.location);
|
|
1744
|
+
}
|
|
1735
1745
|
const targets: GPUColorTargetState[] = colorTargets.map((t, i) => {
|
|
1746
|
+
if (!writtenLocations.has(i)) return { format: t.format, writeMask: 0 };
|
|
1736
1747
|
const a = desc.attachments[i];
|
|
1737
1748
|
if (a === undefined) return { format: t.format };
|
|
1738
1749
|
return {
|