@forgeax/engine-rhi-webgpu 0.1.4 → 0.1.6
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/README.md +3 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/__mocks__/gpu-device.d.ts +0 -7
- package/dist/__tests__/__mocks__/gpu-device.d.ts.map +1 -1
- package/dist/device.d.ts +1 -1
- package/dist/device.d.ts.map +1 -1
- package/dist/index.mjs +112 -99
- package/dist/index.mjs.map +1 -1
- package/dist/internal/timestamp-query.d.ts +0 -24
- package/dist/internal/timestamp-query.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/__mocks__/gpu-device.ts +1 -10
- package/src/__tests__/dawn-real-gpu.dawn.test.ts +0 -43
- package/src/__tests__/rhi-webgpu.unit.test.ts +7 -41
- package/src/device.ts +164 -36
- package/src/internal/timestamp-query.ts +2 -110
- package/dist/__tests__/queue-write-range.browser.test.d.ts +0 -2
- package/dist/__tests__/queue-write-range.browser.test.d.ts.map +0 -1
- package/dist/__tests__/rgba16float-live-probe.browser.test.d.ts +0 -2
- package/dist/__tests__/rgba16float-live-probe.browser.test.d.ts.map +0 -1
- package/dist/__tests__/rgba16float-live-probe.d.ts +0 -12
- package/dist/__tests__/rgba16float-live-probe.d.ts.map +0 -1
- package/dist/__tests__/rgba16float-live-probe.dawn.test.d.ts +0 -2
- package/dist/__tests__/rgba16float-live-probe.dawn.test.d.ts.map +0 -1
- package/src/__tests__/queue-write-range.browser.test.ts +0 -60
- package/src/__tests__/rgba16float-live-probe.browser.test.ts +0 -13
- package/src/__tests__/rgba16float-live-probe.dawn.test.ts +0 -15
- package/src/__tests__/rgba16float-live-probe.ts +0 -44
|
@@ -1,24 +1,4 @@
|
|
|
1
1
|
import { type Result, RhiError } from '@forgeax/engine-rhi';
|
|
2
|
-
export type TimestampQueryHealthCode = 'timestamp-query-unsupported' | 'timestamp-query-encoder-missing' | 'timestamp-query-generation-mismatch';
|
|
3
|
-
export interface TimestampQueryHealth {
|
|
4
|
-
readonly status: 'ready' | 'fault';
|
|
5
|
-
readonly code: TimestampQueryHealthCode | undefined;
|
|
6
|
-
readonly expected: string;
|
|
7
|
-
readonly hint: string;
|
|
8
|
-
readonly deviceGeneration: number;
|
|
9
|
-
readonly expectedGeneration: number | undefined;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Keep timestamp capability and device-generation checks structured. This is
|
|
13
|
-
* also the health vocabulary used by recovery callers; a missing capability
|
|
14
|
-
* or stale encoder is never reported as a fabricated timing interval.
|
|
15
|
-
*/
|
|
16
|
-
export declare function timestampQueryHealth(input: {
|
|
17
|
-
readonly capability: boolean;
|
|
18
|
-
readonly encoderWriteTimestamp: boolean;
|
|
19
|
-
readonly deviceGeneration: number;
|
|
20
|
-
readonly expectedGeneration?: number | undefined;
|
|
21
|
-
}): TimestampQueryHealth;
|
|
22
2
|
export declare function resolveTimestampQueries(args: {
|
|
23
3
|
rawEncoder: GPUCommandEncoder;
|
|
24
4
|
rawQuerySet: GPUQuerySet;
|
|
@@ -26,14 +6,10 @@ export declare function resolveTimestampQueries(args: {
|
|
|
26
6
|
queryCount: number;
|
|
27
7
|
rawDestination: GPUBuffer;
|
|
28
8
|
destinationOffset: number;
|
|
29
|
-
readonly deviceGeneration?: number;
|
|
30
|
-
readonly expectedGeneration?: number;
|
|
31
9
|
}): Result<void, RhiError>;
|
|
32
10
|
export declare function writeTimestamp(args: {
|
|
33
11
|
rawEncoder: GPUCommandEncoder;
|
|
34
12
|
rawQuerySet: GPUQuerySet;
|
|
35
13
|
queryIndex: number;
|
|
36
|
-
readonly deviceGeneration?: number;
|
|
37
|
-
readonly expectedGeneration?: number;
|
|
38
14
|
}): void;
|
|
39
15
|
//# sourceMappingURL=timestamp-query.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timestamp-query.d.ts","sourceRoot":"","sources":["../../src/internal/timestamp-query.ts"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,MAAM,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAErE,
|
|
1
|
+
{"version":3,"file":"timestamp-query.d.ts","sourceRoot":"","sources":["../../src/internal/timestamp-query.ts"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,MAAM,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAErE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,UAAU,EAAE,iBAAiB,CAAC;IAC9B,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,SAAS,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B,GAAG,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAoBzB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,UAAU,EAAE,iBAAiB,CAAC;IAC9B,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,IAAI,CAqBP"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-rhi-webgpu",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@forgeax/engine-rhi": "0.1.
|
|
26
|
-
"@forgeax/engine-types": "0.1.
|
|
25
|
+
"@forgeax/engine-rhi": "0.1.6",
|
|
26
|
+
"@forgeax/engine-types": "0.1.6",
|
|
27
27
|
"@webgpu/types": "^0.1.71"
|
|
28
28
|
},
|
|
29
29
|
"forgeax": {
|
|
@@ -52,14 +52,6 @@ export interface MockFailures {
|
|
|
52
52
|
export type MockCapture =
|
|
53
53
|
| { kind: 'requestAdapter'; options: GPURequestAdapterOptions | undefined }
|
|
54
54
|
| { kind: 'requestDevice'; options: GPUDeviceDescriptor | undefined }
|
|
55
|
-
| {
|
|
56
|
-
kind: 'writeBuffer';
|
|
57
|
-
buffer: MockBuffer;
|
|
58
|
-
bufferOffset: number;
|
|
59
|
-
data: ArrayBufferView | ArrayBuffer;
|
|
60
|
-
dataOffset: number | undefined;
|
|
61
|
-
size: number | undefined;
|
|
62
|
-
}
|
|
63
55
|
| { kind: 'createBuffer'; descriptor: GPUBufferDescriptor }
|
|
64
56
|
| { kind: 'createTexture'; descriptor: GPUTextureDescriptor }
|
|
65
57
|
| { kind: 'createSampler'; descriptor: GPUSamplerDescriptor | undefined }
|
|
@@ -308,9 +300,8 @@ function makeDevice(captured: MockCapture[], failures: MockFailures): MockDevice
|
|
|
308
300
|
// No-op; w6 unit tests assert the shim wiring + bounds error paths
|
|
309
301
|
// before the call would reach the real GPU layer.
|
|
310
302
|
},
|
|
311
|
-
writeBuffer(
|
|
303
|
+
writeBuffer(_buffer, _bufferOffset, _data, _dataOffset, _size): void {
|
|
312
304
|
// No-op; bounds checks happen in the shim before this is reached.
|
|
313
|
-
captured.push({ kind: 'writeBuffer', buffer, bufferOffset, data, dataOffset, size });
|
|
314
305
|
},
|
|
315
306
|
writeTexture(_destination, _data, _dataLayout, _size): void {
|
|
316
307
|
// No-op; alignment validation happens in the shim before this is hit.
|
|
@@ -61,49 +61,6 @@ async function requestRhiDevice(): Promise<RhiDevice | undefined> {
|
|
|
61
61
|
return deviceResult.value;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
describe('dawn-real-gpu - queue.writeBuffer typed range', () => {
|
|
65
|
-
it('reads back exactly the selected typed-array elements', async () => {
|
|
66
|
-
const device = await requestRhiDevice();
|
|
67
|
-
if (device === undefined) return;
|
|
68
|
-
const targetResult = device.createBuffer({
|
|
69
|
-
label: 'dawn-write-range-target',
|
|
70
|
-
size: 32,
|
|
71
|
-
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
|
|
72
|
-
});
|
|
73
|
-
const readbackResult = device.createBuffer({
|
|
74
|
-
label: 'dawn-write-range-readback',
|
|
75
|
-
size: 32,
|
|
76
|
-
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
|
77
|
-
});
|
|
78
|
-
expect(targetResult.ok && readbackResult.ok).toBe(true);
|
|
79
|
-
if (!targetResult.ok || !readbackResult.ok) return;
|
|
80
|
-
|
|
81
|
-
const source = new Float32Array([10, 20, 30, 40, 50]);
|
|
82
|
-
const write = device.queue.writeBuffer(targetResult.value, 0, source, 1, 2);
|
|
83
|
-
expect(write.ok).toBe(true);
|
|
84
|
-
if (!write.ok) return;
|
|
85
|
-
|
|
86
|
-
const encoderResult = device.createCommandEncoder({ label: 'dawn-write-range-copy' });
|
|
87
|
-
expect(encoderResult.ok).toBe(true);
|
|
88
|
-
if (!encoderResult.ok) return;
|
|
89
|
-
encoderResult.value.copyBufferToBuffer(targetResult.value, 0, readbackResult.value, 0, 32);
|
|
90
|
-
const finish = encoderResult.value.finish();
|
|
91
|
-
expect(finish.ok).toBe(true);
|
|
92
|
-
if (!finish.ok) return;
|
|
93
|
-
expect(device.queue.submit([finish.value]).ok).toBe(true);
|
|
94
|
-
await device.queue.onSubmittedWorkDone();
|
|
95
|
-
|
|
96
|
-
const mapped = await readbackResult.value.mapAsync(GPUMapMode.READ);
|
|
97
|
-
expect(mapped.ok).toBe(true);
|
|
98
|
-
if (!mapped.ok) return;
|
|
99
|
-
const range = mapped.value.getMappedRange();
|
|
100
|
-
expect(range.ok).toBe(true);
|
|
101
|
-
if (!range.ok) return;
|
|
102
|
-
expect(Array.from(new Float32Array(range.value.slice(0)))).toEqual([20, 30, 0, 0, 0, 0, 0, 0]);
|
|
103
|
-
mapped.value.unmap();
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
64
|
function reportTimestampEvidence(evidence: Record<string, unknown>): void {
|
|
108
65
|
// biome-ignore lint/suspicious/noConsole: the dawn admission record is the test evidence output
|
|
109
66
|
console.log(JSON.stringify(evidence));
|
|
@@ -685,7 +685,7 @@ import { createMockGpu, type MockCapture, makeShaderError } from './__mocks__/gp
|
|
|
685
685
|
const desc: RenderPipelineDescriptor = {
|
|
686
686
|
label: 'rpp',
|
|
687
687
|
layout: 'auto',
|
|
688
|
-
vertex: { module: shader.value
|
|
688
|
+
vertex: { module: shader.value, entryPoint: 'v', buffers: [] },
|
|
689
689
|
fragment: undefined,
|
|
690
690
|
};
|
|
691
691
|
const out = device.createRenderPipeline(desc);
|
|
@@ -703,12 +703,17 @@ import { createMockGpu, type MockCapture, makeShaderError } from './__mocks__/gp
|
|
|
703
703
|
if (!r.ok) throw new Error('mock requestDevice failed');
|
|
704
704
|
const device = r.value;
|
|
705
705
|
|
|
706
|
+
const shader = await createShaderModule(device, {
|
|
707
|
+
code: '@vertex fn v() -> @builtin(position) vec4f { return vec4f(0); }',
|
|
708
|
+
});
|
|
709
|
+
if (!shader.ok) throw new Error('mock shader creation failed');
|
|
706
710
|
const out = device.createRenderPipeline({
|
|
707
711
|
label: 'invalid-rpp',
|
|
708
712
|
layout: 'auto',
|
|
709
713
|
vertex: {
|
|
710
|
-
module:
|
|
714
|
+
module: shader.value,
|
|
711
715
|
entryPoint: 'v',
|
|
716
|
+
buffers: [],
|
|
712
717
|
},
|
|
713
718
|
fragment: undefined,
|
|
714
719
|
});
|
|
@@ -1653,45 +1658,6 @@ import { createMockGpu, type MockCapture, makeShaderError } from './__mocks__/gp
|
|
|
1653
1658
|
expect(out.error.code).toBe('queue-write-buffer-out-of-bounds');
|
|
1654
1659
|
}
|
|
1655
1660
|
});
|
|
1656
|
-
|
|
1657
|
-
it('interprets Float32Array dataOffset and size as elements for bounds', async () => {
|
|
1658
|
-
const gpu = createMockGpu();
|
|
1659
|
-
const r = await requestDevice({ gpu });
|
|
1660
|
-
if (!r.ok) throw new Error('mock requestDevice should not fail');
|
|
1661
|
-
const device = r.value as unknown as DeviceLike;
|
|
1662
|
-
const bufResult = device.createBuffer({ size: 8, usage: 0 });
|
|
1663
|
-
if (!bufResult.ok) throw new Error('createBuffer should succeed');
|
|
1664
|
-
const data = new Float32Array(4);
|
|
1665
|
-
const out = device.queue.writeBuffer(bufResult.value, 0, data, 1, 3);
|
|
1666
|
-
expect(out.ok).toBe(false);
|
|
1667
|
-
if (!out.ok) expect(out.error.code).toBe('queue-write-buffer-out-of-bounds');
|
|
1668
|
-
});
|
|
1669
|
-
|
|
1670
|
-
it('forwards a non-zero Float32 view and element range without byte reinterpretation', async () => {
|
|
1671
|
-
const gpu = createMockGpu();
|
|
1672
|
-
const r = await requestDevice({ gpu });
|
|
1673
|
-
if (!r.ok) throw new Error('mock requestDevice should not fail');
|
|
1674
|
-
const device = r.value as unknown as DeviceLike;
|
|
1675
|
-
const bufResult = device.createBuffer({ size: 16, usage: 0 });
|
|
1676
|
-
if (!bufResult.ok) throw new Error('createBuffer should succeed');
|
|
1677
|
-
const source = new Float32Array([10, 20, 30, 40, 50]);
|
|
1678
|
-
const view = source.subarray(1);
|
|
1679
|
-
const out = device.queue.writeBuffer(bufResult.value, 0, view, 1, 2);
|
|
1680
|
-
expect(out.ok).toBe(true);
|
|
1681
|
-
const event = [...gpu.__captured].reverse().find((entry) => entry.kind === 'writeBuffer');
|
|
1682
|
-
expect(event?.kind).toBe('writeBuffer');
|
|
1683
|
-
if (event?.kind !== 'writeBuffer') return;
|
|
1684
|
-
expect(event.data).toBe(view);
|
|
1685
|
-
expect(event.dataOffset).toBe(1);
|
|
1686
|
-
expect(event.size).toBe(2);
|
|
1687
|
-
if (!ArrayBuffer.isView(event.data)) return;
|
|
1688
|
-
const selected = new Uint8Array(
|
|
1689
|
-
event.data.buffer,
|
|
1690
|
-
event.data.byteOffset + (event.dataOffset ?? 0) * Float32Array.BYTES_PER_ELEMENT,
|
|
1691
|
-
(event.size ?? 0) * Float32Array.BYTES_PER_ELEMENT,
|
|
1692
|
-
);
|
|
1693
|
-
expect([...selected]).toEqual([...new Uint8Array(source.buffer, 2 * 4, 8)]);
|
|
1694
|
-
});
|
|
1695
1661
|
});
|
|
1696
1662
|
|
|
1697
1663
|
// ---------------------------------------------------------------------------
|
package/src/device.ts
CHANGED
|
@@ -40,11 +40,13 @@ import type {
|
|
|
40
40
|
ComputePassDescriptor,
|
|
41
41
|
ComputePipeline,
|
|
42
42
|
ComputePipelineDescriptor,
|
|
43
|
+
ExternalImageTextureDestination,
|
|
43
44
|
MappedBuffer,
|
|
44
45
|
PipelineLayout,
|
|
45
46
|
PipelineLayoutDescriptor,
|
|
46
47
|
QuerySet,
|
|
47
48
|
QuerySetDescriptor,
|
|
49
|
+
RenderPassDescriptor,
|
|
48
50
|
RenderPipeline,
|
|
49
51
|
RenderPipelineDescriptor,
|
|
50
52
|
Result,
|
|
@@ -64,8 +66,9 @@ import type {
|
|
|
64
66
|
TextureDescriptor,
|
|
65
67
|
TextureView,
|
|
66
68
|
TextureViewDescriptor,
|
|
69
|
+
TextureWriteDestination,
|
|
67
70
|
} from '@forgeax/engine-rhi';
|
|
68
|
-
import { err, ok, RhiError as RhiErrorClass
|
|
71
|
+
import { err, ok, RhiError as RhiErrorClass } from '@forgeax/engine-rhi';
|
|
69
72
|
import {
|
|
70
73
|
commandEncoderFinished,
|
|
71
74
|
queueSubmitFailed,
|
|
@@ -132,15 +135,6 @@ const BGL_KEYS = ['label', 'entries'] as const;
|
|
|
132
135
|
// BG_KEYS removed in w20 — createBindGroup now uses tagged-union dispatch
|
|
133
136
|
// over RhiBindingResource 4 kinds (not field-mirror passthrough).
|
|
134
137
|
const PL_KEYS = ['label', 'bindGroupLayouts'] as const;
|
|
135
|
-
const RPP_KEYS = [
|
|
136
|
-
'label',
|
|
137
|
-
'layout',
|
|
138
|
-
'vertex',
|
|
139
|
-
'primitive',
|
|
140
|
-
'depthStencil',
|
|
141
|
-
'multisample',
|
|
142
|
-
'fragment',
|
|
143
|
-
] as const;
|
|
144
138
|
const ENC_KEYS = ['label'] as const;
|
|
145
139
|
const TEXTURE_VIEW_KEYS = [
|
|
146
140
|
'label',
|
|
@@ -174,6 +168,7 @@ const QUERY_SET_COUNT_LIMIT = 4096;
|
|
|
174
168
|
*/
|
|
175
169
|
const RAW_DEVICE_MAP: WeakMap<RhiDevice, GPUDevice> = new WeakMap();
|
|
176
170
|
const BUFFER_RAW_MAP: WeakMap<Buffer, GPUBuffer> = new WeakMap();
|
|
171
|
+
const TEXTURE_VIEW_RAW_MAP: WeakMap<TextureView, GPUTextureView> = new WeakMap();
|
|
177
172
|
const ENCODER_STATE: WeakMap<RhiCommandEncoder, EncoderState> = new WeakMap();
|
|
178
173
|
const PASS_STATE: WeakMap<RhiRenderPassEncoder, PassState> = new WeakMap();
|
|
179
174
|
const COMMAND_BUFFER_RAW_MAP: WeakMap<CommandBuffer, GPUCommandBuffer> = new WeakMap();
|
|
@@ -271,7 +266,7 @@ const QUERY_RESOLVE_ALIGNMENT = 256;
|
|
|
271
266
|
* The `_internal_` prefix + `@internal` JSDoc tag mark this as engine-internal
|
|
272
267
|
* plumbing; the only sanctioned consumer is
|
|
273
268
|
* `apps/hello/triangle/src/main.ts:96` which threads the raw GPUDevice into
|
|
274
|
-
*
|
|
269
|
+
* the host's internal canvas-device configuration slot so the canvas
|
|
275
270
|
* `GPUCanvasContext.configure({device})` slot keeps working
|
|
276
271
|
* (GPUCanvasContext is outside the RHI surface). Every other engine path
|
|
277
272
|
* goes through the RHI interface.
|
|
@@ -510,12 +505,7 @@ function makeRenderPassEncoder(
|
|
|
510
505
|
rawPass.setViewport(x, y, w, h, minDepth, maxDepth);
|
|
511
506
|
},
|
|
512
507
|
setScissorRect(x: number, y: number, w: number, h: number): void {
|
|
513
|
-
|
|
514
|
-
rawPass as unknown as {
|
|
515
|
-
readonly setScissorRect?: (x: number, y: number, w: number, h: number) => void;
|
|
516
|
-
}
|
|
517
|
-
).setScissorRect;
|
|
518
|
-
setScissorRect?.call(rawPass, x, y, w, h);
|
|
508
|
+
rawPass.setScissorRect(x, y, w, h);
|
|
519
509
|
},
|
|
520
510
|
setBlendConstant(color: GPUColor): void {
|
|
521
511
|
rawPass.setBlendConstant(color);
|
|
@@ -692,6 +682,123 @@ function throwIfFinished(state: EncoderState | undefined): void {
|
|
|
692
682
|
}
|
|
693
683
|
}
|
|
694
684
|
|
|
685
|
+
function rawTextureView(view: TextureView): GPUTextureView {
|
|
686
|
+
return TEXTURE_VIEW_RAW_MAP.get(view) ?? (view as unknown as GPUTextureView);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function mirrorRenderPassDescriptor(desc: RenderPassDescriptor): GPURenderPassDescriptor {
|
|
690
|
+
const colorAttachments: Array<GPURenderPassColorAttachment | null> = [];
|
|
691
|
+
for (const attachment of desc.colorAttachments) {
|
|
692
|
+
if (attachment === null || attachment === undefined) {
|
|
693
|
+
colorAttachments.push(null);
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (attachment.loadOp === undefined || attachment.storeOp === undefined) {
|
|
697
|
+
throw new TypeError('RHI render-pass color attachments require loadOp and storeOp');
|
|
698
|
+
}
|
|
699
|
+
colorAttachments.push({
|
|
700
|
+
view: rawTextureView(attachment.view),
|
|
701
|
+
...(attachment.depthSlice === undefined ? {} : { depthSlice: attachment.depthSlice }),
|
|
702
|
+
...(attachment.resolveTarget === undefined
|
|
703
|
+
? {}
|
|
704
|
+
: { resolveTarget: rawTextureView(attachment.resolveTarget) }),
|
|
705
|
+
...(attachment.clearValue === undefined ? {} : { clearValue: attachment.clearValue }),
|
|
706
|
+
loadOp: attachment.loadOp,
|
|
707
|
+
storeOp: attachment.storeOp,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return {
|
|
712
|
+
...(desc.label === undefined ? {} : { label: desc.label }),
|
|
713
|
+
colorAttachments,
|
|
714
|
+
...(desc.depthStencilAttachment === undefined
|
|
715
|
+
? {}
|
|
716
|
+
: {
|
|
717
|
+
depthStencilAttachment: {
|
|
718
|
+
view: rawTextureView(desc.depthStencilAttachment.view),
|
|
719
|
+
...(desc.depthStencilAttachment.depthClearValue === undefined
|
|
720
|
+
? {}
|
|
721
|
+
: { depthClearValue: desc.depthStencilAttachment.depthClearValue }),
|
|
722
|
+
...(desc.depthStencilAttachment.depthLoadOp === undefined
|
|
723
|
+
? {}
|
|
724
|
+
: { depthLoadOp: desc.depthStencilAttachment.depthLoadOp }),
|
|
725
|
+
...(desc.depthStencilAttachment.depthStoreOp === undefined
|
|
726
|
+
? {}
|
|
727
|
+
: { depthStoreOp: desc.depthStencilAttachment.depthStoreOp }),
|
|
728
|
+
...(desc.depthStencilAttachment.depthReadOnly === undefined
|
|
729
|
+
? {}
|
|
730
|
+
: { depthReadOnly: desc.depthStencilAttachment.depthReadOnly }),
|
|
731
|
+
...(desc.depthStencilAttachment.stencilClearValue === undefined
|
|
732
|
+
? {}
|
|
733
|
+
: { stencilClearValue: desc.depthStencilAttachment.stencilClearValue }),
|
|
734
|
+
...(desc.depthStencilAttachment.stencilLoadOp === undefined
|
|
735
|
+
? {}
|
|
736
|
+
: { stencilLoadOp: desc.depthStencilAttachment.stencilLoadOp }),
|
|
737
|
+
...(desc.depthStencilAttachment.stencilStoreOp === undefined
|
|
738
|
+
? {}
|
|
739
|
+
: { stencilStoreOp: desc.depthStencilAttachment.stencilStoreOp }),
|
|
740
|
+
...(desc.depthStencilAttachment.stencilReadOnly === undefined
|
|
741
|
+
? {}
|
|
742
|
+
: { stencilReadOnly: desc.depthStencilAttachment.stencilReadOnly }),
|
|
743
|
+
},
|
|
744
|
+
}),
|
|
745
|
+
...(desc.occlusionQuerySet === undefined
|
|
746
|
+
? {}
|
|
747
|
+
: {
|
|
748
|
+
occlusionQuerySet:
|
|
749
|
+
QUERY_SET_RAW_MAP.get(desc.occlusionQuerySet) ??
|
|
750
|
+
(desc.occlusionQuerySet as unknown as GPUQuerySet),
|
|
751
|
+
}),
|
|
752
|
+
...(desc.timestampWrites === undefined
|
|
753
|
+
? {}
|
|
754
|
+
: {
|
|
755
|
+
timestampWrites: {
|
|
756
|
+
querySet:
|
|
757
|
+
QUERY_SET_RAW_MAP.get(desc.timestampWrites.querySet) ??
|
|
758
|
+
(desc.timestampWrites.querySet as unknown as GPUQuerySet),
|
|
759
|
+
...(desc.timestampWrites.beginningOfPassWriteIndex === undefined
|
|
760
|
+
? {}
|
|
761
|
+
: { beginningOfPassWriteIndex: desc.timestampWrites.beginningOfPassWriteIndex }),
|
|
762
|
+
...(desc.timestampWrites.endOfPassWriteIndex === undefined
|
|
763
|
+
? {}
|
|
764
|
+
: { endOfPassWriteIndex: desc.timestampWrites.endOfPassWriteIndex }),
|
|
765
|
+
},
|
|
766
|
+
}),
|
|
767
|
+
...(desc.maxDrawCount === undefined ? {} : { maxDrawCount: desc.maxDrawCount }),
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function mirrorRenderPipelineDescriptor(
|
|
772
|
+
desc: RenderPipelineDescriptor,
|
|
773
|
+
): GPURenderPipelineDescriptor {
|
|
774
|
+
const vertex: GPUVertexState = {
|
|
775
|
+
module: desc.vertex.module as unknown as GPUShaderModule,
|
|
776
|
+
...(desc.vertex.buffers === undefined ? {} : { buffers: Array.from(desc.vertex.buffers) }),
|
|
777
|
+
...(desc.vertex.entryPoint === undefined ? {} : { entryPoint: desc.vertex.entryPoint }),
|
|
778
|
+
...(desc.vertex.constants === undefined ? {} : { constants: desc.vertex.constants }),
|
|
779
|
+
};
|
|
780
|
+
const fragment: GPUFragmentState | undefined =
|
|
781
|
+
desc.fragment === undefined
|
|
782
|
+
? undefined
|
|
783
|
+
: {
|
|
784
|
+
module: desc.fragment.module as unknown as GPUShaderModule,
|
|
785
|
+
targets: Array.from(desc.fragment.targets),
|
|
786
|
+
...(desc.fragment.entryPoint === undefined
|
|
787
|
+
? {}
|
|
788
|
+
: { entryPoint: desc.fragment.entryPoint }),
|
|
789
|
+
...(desc.fragment.constants === undefined ? {} : { constants: desc.fragment.constants }),
|
|
790
|
+
};
|
|
791
|
+
return {
|
|
792
|
+
...(desc.label === undefined ? {} : { label: desc.label }),
|
|
793
|
+
layout: desc.layout === 'auto' ? 'auto' : (desc.layout as unknown as GPUPipelineLayout),
|
|
794
|
+
vertex,
|
|
795
|
+
...(desc.primitive === undefined ? {} : { primitive: desc.primitive }),
|
|
796
|
+
...(desc.depthStencil === undefined ? {} : { depthStencil: desc.depthStencil }),
|
|
797
|
+
...(desc.multisample === undefined ? {} : { multisample: desc.multisample }),
|
|
798
|
+
...(fragment === undefined ? {} : { fragment }),
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
695
802
|
/**
|
|
696
803
|
* Build a RhiCommandEncoder around a raw GPUCommandEncoder (w3).
|
|
697
804
|
*
|
|
@@ -736,17 +843,16 @@ function makeCommandEncoder(
|
|
|
736
843
|
}
|
|
737
844
|
|
|
738
845
|
const enc: RhiCommandEncoder = {
|
|
739
|
-
beginRenderPass(desc:
|
|
846
|
+
beginRenderPass(desc: RenderPassDescriptor): RhiRenderPassEncoder {
|
|
740
847
|
const state = ENCODER_STATE.get(enc);
|
|
741
848
|
throwIfFinished(state);
|
|
742
|
-
const rawPass = rawEncoder.beginRenderPass(desc);
|
|
849
|
+
const rawPass = rawEncoder.beginRenderPass(mirrorRenderPassDescriptor(desc));
|
|
743
850
|
// Extract occlusionQuerySet from the descriptor (research §2.2:
|
|
744
851
|
// RPDesc.occlusionQuerySet is the injection point; the render pass
|
|
745
852
|
// PassState.[[occlusion_query_set]] mirrors it). The forgeax
|
|
746
853
|
// RenderPassDescriptor declares occlusionQuerySet as `QuerySet |
|
|
747
854
|
// undefined`; the raw GPU descriptor is structurally compatible.
|
|
748
|
-
const
|
|
749
|
-
const occlusionQuerySet = occQs === undefined || occQs === null ? null : (occQs as QuerySet);
|
|
855
|
+
const occlusionQuerySet = desc.occlusionQuerySet ?? null;
|
|
750
856
|
const pass = makeRenderPassEncoder(rawPass, enc, occlusionQuerySet);
|
|
751
857
|
if (state !== undefined) state.activePass = pass;
|
|
752
858
|
return pass;
|
|
@@ -1214,7 +1320,6 @@ function makeQueue(rawQueue: GPUQueue): RhiQueue {
|
|
|
1214
1320
|
size?: number | undefined,
|
|
1215
1321
|
): Result<void, RhiError> {
|
|
1216
1322
|
const rawBuffer = BUFFER_RAW_MAP.get(buffer) ?? (buffer as unknown as GPUBuffer);
|
|
1217
|
-
const range = resolveBufferWriteRange(data, dataOffset, size);
|
|
1218
1323
|
const bufferSize =
|
|
1219
1324
|
typeof (rawBuffer as { size?: number }).size === 'number'
|
|
1220
1325
|
? (rawBuffer as { size: number }).size
|
|
@@ -1223,13 +1328,17 @@ function makeQueue(rawQueue: GPUQueue): RhiQueue {
|
|
|
1223
1328
|
if (bufferOffset % 4 !== 0) {
|
|
1224
1329
|
return queueWriteBufferOutOfBounds({
|
|
1225
1330
|
offset: bufferOffset,
|
|
1226
|
-
byteLength:
|
|
1331
|
+
byteLength:
|
|
1332
|
+
data instanceof ArrayBuffer ? data.byteLength : (data as ArrayBufferView).byteLength,
|
|
1227
1333
|
bufferSize,
|
|
1228
1334
|
});
|
|
1229
1335
|
}
|
|
1230
|
-
const
|
|
1336
|
+
const dataByteLength =
|
|
1337
|
+
data instanceof ArrayBuffer ? data.byteLength : (data as ArrayBufferView).byteLength;
|
|
1338
|
+
const writeStart = dataOffset ?? 0;
|
|
1339
|
+
const writeSize = size ?? dataByteLength - writeStart;
|
|
1231
1340
|
// Bounds validation: bufferOffset + writeSize <= bufferSize.
|
|
1232
|
-
if (
|
|
1341
|
+
if (bufferOffset + writeSize > bufferSize) {
|
|
1233
1342
|
return queueWriteBufferOutOfBounds({
|
|
1234
1343
|
offset: bufferOffset,
|
|
1235
1344
|
byteLength: writeSize,
|
|
@@ -1242,15 +1351,15 @@ function makeQueue(rawQueue: GPUQueue): RhiQueue {
|
|
|
1242
1351
|
rawBuffer,
|
|
1243
1352
|
bufferOffset,
|
|
1244
1353
|
data as GPUAllowSharedBufferSource,
|
|
1245
|
-
|
|
1246
|
-
|
|
1354
|
+
writeStart,
|
|
1355
|
+
size,
|
|
1247
1356
|
);
|
|
1248
1357
|
} else if (dataOffset !== undefined) {
|
|
1249
1358
|
rawQueue.writeBuffer(
|
|
1250
1359
|
rawBuffer,
|
|
1251
1360
|
bufferOffset,
|
|
1252
1361
|
data as GPUAllowSharedBufferSource,
|
|
1253
|
-
|
|
1362
|
+
writeStart,
|
|
1254
1363
|
);
|
|
1255
1364
|
} else {
|
|
1256
1365
|
rawQueue.writeBuffer(rawBuffer, bufferOffset, data as GPUAllowSharedBufferSource);
|
|
@@ -1289,7 +1398,7 @@ function makeQueue(rawQueue: GPUQueue): RhiQueue {
|
|
|
1289
1398
|
}
|
|
1290
1399
|
},
|
|
1291
1400
|
writeTexture(
|
|
1292
|
-
destination:
|
|
1401
|
+
destination: TextureWriteDestination,
|
|
1293
1402
|
data: ArrayBufferView | ArrayBuffer,
|
|
1294
1403
|
dataLayout: GPUTexelCopyBufferLayout,
|
|
1295
1404
|
size: GPUExtent3DStrict,
|
|
@@ -1309,8 +1418,14 @@ function makeQueue(rawQueue: GPUQueue): RhiQueue {
|
|
|
1309
1418
|
// structural compatibility lets us forward verbatim with a cast.
|
|
1310
1419
|
// The data + size casts cover @webgpu/types polymorphism that the
|
|
1311
1420
|
// forgeax form normalises to the strict spec subset.
|
|
1421
|
+
const rawDestination: GPUTexelCopyTextureInfo = {
|
|
1422
|
+
texture: destination.texture as unknown as GPUTexture,
|
|
1423
|
+
};
|
|
1424
|
+
if (destination.mipLevel !== undefined) rawDestination.mipLevel = destination.mipLevel;
|
|
1425
|
+
if (destination.origin !== undefined) rawDestination.origin = destination.origin;
|
|
1426
|
+
if (destination.aspect !== undefined) rawDestination.aspect = destination.aspect;
|
|
1312
1427
|
rawQueue.writeTexture(
|
|
1313
|
-
|
|
1428
|
+
rawDestination,
|
|
1314
1429
|
data as unknown as GPUAllowSharedBufferSource,
|
|
1315
1430
|
dataLayout,
|
|
1316
1431
|
size as unknown as GPUExtent3D,
|
|
@@ -1329,11 +1444,24 @@ function makeQueue(rawQueue: GPUQueue): RhiQueue {
|
|
|
1329
1444
|
},
|
|
1330
1445
|
copyExternalImageToTexture(
|
|
1331
1446
|
source: GPUCopyExternalImageSourceInfo,
|
|
1332
|
-
destination:
|
|
1447
|
+
destination: ExternalImageTextureDestination,
|
|
1333
1448
|
copySize: GPUExtent3DStrict,
|
|
1334
1449
|
): Result<void, RhiError> {
|
|
1335
1450
|
try {
|
|
1336
|
-
rawQueue.copyExternalImageToTexture(
|
|
1451
|
+
rawQueue.copyExternalImageToTexture(
|
|
1452
|
+
source,
|
|
1453
|
+
{
|
|
1454
|
+
texture: destination.texture as unknown as GPUTexture,
|
|
1455
|
+
...(destination.mipLevel === undefined ? {} : { mipLevel: destination.mipLevel }),
|
|
1456
|
+
...(destination.origin === undefined ? {} : { origin: destination.origin }),
|
|
1457
|
+
...(destination.aspect === undefined ? {} : { aspect: destination.aspect }),
|
|
1458
|
+
...(destination.colorSpace === undefined ? {} : { colorSpace: destination.colorSpace }),
|
|
1459
|
+
...(destination.premultipliedAlpha === undefined
|
|
1460
|
+
? {}
|
|
1461
|
+
: { premultipliedAlpha: destination.premultipliedAlpha }),
|
|
1462
|
+
},
|
|
1463
|
+
copySize,
|
|
1464
|
+
);
|
|
1337
1465
|
return ok(undefined);
|
|
1338
1466
|
} catch (e) {
|
|
1339
1467
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -1538,7 +1666,9 @@ export function makeRhiDevice(rawDevice: GPUDevice): {
|
|
|
1538
1666
|
const rawView = rawTexture.createView(
|
|
1539
1667
|
mirror(desc, TEXTURE_VIEW_KEYS) as unknown as GPUTextureViewDescriptor,
|
|
1540
1668
|
);
|
|
1541
|
-
|
|
1669
|
+
const handle = rawView as unknown as TextureView;
|
|
1670
|
+
TEXTURE_VIEW_RAW_MAP.set(handle, rawView);
|
|
1671
|
+
return ok(handle);
|
|
1542
1672
|
} catch (e) {
|
|
1543
1673
|
const message = e instanceof Error ? e.message : String(e);
|
|
1544
1674
|
return err(
|
|
@@ -1638,9 +1768,7 @@ export function makeRhiDevice(rawDevice: GPUDevice): {
|
|
|
1638
1768
|
},
|
|
1639
1769
|
createRenderPipeline(desc: RenderPipelineDescriptor): Result<RenderPipeline, RhiError> {
|
|
1640
1770
|
try {
|
|
1641
|
-
const out = rawDevice.createRenderPipeline(
|
|
1642
|
-
mirror(desc, RPP_KEYS) as unknown as GPURenderPipelineDescriptor,
|
|
1643
|
-
);
|
|
1771
|
+
const out = rawDevice.createRenderPipeline(mirrorRenderPipelineDescriptor(desc));
|
|
1644
1772
|
return ok(out as unknown as RenderPipeline);
|
|
1645
1773
|
} catch (e) {
|
|
1646
1774
|
const message = e instanceof Error ? e.message : String(e);
|