@forgeax/engine-rhi-null 0.1.2

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.
@@ -0,0 +1,22 @@
1
+ import type { CanvasConfiguration, Result, RhiCanvasContext, RhiError as RhiErrorType, Texture } from '@forgeax/engine-rhi';
2
+ /**
3
+ * Headless canvas context. configure succeeds vacuously; getCurrentTexture
4
+ * mints a legal opaque-handle Texture brand so the swap-chain acquisition path
5
+ * never blocks frame rendering in headless CI.
6
+ */
7
+ export declare class RhiNullCanvasContext implements RhiCanvasContext {
8
+ configure(_desc: CanvasConfiguration): Result<void, RhiErrorType>;
9
+ unconfigure(): void;
10
+ getConfiguration(): CanvasConfiguration | undefined;
11
+ getCurrentTexture(): Result<Texture, RhiErrorType>;
12
+ }
13
+ /**
14
+ * Acquire a canvas context. The headless backend returns ok(context) so the
15
+ * createRenderer context-acquisition step succeeds. The context's
16
+ * getCurrentTexture mints a legal Texture brand once a bookkeeper is attached.
17
+ *
18
+ * R-1: this is mounted on the rhi singleton so Channel 1's facade never
19
+ * crashes on a missing acquireCanvasContext.
20
+ */
21
+ export declare function acquireCanvasContext(_canvas?: HTMLCanvasElement | OffscreenCanvas | null | undefined): Result<RhiCanvasContext, RhiErrorType>;
22
+ //# sourceMappingURL=canvas-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas-context.d.ts","sourceRoot":"","sources":["../src/canvas-context.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EACV,mBAAmB,EACnB,MAAM,EACN,gBAAgB,EAChB,QAAQ,IAAI,YAAY,EACxB,OAAO,EACR,MAAM,qBAAqB,CAAC;AAG7B;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,gBAAgB;IAC3D,SAAS,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;IAIjE,WAAW,IAAI,IAAI;IAEnB,gBAAgB,IAAI,mBAAmB,GAAG,SAAS;IAInD,iBAAiB,IAAI,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC;CAMnD;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,CAAC,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI,GAAG,SAAS,GAC/D,MAAM,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAExC"}
@@ -0,0 +1,27 @@
1
+ import type { Buffer, CommandBuffer, ComputePassDescriptor, QuerySet, Result, RhiCommandEncoder, RhiComputePassEncoder, RhiError as RhiErrorType, RhiRenderPassEncoder } from '@forgeax/engine-rhi';
2
+ import type { Bookkeeper } from './bookkeeping';
3
+ import type { RhiNullDevice } from './device';
4
+ /**
5
+ * Headless command encoder. begin*Pass returns a no-op pass encoder bound to
6
+ * the issuing device's ledger; recording methods are no-ops; finish mints a
7
+ * CommandBuffer brand.
8
+ */
9
+ export declare class RhiNullCommandEncoder implements RhiCommandEncoder {
10
+ private readonly bookkeeper;
11
+ private readonly counter;
12
+ constructor(bookkeeper: Bookkeeper, device: RhiNullDevice);
13
+ beginRenderPass(desc: GPURenderPassDescriptor): RhiRenderPassEncoder;
14
+ beginComputePass(desc?: ComputePassDescriptor | undefined): RhiComputePassEncoder;
15
+ copyBufferToBuffer(_source: Buffer, _sourceOffsetOrDestination: number | Buffer, _destinationOrSize?: Buffer | number | undefined, _destinationOffset?: number | undefined, _size?: number | undefined): void;
16
+ copyBufferToTexture(_source: GPUTexelCopyBufferInfo, _destination: GPUTexelCopyTextureInfo, _copySize: GPUExtent3DStrict): void;
17
+ copyTextureToBuffer(_source: GPUTexelCopyTextureInfo, _destination: GPUTexelCopyBufferInfo, _copySize: GPUExtent3DStrict): void;
18
+ copyTextureToTexture(_source: GPUTexelCopyTextureInfo, _destination: GPUTexelCopyTextureInfo, _copySize: GPUExtent3DStrict): void;
19
+ clearBuffer(_buffer: Buffer, _offset?: number | undefined, _size?: number | undefined): void;
20
+ resolveQuerySet(_querySet: QuerySet, _firstQuery: number, _queryCount: number, _destination: Buffer, _destinationOffset: number): Result<void, RhiErrorType>;
21
+ writeTimestamp(_querySet: QuerySet, _queryIndex: number): void;
22
+ pushDebugGroup(_groupLabel: string): void;
23
+ popDebugGroup(): void;
24
+ insertDebugMarker(_markerLabel: string): void;
25
+ finish(): Result<CommandBuffer, RhiErrorType>;
26
+ }
27
+ //# sourceMappingURL=command-encoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-encoder.d.ts","sourceRoot":"","sources":["../src/command-encoder.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,MAAM,EACN,aAAa,EACb,qBAAqB,EACrB,QAAQ,EACR,MAAM,EACN,iBAAiB,EACjB,qBAAqB,EACrB,QAAQ,IAAI,YAAY,EACxB,oBAAoB,EACrB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA2C9C;;;;GAIG;AACH,qBAAa,qBAAsB,YAAW,iBAAiB;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;gBAE1B,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa;IAKzD,eAAe,CAAC,IAAI,EAAE,uBAAuB,GAAG,oBAAoB;IAKpE,gBAAgB,CAAC,IAAI,CAAC,EAAE,qBAAqB,GAAG,SAAS,GAAG,qBAAqB;IAKjF,kBAAkB,CAChB,OAAO,EAAE,MAAM,EACf,0BAA0B,EAAE,MAAM,GAAG,MAAM,EAC3C,kBAAkB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,EAChD,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,EACvC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GACzB,IAAI;IAEP,mBAAmB,CACjB,OAAO,EAAE,sBAAsB,EAC/B,YAAY,EAAE,uBAAuB,EACrC,SAAS,EAAE,iBAAiB,GAC3B,IAAI;IAEP,mBAAmB,CACjB,OAAO,EAAE,uBAAuB,EAChC,YAAY,EAAE,sBAAsB,EACpC,SAAS,EAAE,iBAAiB,GAC3B,IAAI;IAEP,oBAAoB,CAClB,OAAO,EAAE,uBAAuB,EAChC,YAAY,EAAE,uBAAuB,EACrC,SAAS,EAAE,iBAAiB,GAC3B,IAAI;IAEP,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAE5F,eAAe,CACb,SAAS,EAAE,QAAQ,EACnB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,MAAM,GACzB,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;IAI7B,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAE9D,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAEzC,aAAa,IAAI,IAAI;IAErB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAE7C,MAAM,IAAI,MAAM,CAAC,aAAa,EAAE,YAAY,CAAC;CAG9C"}
@@ -0,0 +1,66 @@
1
+ import type { BindGroup, BindGroupDescriptor, BindGroupLayout, BindGroupLayoutDescriptor, Buffer, BufferDescriptor, CommandEncoderDescriptor, ComputePipeline, ComputePipelineDescriptor, PipelineLayout, PipelineLayoutDescriptor, QuerySet, QuerySetDescriptor, RenderPipeline, RenderPipelineDescriptor, Result, RhiCaps, RhiCommandEncoder, RhiDevice, RhiError as RhiErrorType, RhiFeatures, RhiLimits, RhiQueue, Sampler, SamplerDescriptor, Texture, TextureDescriptor, TextureView, TextureViewDescriptor } from '@forgeax/engine-rhi';
2
+ import { Bookkeeper } from './bookkeeping';
3
+ type RhiNullDeviceLost = Awaited<RhiDevice['lost']>;
4
+ /**
5
+ * Factory that builds a command encoder bound to a device's ledger. Injected at
6
+ * device construction (rather than imported here) so device.ts carries no
7
+ * dependency on command-encoder.ts; the singleton assembly (index.ts) supplies
8
+ * the real factory. The Bookkeeper and RhiNullDevice are passed so the encoder
9
+ * threads draw / dispatch counts + binding validation through the same per-device
10
+ * ledger AND writes aggregated frame stats to the device for M3 unit-test readback.
11
+ */
12
+ export type CommandEncoderFactory = (bookkeeper: Bookkeeper, device: RhiNullDevice) => RhiCommandEncoder;
13
+ /**
14
+ * Headless no-op RhiDevice. Every create* mints a legal brand and records it;
15
+ * every destroy* fail-fasts a double-destroy; caps reports the all-true-except-
16
+ * reserved profile (D-5).
17
+ */
18
+ export declare class RhiNullDevice implements RhiDevice {
19
+ private readonly internalBookkeeper;
20
+ private readonly nullQueue;
21
+ private readonly encoderFactory;
22
+ /** Per-frame total draw count across all pass encoders executed this frame
23
+ * (aggregated by the command encoder on finish, then reset). M3 unit tests
24
+ * (w17) read this to assert draw count >= 1 (AC-06). */
25
+ totalDrawCount: number;
26
+ /** Per-frame total direct and indirect compute dispatch count. */
27
+ totalDispatchCount: number;
28
+ /** Per-frame total bind group set count (AC-06 / AC-05 readback). */
29
+ totalBindGroupCount: number;
30
+ /** Per-frame pass names executed this frame, in schedule order (AC-04). */
31
+ framePassNames: string[];
32
+ /** The per-device handle ledger — exposed so M3 tests can assert create/destroy
33
+ * pairing and BGL/PSO shape counts (AC-05/06/07). */
34
+ get bookkeeper(): Bookkeeper;
35
+ constructor(queue: RhiQueue, encoderFactory: CommandEncoderFactory);
36
+ get caps(): RhiCaps;
37
+ get features(): RhiFeatures;
38
+ get limits(): RhiLimits;
39
+ get queue(): RhiQueue;
40
+ get lost(): Promise<RhiNullDeviceLost>;
41
+ createBuffer(_desc: BufferDescriptor): Result<Buffer, RhiErrorType>;
42
+ createTexture(_desc: TextureDescriptor): Result<Texture, RhiErrorType>;
43
+ destroyBuffer(buf: Buffer): Result<void, RhiErrorType>;
44
+ destroyQuerySet(querySet: QuerySet): Result<void, RhiErrorType>;
45
+ destroyTexture(tex: Texture): Result<void, RhiErrorType>;
46
+ createTextureView(_texture: Texture, _desc: TextureViewDescriptor): Result<TextureView, RhiErrorType>;
47
+ createSampler(_desc?: SamplerDescriptor | undefined): Result<Sampler, RhiErrorType>;
48
+ createBindGroupLayout(_desc: BindGroupLayoutDescriptor): Result<BindGroupLayout, RhiErrorType>;
49
+ createBindGroup(_desc: BindGroupDescriptor): Result<BindGroup, RhiErrorType>;
50
+ createPipelineLayout(_desc: PipelineLayoutDescriptor): Result<PipelineLayout, RhiErrorType>;
51
+ createRenderPipeline(_desc: RenderPipelineDescriptor): Result<RenderPipeline, RhiErrorType>;
52
+ createComputePipeline(_desc: ComputePipelineDescriptor): Result<ComputePipeline, RhiErrorType>;
53
+ createQuerySet(desc: QuerySetDescriptor): Result<QuerySet, RhiErrorType>;
54
+ createCommandEncoder(_desc?: CommandEncoderDescriptor | undefined): Result<RhiCommandEncoder, RhiErrorType>;
55
+ /**
56
+ * Mint a pipeline handle whose object also carries the no-op
57
+ * `getBindGroupLayout(index)` ops method (D-2). The prod auto-layout path
58
+ * (debug-draw.ts) and the existing mock unit tests both call
59
+ * `pipeline.getBindGroupLayout(n)`; returning a legal BindGroupLayout brand
60
+ * (recorded in the ledger) keeps those consumers from crashing on a missing
61
+ * method.
62
+ */
63
+ private makePipeline;
64
+ }
65
+ export {};
66
+ //# sourceMappingURL=device.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { RhiInstance } from '@forgeax/engine-rhi';
2
+ import { acquireCanvasContext } from './canvas-context';
3
+ import { createShaderModule } from './shader';
4
+ /**
5
+ * The `rhi` singleton — RhiBackendPack-shaped entry for Channel 1 injection
6
+ * (`createRenderer(canvas, { rhi })`). Carries acquireCanvasContext (R-1) so
7
+ * the facade never crashes on a missing method, and createShaderModule (R-2) so
8
+ * the ready chain's shader step resolves rather than rejecting.
9
+ */
10
+ export declare const rhi: RhiInstance & {
11
+ acquireCanvasContext: typeof acquireCanvasContext;
12
+ createShaderModule: typeof createShaderModule;
13
+ };
14
+ export { RhiNullAdapter } from './adapter';
15
+ export { acquireCanvasContext, RhiNullCanvasContext } from './canvas-context';
16
+ export { RhiNullCommandEncoder } from './command-encoder';
17
+ export { RhiNullDevice } from './device';
18
+ export { RhiNullComputePassEncoder, RhiNullRenderPassEncoder } from './pass-encoders';
19
+ export { RhiNullQueue } from './queue';
20
+ export { createShaderModule } from './shader';
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAKV,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAc9C;;;;;GAKG;AACH,eAAO,MAAM,GAAG,EAAE,WAAW,GAAG;IAC9B,oBAAoB,EAAE,OAAO,oBAAoB,CAAC;IAClD,kBAAkB,EAAE,OAAO,kBAAkB,CAAC;CAK/C,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.mjs ADDED
@@ -0,0 +1,470 @@
1
+ import { ok, err } from '@forgeax/engine-types';
2
+ import { RhiError } from '@forgeax/engine-rhi';
3
+
4
+ // src/index.ts
5
+ var RhiNullRenderPassEncoder = class {
6
+ /** Number of draw* calls issued on this pass (AC-06 readback). */
7
+ drawCount = 0;
8
+ bindGroupCount = 0;
9
+ /** Most recent setVertexBuffer / setBindGroup ownership validation; ok unless
10
+ * a foreign handle was passed (AC-09 readback). */
11
+ lastValidation = ok(void 0);
12
+ bookkeeper;
13
+ counter;
14
+ passName;
15
+ constructor(bookkeeper, counter, passName) {
16
+ this.bookkeeper = bookkeeper;
17
+ this.counter = counter;
18
+ this.passName = passName;
19
+ }
20
+ setPipeline(_pipeline) {
21
+ }
22
+ setVertexBuffer(_slot, buffer, _offset, _size) {
23
+ this.lastValidation = this.bookkeeper.validateOwnership(buffer);
24
+ }
25
+ setIndexBuffer(_buffer, _format, _offset, _size) {
26
+ }
27
+ setBindGroup(_index, bindGroup, _dynamicOffsetsData, _dynamicOffsetsDataStart, _dynamicOffsetsDataLength) {
28
+ this.bindGroupCount++;
29
+ this.counter?.recordBindGroup();
30
+ this.lastValidation = this.bookkeeper.validateOwnership(bindGroup);
31
+ }
32
+ draw(_vertexCount, _instanceCount, _firstVertex, _firstInstance) {
33
+ this.drawCount++;
34
+ this.counter?.recordDraw();
35
+ }
36
+ drawIndexed(_indexCount, _instanceCount, _firstIndex, _baseVertex, _firstInstance) {
37
+ this.drawCount++;
38
+ this.counter?.recordDraw();
39
+ }
40
+ end() {
41
+ this.counter?.recordPassName(this.passName);
42
+ }
43
+ setViewport(_x, _y, _w, _h, _minDepth, _maxDepth) {
44
+ }
45
+ setScissorRect(_x, _y, _w, _h) {
46
+ }
47
+ setBlendConstant(_color) {
48
+ }
49
+ setStencilReference(_reference) {
50
+ }
51
+ drawIndirect(_indirectBuffer, _indirectOffset) {
52
+ this.drawCount++;
53
+ this.counter?.recordDraw();
54
+ }
55
+ drawIndexedIndirect(_indirectBuffer, _indirectOffset) {
56
+ this.drawCount++;
57
+ this.counter?.recordDraw();
58
+ }
59
+ pushDebugGroup(_groupLabel) {
60
+ }
61
+ popDebugGroup() {
62
+ }
63
+ insertDebugMarker(_markerLabel) {
64
+ }
65
+ executeBundles(_bundles) {
66
+ return ok(void 0);
67
+ }
68
+ beginOcclusionQuery(_queryIndex) {
69
+ return ok(void 0);
70
+ }
71
+ endOcclusionQuery() {
72
+ return ok(void 0);
73
+ }
74
+ };
75
+ var RhiNullComputePassEncoder = class {
76
+ /** Number of dispatchWorkgroups calls issued on this pass (readback). */
77
+ dispatchCount = 0;
78
+ /** Most recent setBindGroup ownership validation (AC-09 readback). */
79
+ lastValidation = ok(void 0);
80
+ bookkeeper;
81
+ counter;
82
+ passName;
83
+ constructor(bookkeeper, counter, passName) {
84
+ this.bookkeeper = bookkeeper;
85
+ this.counter = counter;
86
+ this.passName = passName;
87
+ }
88
+ setPipeline(_pipeline) {
89
+ }
90
+ setBindGroup(_index, bindGroup, _dynamicOffsets) {
91
+ this.lastValidation = this.bookkeeper.validateOwnership(bindGroup);
92
+ }
93
+ dispatchWorkgroups(_x, _y, _z) {
94
+ this.dispatchCount++;
95
+ this.counter?.recordDispatch();
96
+ }
97
+ dispatchWorkgroupsIndirect(indirectBuffer, _indirectOffset) {
98
+ this.lastValidation = this.bookkeeper.validateOwnership(indirectBuffer);
99
+ this.dispatchCount++;
100
+ this.counter?.recordDispatch();
101
+ }
102
+ end() {
103
+ this.counter?.recordPassName(this.passName);
104
+ }
105
+ };
106
+
107
+ // src/command-encoder.ts
108
+ var DeviceCounter = class {
109
+ constructor(device) {
110
+ this.device = device;
111
+ }
112
+ device;
113
+ recordDraw() {
114
+ this.device.totalDrawCount++;
115
+ }
116
+ recordDispatch() {
117
+ this.device.totalDispatchCount++;
118
+ }
119
+ recordBindGroup() {
120
+ this.device.totalBindGroupCount++;
121
+ }
122
+ recordPassName(name) {
123
+ this.device.framePassNames.push(name);
124
+ }
125
+ };
126
+ function readPassLabel(desc) {
127
+ if (desc && typeof desc.label === "string" && desc.label.length > 0) {
128
+ return desc.label;
129
+ }
130
+ return "<unnamed>";
131
+ }
132
+ var RhiNullCommandEncoder = class {
133
+ bookkeeper;
134
+ counter;
135
+ constructor(bookkeeper, device) {
136
+ this.bookkeeper = bookkeeper;
137
+ this.counter = new DeviceCounter(device);
138
+ }
139
+ beginRenderPass(desc) {
140
+ const label = readPassLabel(desc);
141
+ return new RhiNullRenderPassEncoder(this.bookkeeper, this.counter, label);
142
+ }
143
+ beginComputePass(desc) {
144
+ const label = readPassLabel(desc);
145
+ return new RhiNullComputePassEncoder(this.bookkeeper, this.counter, label);
146
+ }
147
+ copyBufferToBuffer(_source, _sourceOffsetOrDestination, _destinationOrSize, _destinationOffset, _size) {
148
+ }
149
+ copyBufferToTexture(_source, _destination, _copySize) {
150
+ }
151
+ copyTextureToBuffer(_source, _destination, _copySize) {
152
+ }
153
+ copyTextureToTexture(_source, _destination, _copySize) {
154
+ }
155
+ clearBuffer(_buffer, _offset, _size) {
156
+ }
157
+ resolveQuerySet(_querySet, _firstQuery, _queryCount, _destination, _destinationOffset) {
158
+ return ok(void 0);
159
+ }
160
+ writeTimestamp(_querySet, _queryIndex) {
161
+ }
162
+ pushDebugGroup(_groupLabel) {
163
+ }
164
+ popDebugGroup() {
165
+ }
166
+ insertDebugMarker(_markerLabel) {
167
+ }
168
+ finish() {
169
+ return ok(this.bookkeeper.register("CommandBuffer"));
170
+ }
171
+ };
172
+ var BOOKKEEPING_KEY = /* @__PURE__ */ Symbol("forgeax-rhi-null-bookkeeping");
173
+ var Bookkeeper = class {
174
+ deviceId;
175
+ nextHandleId = 0;
176
+ records = /* @__PURE__ */ new Map();
177
+ constructor(deviceId) {
178
+ this.deviceId = deviceId;
179
+ }
180
+ /**
181
+ * Register a freshly-minted handle of the given kind, returning a plain
182
+ * object that carries its ledger row. The caller casts the return value to
183
+ * the concrete brand (`as unknown as Buffer` etc.).
184
+ */
185
+ register(kind) {
186
+ const id = this.nextHandleId++;
187
+ const record = {
188
+ id,
189
+ kind,
190
+ destroyed: false,
191
+ sourceDeviceId: this.deviceId
192
+ };
193
+ this.records.set(id, record);
194
+ return { [BOOKKEEPING_KEY]: record };
195
+ }
196
+ /**
197
+ * Mark a handle destroyed. Fail-fasts on a second destroy
198
+ * ('destroy-after-destroy') or on a handle issued by a different device
199
+ * ('rhi-not-available'); otherwise flips the destroyed flag and returns ok.
200
+ */
201
+ destroy(handle) {
202
+ const ownership = this.validateOwnership(handle);
203
+ if (!ownership.ok) return ownership;
204
+ const destroyedCheck = isHandleDestroyed(handle);
205
+ if (!destroyedCheck.ok) return destroyedCheck;
206
+ ownership.value.destroyed = true;
207
+ return ok(void 0);
208
+ }
209
+ /**
210
+ * Report whether a handle has already been destroyed (true once destroy()
211
+ * has flipped its flag). Used by command-stream methods that must not
212
+ * consume a stale handle.
213
+ */
214
+ isDestroyed(handle) {
215
+ return readRecord(handle)?.destroyed === true;
216
+ }
217
+ /**
218
+ * Validate that a handle was issued by THIS device. Returns the ledger row on
219
+ * success so callers can mutate it (e.g. flip destroyed); returns
220
+ * 'rhi-not-available' for a foreign handle (AC-09 — no silent pass).
221
+ */
222
+ validateOwnership(handle) {
223
+ const record = readRecord(handle);
224
+ if (record === void 0 || record.sourceDeviceId !== this.deviceId) {
225
+ return err(
226
+ new RhiError({
227
+ code: "rhi-not-available",
228
+ expected: "handle was issued by this RhiNull device",
229
+ hint: "do not pass a handle created on a different RhiNull device into this device; create resources on the device they are used with"
230
+ })
231
+ );
232
+ }
233
+ return ok(record);
234
+ }
235
+ /**
236
+ * Return all ledger rows as a readonly array. M3 unit tests (w17) read this
237
+ * to assert create/destroy pairing, BGL/PSO shape counts, and resource
238
+ * lifecycle coverage (AC-05/06/07). Returns a snapshot of the current Map
239
+ * so callers can iterate without a stale reference after further mutations.
240
+ */
241
+ allRecords() {
242
+ return [...this.records.values()];
243
+ }
244
+ /** Report the total number of records in the ledger. */
245
+ recordCount() {
246
+ return this.records.size;
247
+ }
248
+ };
249
+ function readRecord(handle) {
250
+ if (handle === null || typeof handle !== "object") return void 0;
251
+ const tagged = handle;
252
+ return tagged[BOOKKEEPING_KEY];
253
+ }
254
+ function isHandleDestroyed(handle) {
255
+ if (readRecord(handle)?.destroyed === true) {
256
+ return err(
257
+ new RhiError({
258
+ code: "destroy-after-destroy",
259
+ expected: "GPU buffer/texture handle has not been destroyed yet",
260
+ hint: "object already destroyed; track lifecycle in caller or check isDestroyed before re-destroy"
261
+ })
262
+ );
263
+ }
264
+ return ok(void 0);
265
+ }
266
+
267
+ // src/device.ts
268
+ var nextDeviceId = 0;
269
+ var RhiNullDevice = class {
270
+ internalBookkeeper;
271
+ nullQueue;
272
+ encoderFactory;
273
+ /** Per-frame total draw count across all pass encoders executed this frame
274
+ * (aggregated by the command encoder on finish, then reset). M3 unit tests
275
+ * (w17) read this to assert draw count >= 1 (AC-06). */
276
+ totalDrawCount = 0;
277
+ /** Per-frame total direct and indirect compute dispatch count. */
278
+ totalDispatchCount = 0;
279
+ /** Per-frame total bind group set count (AC-06 / AC-05 readback). */
280
+ totalBindGroupCount = 0;
281
+ /** Per-frame pass names executed this frame, in schedule order (AC-04). */
282
+ framePassNames = [];
283
+ /** The per-device handle ledger — exposed so M3 tests can assert create/destroy
284
+ * pairing and BGL/PSO shape counts (AC-05/06/07). */
285
+ get bookkeeper() {
286
+ return this.internalBookkeeper;
287
+ }
288
+ constructor(queue, encoderFactory) {
289
+ this.internalBookkeeper = new Bookkeeper(nextDeviceId++);
290
+ this.nullQueue = queue;
291
+ this.encoderFactory = encoderFactory;
292
+ }
293
+ get caps() {
294
+ return {
295
+ backendKind: "null",
296
+ compute: true,
297
+ timestampQuery: false,
298
+ timestampPeriodNanoseconds: null,
299
+ indirectDrawing: true,
300
+ textureCompressionBc: false,
301
+ textureCompressionEtc2: false,
302
+ textureCompressionAstc: false,
303
+ // 3 wgpu-native-only reserved flags stay false on non-native backends
304
+ // (D-5); the headless backend is not a native runtime.
305
+ multiDrawIndirect: false,
306
+ pushConstants: false,
307
+ textureBindingArray: false,
308
+ samplerAliasing: true,
309
+ firstInstanceIndirect: true,
310
+ storageBuffer: true,
311
+ storageTexture: true,
312
+ rgba16floatRenderable: true,
313
+ rg11b10ufloatRenderable: true,
314
+ float32Filterable: true,
315
+ maxColorAttachments: 8
316
+ };
317
+ }
318
+ get features() {
319
+ return EMPTY_FEATURES;
320
+ }
321
+ get limits() {
322
+ return EMPTY_LIMITS;
323
+ }
324
+ get queue() {
325
+ return this.nullQueue;
326
+ }
327
+ // forgeax-async-whitelist: dom-native — spec `GPUDevice.lost` Promise
328
+ // passthrough. The headless backend never loses a device (no GPU), so the
329
+ // Promise stays unsettled for the lifetime of the device, mirroring a live
330
+ // device that never transitions to the lost state.
331
+ get lost() {
332
+ return NEVER;
333
+ }
334
+ createBuffer(_desc) {
335
+ return ok(this.internalBookkeeper.register("Buffer"));
336
+ }
337
+ createTexture(_desc) {
338
+ return ok(this.internalBookkeeper.register("Texture"));
339
+ }
340
+ destroyBuffer(buf) {
341
+ return this.internalBookkeeper.destroy(buf);
342
+ }
343
+ destroyQuerySet(querySet) {
344
+ return this.internalBookkeeper.destroy(querySet);
345
+ }
346
+ destroyTexture(tex) {
347
+ return this.internalBookkeeper.destroy(tex);
348
+ }
349
+ createTextureView(_texture, _desc) {
350
+ return ok(this.internalBookkeeper.register("TextureView"));
351
+ }
352
+ createSampler(_desc) {
353
+ return ok(this.internalBookkeeper.register("Sampler"));
354
+ }
355
+ createBindGroupLayout(_desc) {
356
+ return ok(this.internalBookkeeper.register("BindGroupLayout"));
357
+ }
358
+ createBindGroup(_desc) {
359
+ return ok(this.internalBookkeeper.register("BindGroup"));
360
+ }
361
+ createPipelineLayout(_desc) {
362
+ return ok(this.internalBookkeeper.register("PipelineLayout"));
363
+ }
364
+ createRenderPipeline(_desc) {
365
+ return ok(this.makePipeline("RenderPipeline"));
366
+ }
367
+ createComputePipeline(_desc) {
368
+ return ok(this.makePipeline("ComputePipeline"));
369
+ }
370
+ createQuerySet(desc) {
371
+ if (desc.type === "timestamp") {
372
+ return err(
373
+ new RhiError({
374
+ code: "feature-not-enabled",
375
+ expected: "caps.timestampQuery === true (timestamp-query feature)",
376
+ hint: "RhiNull is structural-only and cannot produce GPU timestamp ticks"
377
+ })
378
+ );
379
+ }
380
+ return ok(this.internalBookkeeper.register("QuerySet"));
381
+ }
382
+ createCommandEncoder(_desc) {
383
+ return ok(this.encoderFactory(this.internalBookkeeper, this));
384
+ }
385
+ /**
386
+ * Mint a pipeline handle whose object also carries the no-op
387
+ * `getBindGroupLayout(index)` ops method (D-2). The prod auto-layout path
388
+ * (debug-draw.ts) and the existing mock unit tests both call
389
+ * `pipeline.getBindGroupLayout(n)`; returning a legal BindGroupLayout brand
390
+ * (recorded in the ledger) keeps those consumers from crashing on a missing
391
+ * method.
392
+ */
393
+ makePipeline(kind) {
394
+ const handle = this.internalBookkeeper.register(kind);
395
+ const getBindGroupLayout = (_index) => this.internalBookkeeper.register("BindGroupLayout");
396
+ return Object.assign(handle, { getBindGroupLayout });
397
+ }
398
+ };
399
+ var EMPTY_FEATURES = /* @__PURE__ */ new Set();
400
+ var EMPTY_LIMITS = {};
401
+ var NEVER = new Promise(() => {
402
+ });
403
+ var RhiNullQueue = class {
404
+ writeBuffer(_buffer, _bufferOffset, _data, _dataOffset, _size) {
405
+ return ok(void 0);
406
+ }
407
+ writeTexture(_destination, _data, _dataLayout, _size) {
408
+ return ok(void 0);
409
+ }
410
+ copyExternalImageToTexture(_source, _destination, _copySize) {
411
+ return ok(void 0);
412
+ }
413
+ submit(_commandBuffers) {
414
+ return ok(void 0);
415
+ }
416
+ // forgeax-async-whitelist: dom-native — spec `GPUQueue.onSubmittedWorkDone`
417
+ // never rejects. The headless backend has no pending GPU work, so it resolves
418
+ // immediately (AC-12: read-back idioms must not hang).
419
+ onSubmittedWorkDone() {
420
+ return Promise.resolve(void 0);
421
+ }
422
+ };
423
+
424
+ // src/adapter.ts
425
+ var RhiNullAdapter = class {
426
+ features = /* @__PURE__ */ new Set();
427
+ limits = {};
428
+ // forgeax-async-whitelist is not needed: this returns Promise<Result<...>>
429
+ // per the spec contract; never rejects.
430
+ requestDevice(_opts) {
431
+ const device = new RhiNullDevice(
432
+ new RhiNullQueue(),
433
+ (bookkeeper, dev) => new RhiNullCommandEncoder(bookkeeper, dev)
434
+ );
435
+ return Promise.resolve(ok(device));
436
+ }
437
+ };
438
+ var RhiNullCanvasContext = class {
439
+ configure(_desc) {
440
+ return ok(void 0);
441
+ }
442
+ unconfigure() {
443
+ }
444
+ getConfiguration() {
445
+ return void 0;
446
+ }
447
+ getCurrentTexture() {
448
+ return ok({});
449
+ }
450
+ };
451
+ function acquireCanvasContext(_canvas) {
452
+ return ok(new RhiNullCanvasContext());
453
+ }
454
+ function createShaderModule(_device, _desc) {
455
+ return Promise.resolve(ok({}));
456
+ }
457
+
458
+ // src/index.ts
459
+ function requestAdapter(_opts, _compatibleSurface) {
460
+ return Promise.resolve(ok(new RhiNullAdapter()));
461
+ }
462
+ var rhi = {
463
+ requestAdapter,
464
+ acquireCanvasContext,
465
+ createShaderModule
466
+ };
467
+
468
+ export { RhiNullAdapter, RhiNullCanvasContext, RhiNullCommandEncoder, RhiNullComputePassEncoder, RhiNullDevice, RhiNullQueue, RhiNullRenderPassEncoder, acquireCanvasContext, createShaderModule, rhi };
469
+ //# sourceMappingURL=index.mjs.map
470
+ //# sourceMappingURL=index.mjs.map