@forgeax/engine-rhi 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.
Files changed (83) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +234 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/async-form.test-d.d.ts +2 -0
  5. package/dist/__tests__/async-form.test-d.d.ts.map +1 -0
  6. package/dist/__tests__/binding-resource.test-d.d.ts +2 -0
  7. package/dist/__tests__/binding-resource.test-d.d.ts.map +1 -0
  8. package/dist/__tests__/buffer-mapping.test-d.d.ts +2 -0
  9. package/dist/__tests__/buffer-mapping.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/canvasContext.test-d.d.ts +2 -0
  11. package/dist/__tests__/canvasContext.test-d.d.ts.map +1 -0
  12. package/dist/__tests__/caps-three-way.unit.test.d.ts +2 -0
  13. package/dist/__tests__/caps-three-way.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/caps.test-d.d.ts +2 -0
  15. package/dist/__tests__/caps.test-d.d.ts.map +1 -0
  16. package/dist/__tests__/command-encoder-spec-alignment.test-d.d.ts +2 -0
  17. package/dist/__tests__/command-encoder-spec-alignment.test-d.d.ts.map +1 -0
  18. package/dist/__tests__/createComputePipeline.test-d.d.ts +2 -0
  19. package/dist/__tests__/createComputePipeline.test-d.d.ts.map +1 -0
  20. package/dist/__tests__/createQuerySet.test-d.d.ts +2 -0
  21. package/dist/__tests__/createQuerySet.test-d.d.ts.map +1 -0
  22. package/dist/__tests__/createTextureView.test-d.d.ts +2 -0
  23. package/dist/__tests__/createTextureView.test-d.d.ts.map +1 -0
  24. package/dist/__tests__/descriptor-mirror.test-d.d.ts +2 -0
  25. package/dist/__tests__/descriptor-mirror.test-d.d.ts.map +1 -0
  26. package/dist/__tests__/draw-validation-errors.test.d.ts +2 -0
  27. package/dist/__tests__/draw-validation-errors.test.d.ts.map +1 -0
  28. package/dist/__tests__/errors-instancing.unit.test.d.ts +2 -0
  29. package/dist/__tests__/errors-instancing.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/errors.test-d.d.ts +2 -0
  31. package/dist/__tests__/errors.test-d.d.ts.map +1 -0
  32. package/dist/__tests__/limit-exceeded-detail.test-d.d.ts +2 -0
  33. package/dist/__tests__/limit-exceeded-detail.test-d.d.ts.map +1 -0
  34. package/dist/__tests__/mapped-buffer.test-d.d.ts +2 -0
  35. package/dist/__tests__/mapped-buffer.test-d.d.ts.map +1 -0
  36. package/dist/__tests__/opaque-handle.test-d.d.ts +2 -0
  37. package/dist/__tests__/opaque-handle.test-d.d.ts.map +1 -0
  38. package/dist/__tests__/owner-out-of-range-role.test.d.ts +2 -0
  39. package/dist/__tests__/owner-out-of-range-role.test.d.ts.map +1 -0
  40. package/dist/__tests__/queue-writes.test-d.d.ts +2 -0
  41. package/dist/__tests__/queue-writes.test-d.d.ts.map +1 -0
  42. package/dist/__tests__/queue-writes.unit.test.d.ts +2 -0
  43. package/dist/__tests__/queue-writes.unit.test.d.ts.map +1 -0
  44. package/dist/__tests__/render-pass-encoder-spec-alignment.test-d.d.ts +2 -0
  45. package/dist/__tests__/render-pass-encoder-spec-alignment.test-d.d.ts.map +1 -0
  46. package/dist/__tests__/requestAdapter.test-d.d.ts +2 -0
  47. package/dist/__tests__/requestAdapter.test-d.d.ts.map +1 -0
  48. package/dist/errors.d.ts +315 -0
  49. package/dist/errors.d.ts.map +1 -0
  50. package/dist/errors.mjs +56 -0
  51. package/dist/errors.mjs.map +1 -0
  52. package/dist/index.d.ts +1749 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.mjs +256 -0
  55. package/dist/index.mjs.map +1 -0
  56. package/dist/queue-writes.d.ts +20 -0
  57. package/dist/queue-writes.d.ts.map +1 -0
  58. package/package.json +61 -0
  59. package/src/__tests__/async-form.test-d.ts +40 -0
  60. package/src/__tests__/binding-resource.test-d.ts +66 -0
  61. package/src/__tests__/buffer-mapping.test-d.ts +84 -0
  62. package/src/__tests__/canvasContext.test-d.ts +106 -0
  63. package/src/__tests__/caps-three-way.unit.test.ts +144 -0
  64. package/src/__tests__/caps.test-d.ts +83 -0
  65. package/src/__tests__/command-encoder-spec-alignment.test-d.ts +117 -0
  66. package/src/__tests__/createComputePipeline.test-d.ts +149 -0
  67. package/src/__tests__/createQuerySet.test-d.ts +81 -0
  68. package/src/__tests__/createTextureView.test-d.ts +127 -0
  69. package/src/__tests__/descriptor-mirror.test-d.ts +267 -0
  70. package/src/__tests__/draw-validation-errors.test.ts +155 -0
  71. package/src/__tests__/errors-instancing.unit.test.ts +167 -0
  72. package/src/__tests__/errors.test-d.ts +199 -0
  73. package/src/__tests__/limit-exceeded-detail.test-d.ts +33 -0
  74. package/src/__tests__/mapped-buffer.test-d.ts +65 -0
  75. package/src/__tests__/opaque-handle.test-d.ts +121 -0
  76. package/src/__tests__/owner-out-of-range-role.test.ts +84 -0
  77. package/src/__tests__/queue-writes.test-d.ts +107 -0
  78. package/src/__tests__/queue-writes.unit.test.ts +30 -0
  79. package/src/__tests__/render-pass-encoder-spec-alignment.test-d.ts +202 -0
  80. package/src/__tests__/requestAdapter.test-d.ts +123 -0
  81. package/src/errors.ts +504 -0
  82. package/src/index.ts +2398 -0
  83. package/src/queue-writes.ts +62 -0
@@ -0,0 +1,1749 @@
1
+ import type { AddressMode, CompareFunction, FilterMode, TextureFormat } from '@forgeax/engine-types';
2
+ import { type Result, RhiError } from './errors';
3
+ declare const RhiBufferBrand: unique symbol;
4
+ declare const RhiMappedBufferBrand: unique symbol;
5
+ /**
6
+ * GPU buffer opaque handle (vertex / index / uniform / storage / indirect).
7
+ *
8
+ * Spec anchor: W3C WebGPU §4 Buffers / [@webgpu/types.GPUBuffer]; research
9
+ * §4.1 mapState 3-state enum + §4.2 mapAsync 8-item validation + §4.4 unmap
10
+ * detach semantics.
11
+ *
12
+ * The buffer mapping surface is added in feat-20260510-rhi-resource-creation
13
+ * M5 (K-1: raw GPUMapMode bitmask; K-2: alignment / mode-usage / detach
14
+ * faults all ride 'webgpu-runtime-error' with structured .expected / .hint);
15
+ * re-shaped in feat-20260511-rhi-spec-realign-aggressive M1 (D-P2 #6):
16
+ * - `mapAsync` resolves to `Result<MappedBuffer, RhiError>` (success branch
17
+ * carries a branded handle subsequently used for getMappedRange / unmap).
18
+ * - `getMappedRange` / `unmap` are methods on `MappedBuffer` (not Buffer);
19
+ * calling them on a plain Buffer is a TS2345 compile-time red.
20
+ *
21
+ * The forgeax form keeps the spec verb names but routes failures via Result
22
+ * (charter proposition 4 explicit failure):
23
+ * mapAsync(mode, offset?, size?): Promise<Result<MappedBuffer, RhiError>>
24
+ * readonly mapState: 'unmapped' | 'pending' | 'mapped' - getter (research
25
+ * §4.1; same closed union as GPUBufferMapState).
26
+ */
27
+ export interface Buffer {
28
+ readonly [RhiBufferBrand]: void;
29
+ /**
30
+ * Map the buffer for CPU access.
31
+ *
32
+ * Spec anchor: W3C WebGPU §gpubuffer-mapasync /
33
+ * [@webgpu/types.GPUBuffer.mapAsync]. K-1 decision: `mode` is the raw
34
+ * `GPUMapMode` bitmask (NOT a closed union 'read' | 'write') so the forgeax
35
+ * form mirrors `GPUMapMode.READ` / `GPUMapMode.WRITE` literals.
36
+ *
37
+ * D-P2 #6 (feat-20260511-rhi-spec-realign-aggressive): the success branch
38
+ * resolves to `MappedBuffer`, a brand on top of `Buffer`. AI users
39
+ * subsequently call `mapped.getMappedRange(...)` / `mapped.unmap()` on the
40
+ * branded handle; calling those methods on a plain `Buffer` is a TS2345
41
+ * compile-time signal (charter proposition 4 explicit failure encoded at
42
+ * the type layer).
43
+ *
44
+ * Failure paths (research §4.2 + plan-strategy §2 K-2):
45
+ * - mapState !== 'unmapped' (F-8 row 1) -> 'webgpu-runtime-error'.
46
+ * - offset % 8 != 0 (step 4) -> 'webgpu-runtime-error'.
47
+ * - rangeSize % 4 != 0 (step 5) -> 'webgpu-runtime-error'.
48
+ * - offset + rangeSize > size (step 6) -> 'webgpu-runtime-error'.
49
+ * - mode contains foreign bits (step 7) -> 'webgpu-runtime-error'.
50
+ * - mode is not exactly READ or WRITE (step 8) -> 'webgpu-runtime-error'.
51
+ * - mode-usage mismatch (step 9 / F-8 row 3) -> 'webgpu-runtime-error'.
52
+ *
53
+ * @example
54
+ * const r = await buffer.mapAsync(GPUMapMode.WRITE);
55
+ * if (!r.ok) {
56
+ * // route via switch (r.error.code)
57
+ * return;
58
+ * }
59
+ * const mapped: MappedBuffer = r.value;
60
+ * const range = mapped.getMappedRange();
61
+ * if (range.ok) new Uint32Array(range.value).set([1, 2, 3, 4]);
62
+ * mapped.unmap();
63
+ */
64
+ mapAsync(mode: GPUMapModeFlags, offset?: number | undefined, size?: number | undefined): Promise<Result<MappedBuffer, RhiError>>;
65
+ /**
66
+ * Current mapping state (read-only getter).
67
+ *
68
+ * Spec anchor: research §4.1 mapState 3-state enum; mirrors
69
+ * GPUBufferMapState. Transitions:
70
+ * - createBuffer({mappedAtCreation:true}) sets mapState='mapped'.
71
+ * - mapAsync moves 'unmapped' -> 'pending' -> 'mapped'.
72
+ * - unmap moves 'mapped' -> 'unmapped'.
73
+ */
74
+ readonly mapState: 'unmapped' | 'pending' | 'mapped';
75
+ }
76
+ /**
77
+ * Brand on top of `Buffer` indicating the mapping is currently open; only the
78
+ * `MappedBuffer` exposes `getMappedRange` / `unmap` method forms so AI users
79
+ * cannot accidentally call them on an unmapped `Buffer` (D-P2 #6).
80
+ *
81
+ * The brand is structural — runtime the `MappedBuffer` is the same JS object
82
+ * as the underlying `Buffer`; TypeScript narrows access through the
83
+ * `__mapped: void` private brand symbol.
84
+ *
85
+ * Spec anchor: W3C WebGPU §4 Buffers mapping lifecycle (research §4.1 /
86
+ * §4.4); plan-strategy §7.1 + D-P2 break-point #6 (brand + method form
87
+ * merged).
88
+ *
89
+ * @example
90
+ * const r = await buffer.mapAsync(GPUMapMode.WRITE);
91
+ * if (!r.ok) return;
92
+ * const mapped: MappedBuffer = r.value;
93
+ * mapped.getMappedRange(); // method form, this: MappedBuffer
94
+ * mapped.unmap(); // method form, this: MappedBuffer
95
+ */
96
+ export interface MappedBuffer extends Buffer {
97
+ readonly [RhiMappedBufferBrand]: void;
98
+ /**
99
+ * Return an ArrayBuffer view of the mapped range. Method form on
100
+ * `MappedBuffer` per D-P2 #6 — calling on a plain `Buffer` is TS2339.
101
+ *
102
+ * Spec anchor: W3C WebGPU §gpubuffer-getmappedrange /
103
+ * [@webgpu/types.GPUBuffer.getMappedRange].
104
+ *
105
+ * Failure paths:
106
+ * - mapState !== 'mapped' (incl after unmap, F-8 row 2 detach guard) ->
107
+ * 'webgpu-runtime-error'.
108
+ */
109
+ getMappedRange(offset?: number | undefined, size?: number | undefined): Result<ArrayBuffer, RhiError>;
110
+ /**
111
+ * Unmap the buffer, detaching all ArrayBuffer views obtained from
112
+ * getMappedRange. Method form on `MappedBuffer` per D-P2 #6.
113
+ *
114
+ * Spec anchor: W3C WebGPU §gpubuffer-unmap /
115
+ * [@webgpu/types.GPUBuffer.unmap]. unmap() returns void per spec normative
116
+ * silent no-op (research §4.4); calling unmap on an already-unmapped buffer
117
+ * does NOT error. This is the ONE Result-shape exception in the buffer
118
+ * surface (AI User Affordances explicit listing).
119
+ *
120
+ * After unmap, the JS object continues to exist but the brand narrows
121
+ * away at the TS layer: AI users who hold a `MappedBuffer` after the
122
+ * underlying state flipped should re-`mapAsync` to obtain a fresh branded
123
+ * instance (OQ-5 plan-decisions: unmap returns void; subsequent mapAsync
124
+ * returns a new MappedBuffer brand).
125
+ */
126
+ unmap(): void;
127
+ }
128
+ declare const RhiTextureBrand: unique symbol;
129
+ /** GPU texture opaque handle (2D / 3D / cube / array). */
130
+ export interface Texture {
131
+ readonly [RhiTextureBrand]: void;
132
+ }
133
+ declare const RhiTextureViewBrand: unique symbol;
134
+ /** GPU texture view opaque handle. */
135
+ export interface TextureView {
136
+ readonly [RhiTextureViewBrand]: void;
137
+ }
138
+ declare const RhiSamplerBrand: unique symbol;
139
+ /** GPU sampler opaque handle. */
140
+ export interface Sampler {
141
+ readonly [RhiSamplerBrand]: void;
142
+ }
143
+ declare const RhiBindGroupBrand: unique symbol;
144
+ /** GPU bind group opaque handle (instantiated layout). */
145
+ export interface BindGroup {
146
+ readonly [RhiBindGroupBrand]: void;
147
+ }
148
+ declare const RhiBindGroupLayoutBrand: unique symbol;
149
+ /** GPU bind group layout opaque handle (declares binding shapes). */
150
+ export interface BindGroupLayout {
151
+ readonly [RhiBindGroupLayoutBrand]: void;
152
+ }
153
+ declare const RhiPipelineLayoutBrand: unique symbol;
154
+ /** GPU pipeline layout opaque handle (aggregates BindGroupLayouts). */
155
+ export interface PipelineLayout {
156
+ readonly [RhiPipelineLayoutBrand]: void;
157
+ }
158
+ declare const RhiRenderPipelineBrand: unique symbol;
159
+ /** GPU render pipeline opaque handle. */
160
+ export interface RenderPipeline {
161
+ readonly [RhiRenderPipelineBrand]: void;
162
+ }
163
+ declare const RhiComputePipelineBrand: unique symbol;
164
+ /** GPU compute pipeline opaque handle. */
165
+ export interface ComputePipeline {
166
+ readonly [RhiComputePipelineBrand]: void;
167
+ }
168
+ declare const RhiShaderModuleBrand: unique symbol;
169
+ /** GPU shader module opaque handle (WGSL / SPIR-V compile artifact). */
170
+ export interface ShaderModule {
171
+ readonly [RhiShaderModuleBrand]: void;
172
+ }
173
+ declare const RhiQuerySetBrand: unique symbol;
174
+ /** GPU query set opaque handle (occlusion / timestamp). */
175
+ export interface QuerySet {
176
+ readonly [RhiQuerySetBrand]: void;
177
+ }
178
+ declare const RhiFenceBrand: unique symbol;
179
+ /** GPU fence opaque handle (GPU/CPU sync barrier). */
180
+ export interface Fence {
181
+ readonly [RhiFenceBrand]: void;
182
+ }
183
+ declare const RhiCommandEncoderBrand: unique symbol;
184
+ /** GPU command encoder opaque handle (single-use). */
185
+ export interface CommandEncoder {
186
+ readonly [RhiCommandEncoderBrand]: void;
187
+ }
188
+ declare const RhiCommandBufferBrand: unique symbol;
189
+ /** GPU command buffer opaque handle (submitted to Queue). */
190
+ export interface CommandBuffer {
191
+ readonly [RhiCommandBufferBrand]: void;
192
+ }
193
+ /**
194
+ * Convert `?: T` optional fields to `?: T | undefined` (decision S-7).
195
+ *
196
+ * Compatible with exactOptionalPropertyTypes: writers may pass `undefined`
197
+ * explicitly or omit the field; the M2 shim distinguishes the two via
198
+ * `'x' in src` guards (research F-3 anti-pattern 2).
199
+ */
200
+ type ExplicitUndefined<T> = {
201
+ [K in keyof T]: T[K] | undefined;
202
+ };
203
+ /** GPU buffer descriptor. Field set strictly matches GPUBufferDescriptor;
204
+ * optional fields use `?: T | undefined`. */
205
+ export type BufferDescriptor = ExplicitUndefined<Pick<GPUBufferDescriptor, 'label' | 'size' | 'usage' | 'mappedAtCreation'>>;
206
+ /** GPU texture descriptor. Field set strictly matches GPUTextureDescriptor
207
+ * (incl R8 Compatibility Mode field). */
208
+ export type TextureDescriptor = ExplicitUndefined<Pick<GPUTextureDescriptor, 'label' | 'size' | 'mipLevelCount' | 'sampleCount' | 'dimension' | 'format' | 'usage' | 'viewFormats' | 'textureBindingViewDimension'>>;
209
+ /** GPU sampler descriptor. Field set strictly matches GPUSamplerDescriptor. */
210
+ export type SamplerDescriptor = ExplicitUndefined<Pick<GPUSamplerDescriptor, 'label' | 'addressModeU' | 'addressModeV' | 'addressModeW' | 'magFilter' | 'minFilter' | 'mipmapFilter' | 'lodMinClamp' | 'lodMaxClamp' | 'compare' | 'maxAnisotropy'>>;
211
+ /** GPU bind group layout descriptor. Field set strictly matches
212
+ * GPUBindGroupLayoutDescriptor. */
213
+ export type BindGroupLayoutDescriptor = ExplicitUndefined<Pick<GPUBindGroupLayoutDescriptor, 'label' | 'entries'>>;
214
+ /**
215
+ * GPU texture view descriptor (Pick<GPUTextureViewDescriptor, 9 fields>).
216
+ *
217
+ * Spec anchor: W3C WebGPU §texture-view-creation /
218
+ * [@webgpu/types.GPUTextureViewDescriptor]. Field NAMES align byte-for-byte;
219
+ * field set excludes the feature-gated `swizzle` field (research §1.1 OOS-MVP;
220
+ * a future closure can add it once `'texture-component-swizzle'` is enabled).
221
+ *
222
+ * Cross-resource validation (shim fast-path, research §1.1):
223
+ * - `format` must equal source.format OR be in source.viewFormats; otherwise
224
+ * the shim returns Result.err({ code: 'webgpu-runtime-error' }).
225
+ * - `usage` must be a subset of source.usage (bitmask); otherwise the shim
226
+ * returns the same code.
227
+ *
228
+ * @example
229
+ * const r = device.createTextureView(tex, { format: 'rgba8unorm', dimension: '2d' });
230
+ * if (!r.ok) {
231
+ * // route via switch (r.error.code)
232
+ * }
233
+ */
234
+ export type TextureViewDescriptor = ExplicitUndefined<Pick<GPUTextureViewDescriptor, 'label' | 'format' | 'dimension' | 'usage' | 'aspect' | 'baseMipLevel' | 'mipLevelCount' | 'baseArrayLayer' | 'arrayLayerCount'>>;
235
+ /**
236
+ * GPU compute pipeline descriptor (Pick<GPUComputePipelineDescriptor,
237
+ * 'label' | 'layout' | 'compute'>).
238
+ *
239
+ * Spec anchor: W3C WebGPU §compute-pipeline-creation /
240
+ * [@webgpu/types.GPUComputePipelineDescriptor]. Field NAMES align byte-for-byte
241
+ * with spec.
242
+ *
243
+ * `layout` is the spec union `'auto' | GPUPipelineLayout`; forgeax tightens
244
+ * the explicit form to the `PipelineLayout` opaque handle (D-S5 pattern):
245
+ * layout: 'auto' | PipelineLayout
246
+ *
247
+ * `compute` mirrors `GPUProgrammableStage` verbatim — `module` (required
248
+ * `ShaderModule` opaque handle), `entryPoint?` (optional string),
249
+ * `constants?` (optional `Record<string, number>`).
250
+ *
251
+ * Capability gate (research §1.2 NOTE; plan-strategy §4.3 boundary case row 1):
252
+ * - `caps.compute === false` -> shim returns Result.err({
253
+ * code: 'feature-not-enabled', expected: 'caps.compute === true',
254
+ * hint: 'check device.caps.compute before calling createComputePipeline'
255
+ * }). MVP WebGPU path always has caps.compute=true (spec mandate); the gate
256
+ * exists for potential future non-WebGPU backends.
257
+ *
258
+ * @example
259
+ * const r = device.createComputePipeline({
260
+ * label: 'cs',
261
+ * layout: 'auto',
262
+ * compute: { module: csModule, entryPoint: 'cs_main' },
263
+ * });
264
+ */
265
+ export type ComputePipelineDescriptor = ExplicitUndefined<Omit<Pick<GPUComputePipelineDescriptor, 'label' | 'layout' | 'compute'>, 'layout' | 'compute'>> & {
266
+ /**
267
+ * Either `'auto'` for user-agent BGL inference or a forgeax
268
+ * `PipelineLayout` opaque handle (D-S5 pattern: forgeax handle replaces
269
+ * spec polymorphism `(GPUAutoLayoutMode or GPUPipelineLayout)`).
270
+ */
271
+ layout: 'auto' | PipelineLayout;
272
+ /**
273
+ * The compute programmable stage. `module` is the forgeax `ShaderModule`
274
+ * opaque handle (replacing spec `GPUShaderModule`); `entryPoint?` defaults
275
+ * to the module's single compute entry; `constants?` is a record of
276
+ * pipeline-overridable constants.
277
+ */
278
+ compute: {
279
+ module: ShaderModule;
280
+ entryPoint?: string | undefined;
281
+ constants?: Record<string, number> | undefined;
282
+ };
283
+ };
284
+ /**
285
+ * GPU query set descriptor (Pick<GPUQuerySetDescriptor, 'label' | 'type' | 'count'>).
286
+ *
287
+ * Spec anchor: W3C WebGPU §queries / [@webgpu/types.GPUQuerySetDescriptor].
288
+ * Field NAMES align byte-for-byte.
289
+ *
290
+ * Hard constraints (research §1.3):
291
+ * - `count <= 4096` (spec normative). The shim fast-paths a violation to
292
+ * Result.err({ code: 'limit-exceeded',
293
+ * expected: 'count <= 4096 (spec normative)',
294
+ * hint: 'create multiple QuerySet instances if more than 4096 queries needed' }).
295
+ * - `type === 'timestamp'` requires `caps.timestampQuery === true` (the
296
+ * 'timestamp-query' feature). Otherwise the shim fast-paths to
297
+ * Result.err({ code: 'feature-not-enabled' }).
298
+ * - `count = 0` is legal (lower bound; dawn end2end test fixture).
299
+ *
300
+ * @example
301
+ * const r = device.createQuerySet({ type: 'occlusion', count: 4 });
302
+ * if (!r.ok) {
303
+ * // route via switch (r.error.code)
304
+ * }
305
+ */
306
+ export type QuerySetDescriptor = ExplicitUndefined<Pick<GPUQuerySetDescriptor, 'label' | 'type' | 'count'>>;
307
+ /**
308
+ * Discriminated union over the 4 BindGroup entry resource kinds (the spec
309
+ * polymorphic `GPUBindingResource` collapsed to a tagged union — charter
310
+ * proposition 4 closed-union exhaustive switch + proposition 5 consistent
311
+ * abstraction over duck-typing).
312
+ *
313
+ * Introduced in feat-20260511-rhi-spec-realign-aggressive w9 per requirements
314
+ * AC-10 + plan-strategy §7.1 + D-P2 break-point #5. AI users `switch
315
+ * (resource.kind)` is exhaustive without a default fallback; construction-side
316
+ * typos like `{ kind: 'samplre', ... }` trip TS2322 at the literal slot.
317
+ *
318
+ * Kind discriminator uses kebab-case for multi-word entries (`textureView` is
319
+ * already single-word camelCase by convention; `externalTexture` is multi-word
320
+ * camelCase for parity with the spec verb `GPUExternalTexture`).
321
+ *
322
+ * @example
323
+ * const e: RhiBindingResource = { kind: 'sampler', value: linearSampler };
324
+ * const e2: RhiBindingResource = {
325
+ * kind: 'buffer',
326
+ * value: { buffer: viewUniforms, offset: 0, size: 64 },
327
+ * };
328
+ */
329
+ export type RhiBindingResource = {
330
+ readonly kind: 'sampler';
331
+ readonly value: Sampler;
332
+ } | {
333
+ readonly kind: 'buffer';
334
+ readonly value: {
335
+ readonly buffer: Buffer;
336
+ readonly offset?: number;
337
+ readonly size?: number;
338
+ };
339
+ } | {
340
+ readonly kind: 'textureView';
341
+ readonly value: TextureView;
342
+ } | {
343
+ readonly kind: 'externalTexture';
344
+ readonly value: GPUExternalTexture;
345
+ };
346
+ /**
347
+ * BindGroup entry — one slot in a BindGroup, identified by `binding` (the
348
+ * shader binding number) and `resource` (the tagged-union `RhiBindingResource`
349
+ * — replaces the spec polymorphic `GPUBindingResource`).
350
+ *
351
+ * Field set strictly mirrors `Pick<GPUBindGroupEntry, 'binding'>`; the
352
+ * `resource` field is tightened to the forgeax `RhiBindingResource` tagged
353
+ * union per D-P2 break-point #5 (charter proposition 5 consistent abstraction:
354
+ * AI users see one canonical 4-kind switch rather than spec duck-typing).
355
+ */
356
+ export type BindGroupEntry = Pick<GPUBindGroupEntry, 'binding'> & {
357
+ resource: RhiBindingResource;
358
+ };
359
+ /**
360
+ * GPU bind group descriptor (Pick<GPUBindGroupDescriptor, 'label' | 'layout' | 'entries'>).
361
+ *
362
+ * Spec anchor: W3C WebGPU 10 Resource binding /
363
+ * [@webgpu/types.GPUBindGroupDescriptor].
364
+ *
365
+ * Introduced in feat-20260509-ecs-render-bridge-mvp (D-S1) so the RenderSystem
366
+ * can record `pass.setBindGroup(0/1/2, bg, ...)` through a single RHI surface
367
+ * (charter proposition 5 consistent abstraction; never via raw GPUDevice).
368
+ *
369
+ * The `layout` field references the forgeax `BindGroupLayout` opaque handle
370
+ * (created via `RhiDevice.createBindGroupLayout`); the `entries` array uses
371
+ * the forgeax `BindGroupEntry` shape (binding number + tagged-union
372
+ * `RhiBindingResource`) per feat-20260511-rhi-spec-realign-aggressive D-P2
373
+ * break-point #5 (was previously verbatim `GPUBindGroupEntry`).
374
+ *
375
+ * @example
376
+ * const desc: BindGroupDescriptor = {
377
+ * label: 'view-bg',
378
+ * layout: bgl,
379
+ * entries: [{ binding: 0, resource: { kind: 'buffer', value: { buffer: viewUniforms } } }],
380
+ * };
381
+ */
382
+ export type BindGroupDescriptor = ExplicitUndefined<Omit<Pick<GPUBindGroupDescriptor, 'label' | 'layout' | 'entries'>, 'layout' | 'entries'>> & {
383
+ /**
384
+ * BindGroupLayout opaque handle (forgeax tightening: `layout` is the
385
+ * already-shipped `BindGroupLayout` brand, not the spec
386
+ * `GPUBindGroupLayout`). Same D-S5 pattern as RenderPassColorAttachment.view
387
+ * — AI users receive a forgeax-creatable handle, never a phantom spec type.
388
+ */
389
+ layout: BindGroupLayout;
390
+ /**
391
+ * Iterable of forgeax `BindGroupEntry` (binding + tagged-union
392
+ * `RhiBindingResource`); replaces the spec polymorphic
393
+ * `iterable<GPUBindGroupEntry>` per D-P2 break-point #5.
394
+ */
395
+ entries: Iterable<BindGroupEntry>;
396
+ };
397
+ /**
398
+ * GPU pipeline layout descriptor (Pick<GPUPipelineLayoutDescriptor, 'label' | 'bindGroupLayouts'>).
399
+ *
400
+ * Spec anchor: W3C WebGPU 10.3 Pipeline layout /
401
+ * [@webgpu/types.GPUPipelineLayoutDescriptor].
402
+ *
403
+ * Introduced in feat-20260509-ecs-render-bridge-mvp (D-S1) so the
404
+ * `Renderer.ready` step 2 (PBR pipeline compile) can compose the 3
405
+ * BindGroupLayouts (view / material / mesh-array) into a single
406
+ * `PipelineLayout` for `RhiDevice.createRenderPipeline`.
407
+ *
408
+ * @example
409
+ * const desc: PipelineLayoutDescriptor = {
410
+ * label: 'pbr-pl',
411
+ * bindGroupLayouts: [viewBgl, materialBgl, meshArrayBgl],
412
+ * };
413
+ */
414
+ export type PipelineLayoutDescriptor = ExplicitUndefined<Omit<Pick<GPUPipelineLayoutDescriptor, 'label' | 'bindGroupLayouts'>, 'bindGroupLayouts'>> & {
415
+ /**
416
+ * Iterable of BindGroupLayout opaque handles (forgeax tightening: the
417
+ * iterable element type is the forgeax `BindGroupLayout` brand). Same D-S5
418
+ * pattern as BindGroupDescriptor.layout.
419
+ */
420
+ bindGroupLayouts: Iterable<BindGroupLayout>;
421
+ };
422
+ /** GPU render pipeline descriptor. Field set strictly matches
423
+ * GPURenderPipelineDescriptor. */
424
+ export type RenderPipelineDescriptor = ExplicitUndefined<Pick<GPURenderPipelineDescriptor, 'label' | 'layout' | 'vertex' | 'primitive' | 'depthStencil' | 'multisample' | 'fragment'>>;
425
+ /**
426
+ * Command encoder descriptor (Pick<GPUCommandEncoderDescriptor, 'label'>).
427
+ *
428
+ * Spec anchor: W3C WebGPU 22 GPUCommandEncoder /
429
+ * [@webgpu/types.GPUCommandEncoderDescriptor].
430
+ *
431
+ * @example
432
+ * const desc: CommandEncoderDescriptor = { label: 'frame-encoder' };
433
+ */
434
+ export type CommandEncoderDescriptor = ExplicitUndefined<Pick<GPUCommandEncoderDescriptor, 'label'>>;
435
+ /**
436
+ * Render-pass color attachment (Pick<GPURenderPassColorAttachment, ...> with
437
+ * `view` field aligned to the forgeax `TextureView` opaque handle, per
438
+ * feat-20260510-rhi-resource-creation M2 view narrow Path X / breakage point
439
+ * #1).
440
+ *
441
+ * Spec anchor: W3C WebGPU 22.7 Render pass /
442
+ * [@webgpu/types.GPURenderPassColorAttachment].
443
+ *
444
+ * v0.1.69 spec shape: `view: GPUTexture | GPUTextureView`. The forgeax RHI
445
+ * tightens the union to the single `TextureView` brand (the only branch the
446
+ * shim ever produces post-M1). Charter proposition 5 consistent abstraction
447
+ * (the field type matches what `RhiDevice.createTextureView` returns) +
448
+ * proposition 4 explicit failure (passing a `Texture` brand here is a tsc red
449
+ * signal; the AI user is steered to the spec idiom).
450
+ *
451
+ * **Migration**: see AGENTS.md break-point list 2026-05-10 #1
452
+ * "view: Texture -> TextureView narrow (major breaking)" for the call-site
453
+ * upgrade diff (3 narrowed fields: this `view` + `resolveTarget` +
454
+ * `RenderPassDepthStencilAttachment.view`).
455
+ *
456
+ * @example
457
+ * const view = device.createTextureView(tex, {}).unwrap();
458
+ * const att: RenderPassColorAttachment = {
459
+ * view,
460
+ * clearValue: { r: 0, g: 0, b: 0, a: 1 },
461
+ * loadOp: 'clear',
462
+ * storeOp: 'store',
463
+ * };
464
+ */
465
+ export type RenderPassColorAttachment = ExplicitUndefined<Omit<Pick<GPURenderPassColorAttachment, 'view' | 'depthSlice' | 'resolveTarget' | 'clearValue' | 'loadOp' | 'storeOp'>, 'view' | 'resolveTarget'>> & {
466
+ /**
467
+ * TextureView target of this color attachment (view narrow Path X: aligned
468
+ * to the forgeax `TextureView` brand returned by `createTextureView`; not
469
+ * the spec union `GPUTexture | GPUTextureView`).
470
+ */
471
+ view: TextureView;
472
+ /**
473
+ * Optional resolve target for multisample resolution. Same view narrow
474
+ * alignment as `view`.
475
+ */
476
+ resolveTarget?: TextureView | undefined;
477
+ };
478
+ /**
479
+ * Render-pass depth/stencil attachment (Pick<GPURenderPassDepthStencilAttachment,
480
+ * ...> with `view` field aligned to the forgeax `TextureView` opaque handle,
481
+ * per feat-20260510-rhi-resource-creation M2 view narrow Path X / breakage
482
+ * point #1).
483
+ *
484
+ * Spec anchor: W3C WebGPU 22.7 Render pass /
485
+ * [@webgpu/types.GPURenderPassDepthStencilAttachment].
486
+ *
487
+ * Same view narrow alignment as RenderPassColorAttachment.
488
+ *
489
+ * **Migration**: see AGENTS.md break-point list 2026-05-10 #1 for the
490
+ * call-site upgrade diff covering this `view` field plus the two
491
+ * `RenderPassColorAttachment` narrowed fields.
492
+ *
493
+ * @example
494
+ * const view = device.createTextureView(depthTex, {}).unwrap();
495
+ * const ds: RenderPassDepthStencilAttachment = {
496
+ * view,
497
+ * depthClearValue: 1,
498
+ * depthLoadOp: 'clear',
499
+ * depthStoreOp: 'store',
500
+ * };
501
+ */
502
+ export type RenderPassDepthStencilAttachment = ExplicitUndefined<Omit<Pick<GPURenderPassDepthStencilAttachment, 'view' | 'depthClearValue' | 'depthLoadOp' | 'depthStoreOp' | 'depthReadOnly' | 'stencilClearValue' | 'stencilLoadOp' | 'stencilStoreOp' | 'stencilReadOnly'>, 'view'>> & {
503
+ /**
504
+ * TextureView target of this depth/stencil attachment (view narrow Path X:
505
+ * aligned to the forgeax `TextureView` brand).
506
+ */
507
+ view: TextureView;
508
+ };
509
+ /**
510
+ * Render-pass descriptor (Pick<GPURenderPassDescriptor, ...>).
511
+ *
512
+ * Spec anchor: W3C WebGPU 22.7 Render pass /
513
+ * [@webgpu/types.GPURenderPassDescriptor].
514
+ *
515
+ * `colorAttachments` element type uses the forgeax narrow
516
+ * `RenderPassColorAttachment` (with `view: TextureView`);
517
+ * `depthStencilAttachment` uses `RenderPassDepthStencilAttachment`.
518
+ *
519
+ * @example
520
+ * const view = device.createTextureView(tex, {}).unwrap();
521
+ * const desc: RenderPassDescriptor = {
522
+ * label: 'frame',
523
+ * colorAttachments: [{ view, loadOp: 'clear', storeOp: 'store',
524
+ * clearValue: { r: 0, g: 0, b: 0, a: 1 } }],
525
+ * };
526
+ */
527
+ export type RenderPassDescriptor = ExplicitUndefined<Omit<Pick<GPURenderPassDescriptor, 'label' | 'colorAttachments' | 'depthStencilAttachment' | 'occlusionQuerySet' | 'timestampWrites' | 'maxDrawCount'>, 'colorAttachments' | 'depthStencilAttachment' | 'occlusionQuerySet' | 'timestampWrites'>> & {
528
+ /**
529
+ * Color attachments for this render pass (forgeax narrow element type per
530
+ * view narrow Path X: each entry's `view` field is `TextureView`, not the
531
+ * spec union `GPUTexture | GPUTextureView`).
532
+ */
533
+ colorAttachments: Iterable<RenderPassColorAttachment | null | undefined>;
534
+ /** Optional depth/stencil attachment with the same view narrow alignment. */
535
+ depthStencilAttachment?: RenderPassDepthStencilAttachment | undefined;
536
+ /** Occlusion query set (capability-gated). The shim accepts a
537
+ * `QuerySet` brand created via `device.createQuerySet({ type: 'occlusion',
538
+ * count })` and pairs it with `pass.beginOcclusionQuery(idx) /
539
+ * pass.endOcclusionQuery()`; both methods now have real implementations
540
+ * (see `RhiRenderPassEncoder` below). Capability gate: read
541
+ * `device.caps.timestampQuery` ahead of `'timestamp'` query sets;
542
+ * occlusion sets are unconditionally available. */
543
+ occlusionQuerySet?: QuerySet | undefined;
544
+ /** Optional timestamp query set and the pass boundary write slots. */
545
+ timestampWrites?: RenderPassTimestampWrites | undefined;
546
+ };
547
+ /** Timestamp writes attached to a render pass descriptor. */
548
+ export interface RenderPassTimestampWrites {
549
+ querySet: QuerySet;
550
+ beginningOfPassWriteIndex?: number | undefined;
551
+ endOfPassWriteIndex?: number | undefined;
552
+ }
553
+ /** Compute-pass descriptor with forgeax-owned timestamp query handles. */
554
+ export type ComputePassDescriptor = ExplicitUndefined<Omit<Pick<GPUComputePassDescriptor, 'label' | 'timestampWrites'>, 'timestampWrites'>> & {
555
+ timestampWrites?: ComputePassTimestampWrites | undefined;
556
+ };
557
+ /** Timestamp writes attached to a compute pass descriptor. */
558
+ export interface ComputePassTimestampWrites {
559
+ querySet: QuerySet;
560
+ beginningOfPassWriteIndex?: number | undefined;
561
+ endOfPassWriteIndex?: number | undefined;
562
+ }
563
+ /** Hardware-probe layer: readonly boolean capability flags. */
564
+ export interface RhiCaps {
565
+ /**
566
+ * The rendering backend kind — single source of truth for backend-aware
567
+ * logic (e.g. explicit barrier insertion vs. spec-managed / GL-implicit
568
+ * sync). Closed 4-member union: every backend reports exactly one.
569
+ *
570
+ * - `'webgpu'`: browser WebGPU — spec-managed barriers, no explicit
571
+ * barrier insertion needed.
572
+ * - `'wgpu-native'`: wgpu native-desktop runtime (Tauri / native) —
573
+ * requires explicit Vulkan/Metal/DX12 barrier commands.
574
+ * - `'wgpu-webgl2'`: wgpu GLES3/WebGL2 backend — GL implicit sync, no
575
+ * explicit barrier insertion needed (equivalence group with `'webgpu'`).
576
+ * - `'null'`: headless no-op backend (`@forgeax/engine-rhi-null`) for
577
+ * structural unit tests — no GPU / DOM; records command-stream shape into
578
+ * a ledger instead of executing it. No barrier insertion needed (the
579
+ * no-op backend executes nothing); falls into the same no-barrier branch
580
+ * as `'webgpu'` / `'wgpu-webgl2'`.
581
+ *
582
+ * @note `exactOptionalPropertyTypes` requires every backend fill this
583
+ * field; a backend that omits it produces a tsc compile error.
584
+ */
585
+ readonly backendKind: 'webgpu' | 'wgpu-native' | 'wgpu-webgl2' | 'null';
586
+ /** Whether compute pipelines are supported. */
587
+ readonly compute: boolean;
588
+ /** Whether timestamp queries are supported. */
589
+ readonly timestampQuery: boolean;
590
+ /** Backend-owned nanoseconds represented by one timestamp tick, or null when unavailable. */
591
+ readonly timestampPeriodNanoseconds: number | null;
592
+ /** Whether indirect drawing is supported. */
593
+ readonly indirectDrawing: boolean;
594
+ /**
595
+ * Whether BC texture compression (BC1-BC7) is available.
596
+ *
597
+ * Derived from `adapter.features.has('texture-compression-bc')`.
598
+ * On rhi-null this is always `false` (headless has no compression hardware,
599
+ * AC-06).
600
+ */
601
+ readonly textureCompressionBc: boolean;
602
+ /**
603
+ * Whether ETC2 texture compression is available.
604
+ *
605
+ * Derived from `adapter.features.has('texture-compression-etc2')`.
606
+ * On rhi-null this is always `false` (headless has no compression hardware,
607
+ * AC-06).
608
+ */
609
+ readonly textureCompressionEtc2: boolean;
610
+ /**
611
+ * Whether ASTC texture compression is available.
612
+ *
613
+ * Derived from `adapter.features.has('texture-compression-astc')`.
614
+ * On rhi-null this is always `false` (headless has no compression hardware,
615
+ * AC-06).
616
+ */
617
+ readonly textureCompressionAstc: boolean;
618
+ /**
619
+ * Whether multi-draw indirect is available (wgpu native extension).
620
+ *
621
+ * @reserved-for-wgpu-native-only always `false` on browser backends; only
622
+ * available when the forgeax renderer runs against a wgpu native runtime
623
+ * (Tauri / native runtime, not the wasm bundle). `caps.X = false` is an
624
+ * explicit signal, never an exception (charter proposition 4 / AGENTS.md
625
+ * `RHI / WebGPU` shape rule #2 capability-gated).
626
+ */
627
+ readonly multiDrawIndirect: boolean;
628
+ /**
629
+ * Whether push constants are available (wgpu native extension).
630
+ *
631
+ * @reserved-for-wgpu-native-only always `false` on browser backends; only
632
+ * available when the forgeax renderer runs against a wgpu native runtime
633
+ * (Tauri / native runtime, not the wasm bundle). `caps.X = false` is an
634
+ * explicit signal, never an exception (charter proposition 4 / AGENTS.md
635
+ * `RHI / WebGPU` shape rule #2 capability-gated).
636
+ */
637
+ readonly pushConstants: boolean;
638
+ /**
639
+ * Whether bindless texture array is available (wgpu native extension).
640
+ *
641
+ * @reserved-for-wgpu-native-only always `false` on browser backends; only
642
+ * available when the forgeax renderer runs against a wgpu native runtime
643
+ * (Tauri / native runtime, not the wasm bundle). `caps.X = false` is an
644
+ * explicit signal, never an exception (charter proposition 4 / AGENTS.md
645
+ * `RHI / WebGPU` shape rule #2 capability-gated).
646
+ */
647
+ readonly textureBindingArray: boolean;
648
+ /**
649
+ * Whether sampler binding aliasing is supported across pipelines.
650
+ *
651
+ * @spec-anchor W3C WebGPU §10.3 Bind group layout — spec mandates that a
652
+ * sampler may alias multiple binding slots; both navigator.gpu and the
653
+ * wgpu wasm bundle satisfy this so the field is always `true` on browser
654
+ * backends.
655
+ * @note Always `true` on shipped backends (WebGPU + wgpu wasm). The field
656
+ * exists for potential future backends that lack sampler aliasing.
657
+ * @hint AI users use `caps.samplerAliasing` to gate code that creates two
658
+ * `BindGroupEntry`s pointing at the same `Sampler` across different
659
+ * layouts; `caps.X = false` is an explicit signal, never an exception
660
+ * (charter proposition 4).
661
+ */
662
+ readonly samplerAliasing: boolean;
663
+ /**
664
+ * Whether the renderer can issue indirect draws with a non-zero
665
+ * `firstInstance`.
666
+ *
667
+ * @spec-anchor W3C WebGPU §22.4 drawIndirect — the `indirect-first-instance`
668
+ * feature on `GPUAdapter.features` gates non-zero `firstInstance` in
669
+ * indirect draws; rhi-webgpu maps this to `device.features.has(
670
+ * 'indirect-first-instance')`.
671
+ * @note `false` on backends without indirect drawing support.
672
+ * @hint Most AI users never need this; the field surfaces so a renderer
673
+ * author building instanced draw batchers can gate the fast path. With
674
+ * `caps.firstInstanceIndirect === false` the renderer must pre-rebase
675
+ * instance indices in the vertex shader (charter proposition 5
676
+ * consistent abstraction over a discoverable cap difference).
677
+ */
678
+ readonly firstInstanceIndirect: boolean;
679
+ /**
680
+ * Whether storage buffer bindings are available
681
+ * (`device.limits.maxStorageBuffersPerShaderStage > 0`).
682
+ *
683
+ * @spec-anchor W3C WebGPU §3.6.2 GPUSupportedLimits.
684
+ * maxStorageBuffersPerShaderStage; `> 0` means the device supports the
685
+ * `storage` / `read-only-storage` binding types.
686
+ * @note `false` on backends without storage buffer support.
687
+ * @hint AI users gate compute / large-buffer paths on
688
+ * `caps.storageBuffer`; the per-stage numeric limit lives on
689
+ * `device.limits.maxStorageBuffersPerShaderStage` for capacity planning
690
+ * (charter proposition 4: `caps.X = false` is an explicit signal).
691
+ */
692
+ readonly storageBuffer: boolean;
693
+ /**
694
+ * Whether storage texture bindings are available
695
+ * (`device.limits.maxStorageTexturesPerShaderStage > 0`).
696
+ *
697
+ * @spec-anchor W3C WebGPU §3.6.2 GPUSupportedLimits.
698
+ * maxStorageTexturesPerShaderStage; `> 0` means the device supports the
699
+ * `write-only` / `read-write` storage texture binding types.
700
+ * @note `false` on backends without storage texture support.
701
+ * @hint AI users gate image-effects compute / postprocess paths on
702
+ * `caps.storageTexture`; the per-stage numeric limit lives on
703
+ * `device.limits.maxStorageTexturesPerShaderStage` (charter proposition
704
+ * 4: `caps.X = false` is an explicit signal).
705
+ */
706
+ readonly storageTexture: boolean;
707
+ /**
708
+ * Whether the device can create `rgba16float` textures with `RENDER_ATTACHMENT`
709
+ * usage, enabling the HDR cubemap path for IBL irradiance / specular prefilter
710
+ * and downstream HDR render-target chains.
711
+ *
712
+ * @spec-anchor W3C WebGPU $25.1 GPUTextureFormat — `rgba16float` is an
713
+ * optional texture format whose `RENDER_ATTACHMENT` capability is probed by
714
+ * attempting `createTexture({ format: 'rgba16float', usage:
715
+ * GPUTextureUsage.RENDER_ATTACHMENT, size: [1, 1, 1] })` on the live
716
+ * device; failure maps the cap to `false`.
717
+ * @note Probed at device-creation time via a synchronous `createTexture`
718
+ * call, not via `GPUAdapter.features`. The `rgba16float` format is widely
719
+ * supported but `RENDER_ATTACHMENT` with float formats is optional per spec
720
+ * so the cap reflects the concrete device, not the adapter feature list.
721
+ * @hint AI users gate IBL / HDR post-processing paths on
722
+ * `caps.rgba16floatRenderable`; when `false` the internal equirect-to-cubemap
723
+ * IBL projection (driven by declaring `Skylight{equirect}`) degrades to the
724
+ * white-cube fallback and fires `{ code: 'equirect-projection-failed' }` with
725
+ * a machine-readable `expected` field naming this cap (charter P3 structured
726
+ * failure).
727
+ */
728
+ readonly rgba16floatRenderable: boolean;
729
+ /**
730
+ * Whether the device can create `rg11b10ufloat` textures with
731
+ * `RENDER_ATTACHMENT` usage, enabling the HDR swapchain / render-target path
732
+ * with reduced bit-depth precision versus `rgba16float`.
733
+ *
734
+ * @spec-anchor W3C WebGPU $25.1 GPUTextureFormat — `rg11b10ufloat` is
735
+ * `RENDER_ATTACHMENT`-capable only when the optional feature
736
+ * `rg11b10ufloat-renderable` is enabled (W3C WebGPU $4.2). Probed by gating
737
+ * on `device.features.has('rg11b10ufloat-renderable')` first; only then
738
+ * confirmed by `createTexture({ format: 'rg11b10ufloat', usage:
739
+ * GPUTextureUsage.RENDER_ATTACHMENT, size: [1, 1, 1] })`.
740
+ * @note The format packs 11+11+10 unsigned float bits into 32 bits per pixel;
741
+ * it is a popular HDR swapchain format for engines that trade precision
742
+ * for bandwidth but its `RENDER_ATTACHMENT` capability is not universal.
743
+ * The feature gate is the authoritative answer (avoiding fan-out via
744
+ * `device.onuncapturederror` when the optional feature is absent); the
745
+ * subsequent probe handles the rare case where the feature is reported
746
+ * but the concrete device still rejects.
747
+ * @hint AI users can select an HDR back-buffer format by reading
748
+ * `caps.rg11b10ufloatRenderable` before creating a
749
+ * `GPUTextureUsage.RENDER_ATTACHMENT` texture at that format; when `false`
750
+ * fall back to `rgba16float` (if `caps.rgba16floatRenderable` is true)
751
+ * or an SDR format.
752
+ */
753
+ readonly rg11b10ufloatRenderable: boolean;
754
+ /**
755
+ * Whether the device supports sampling `rgba32float` textures with a
756
+ * `filtering` sampler (linear / mipmap filtering), NOT just with a
757
+ * `non-filtering` sampler.
758
+ *
759
+ * @spec-anchor W3C WebGPU $10.3 Bind group layout — a bind group layout
760
+ * entry pairing a `filtering` sampler type with `sampleType: 'float'`
761
+ * (matching `rgba32float`) validates only when the
762
+ * `float32-filterable` feature is enabled. The cap probes this via
763
+ * `device.createBindGroupLayout({ entries: [{ sampler: { type:
764
+ * 'filtering' } }, { texture: { sampleType: 'float' } }] })` and
765
+ * `device.createSampler({ minFilter: 'linear', magFilter: 'linear' })`;
766
+ * failure maps the cap to `false`.
767
+ * @note Probed by gating on `device.features.has('float32-filterable')`
768
+ * first (the authoritative answer per spec $4.2; avoids fan-out via
769
+ * `device.onuncapturederror` when the optional feature is absent); only
770
+ * then confirmed by exercising the bind-group-layout. The subsequent
771
+ * probe handles the rare case where the feature is reported but the
772
+ * concrete device still rejects (spec ambiguity, driver quirks).
773
+ * @hint AI users gate float32-sampled compute / post-process paths on
774
+ * `caps.float32Filterable`; when `false` use `sampleType: 'unfilterable-
775
+ * float'` with a `non-filtering` sampler and compute the filter kernel
776
+ * manually in the shader, or fall back to `rgba16float` with filtering.
777
+ */
778
+ readonly float32Filterable: boolean;
779
+ /**
780
+ * Maximum number of color attachments per render pass.
781
+ *
782
+ * @spec-anchor W3C WebGPU $3.6.2 GPUSupportedLimits.maxColorAttachments;
783
+ * spec minimum = 4, defaults to 8 on mainstream backends.
784
+ * HDRP deferred pipeline requires >= 4 (3 g-buffer RT + 1 depth);
785
+ * installPipeline checks this cap at install time and throws
786
+ * `hdrp-deferred-caps-insufficient` on violation (charter P3).
787
+ * @note add-only minor (feat-20260612-hdrp-deferred-shading-learn-render-5-8
788
+ * M1 / w5); no existing field is modified.
789
+ */
790
+ readonly maxColorAttachments: number;
791
+ }
792
+ /**
793
+ * Enabled feature set, opaque iteration only via `has()`.
794
+ *
795
+ * Aligned with `GPUSupportedFeatures` shape; this empty interface intentionally
796
+ * adds no fields — the concrete enabled set is decided at `requestDevice` time
797
+ * and is then probed by AI users via `device.features.has('feature-name')`
798
+ * (charter proposition 5 consistent abstraction; no implementation-detail leak,
799
+ * no enumeration helper that would tie callers to a fixed feature list).
800
+ *
801
+ * @see {@link GPUSupportedFeatures}
802
+ */
803
+ export interface RhiFeatures extends ReadonlySet<GPUFeatureName> {
804
+ }
805
+ /** Numeric-limits layer aligned with GPUSupportedLimits (incl Compatibility
806
+ * Mode follow-on fields). */
807
+ export type RhiLimits = Readonly<GPUSupportedLimits>;
808
+ /**
809
+ * RhiAdapter request options.
810
+ *
811
+ * Spec anchor: W3C WebGPU §3.2 `GPURequestAdapterOptions` /
812
+ * [@webgpu/types.GPURequestAdapterOptions]. Fields pass through to the
813
+ * underlying `navigator.gpu.requestAdapter(opts)` call.
814
+ */
815
+ export type RequestAdapterOptions = ExplicitUndefined<Pick<GPURequestAdapterOptions, 'powerPreference' | 'forceFallbackAdapter'>>;
816
+ /**
817
+ * RhiAdapter.requestDevice options.
818
+ *
819
+ * Spec anchor: W3C WebGPU §3.4 `GPUDeviceDescriptor` /
820
+ * [@webgpu/types.GPUDeviceDescriptor]. Fields pass through to the underlying
821
+ * `adapter.requestDevice(opts)` call.
822
+ */
823
+ export type RequestDeviceOptions = ExplicitUndefined<Pick<GPUDeviceDescriptor, 'label' | 'requiredFeatures' | 'requiredLimits'>>;
824
+ /**
825
+ * RhiInstance — entry point for adapter discovery (K-6 strict two-step path).
826
+ *
827
+ * Spec anchor: W3C WebGPU §3.1 `GPU` interface / [@webgpu/types.GPU]; wgpu
828
+ * `Instance::request_adapter` (research §6.1) + Dawn
829
+ * `InstanceBase::APIRequestAdapter` (§6.2).
830
+ *
831
+ * Replaces the legacy top-level `rhi.requestDevice(opts)` factory (break-
832
+ * point #2). AI users follow the spec idiom:
833
+ * const a = (await rhi.requestAdapter()).unwrap();
834
+ * const d = (await a.requestDevice(opts)).unwrap();
835
+ *
836
+ * @example
837
+ * const adapterResult = await rhi.requestAdapter();
838
+ * if (!adapterResult.ok) {
839
+ * // route via switch (adapterResult.error.code)
840
+ * }
841
+ */
842
+ export interface RhiInstance {
843
+ /**
844
+ * Request a GPU adapter.
845
+ *
846
+ * Spec anchor: W3C WebGPU §3.1 `GPU.requestAdapter` /
847
+ * [@webgpu/types.GPU.requestAdapter].
848
+ *
849
+ * @param opts — W3C-spec request adapter options (powerPreference,
850
+ * forceFallbackAdapter).
851
+ * @param compatibleSurface — non-W3C extension required by the wgpu GL
852
+ * backend for adapter enumeration. Provided as a positional escape hatch
853
+ * so the first parameter stays spec-aligned (plan-strategy D-5).
854
+ * rhi-webgpu accepts and ignores this parameter (dual-impl symmetry);
855
+ * rhi-wgpu routes it to `requestAdapterWithCanvas`.
856
+ *
857
+ * Failure paths (research §F-5):
858
+ * - adapter null -> `Result.err({ code: 'adapter-unavailable' })`.
859
+ */
860
+ requestAdapter(opts?: RequestAdapterOptions | undefined, compatibleSurface?: HTMLCanvasElement | OffscreenCanvas | undefined): Promise<Result<RhiAdapter, RhiError>>;
861
+ }
862
+ /**
863
+ * RhiAdapter — capability-probe layer + device-creation entry (K-5 + K-6).
864
+ *
865
+ * Spec anchor: W3C WebGPU §3.2 `GPUAdapter` interface /
866
+ * [@webgpu/types.GPUAdapter]; wgpu `Adapter::request_device` (research §6.1)
867
+ * + Dawn `AdapterBase::APIRequestDevice` (§6.2).
868
+ *
869
+ * The `features` / `limits` fields let AI users **pre-screen** device
870
+ * capabilities before calling `requestDevice(opts)` (charter proposition 4
871
+ * forward-reachable: features mismatch becomes visible before spec
872
+ * validation surfaces it).
873
+ *
874
+ * @example
875
+ * if (!adapter.features.has('timestamp-query')) {
876
+ * // skip timestamp-related code paths
877
+ * }
878
+ * const deviceResult = await adapter.requestDevice({
879
+ * requiredFeatures: ['timestamp-query'],
880
+ * });
881
+ */
882
+ export interface RhiAdapter {
883
+ /**
884
+ * Read-only feature-name set (K-5).
885
+ *
886
+ * Aligned with `GPUAdapter.features` projection of `GPUSupportedFeatures`
887
+ * (a read-only Set) **and** with `RhiDevice.features` (Round 3 fix-up
888
+ * F-P1-2: cross-tier shape uniformity — AI users use `.has(name)` on
889
+ * both abstraction layers, no projection drift).
890
+ *
891
+ * F-1 ai-user-review: mutation of set entries (`features.add('x')` /
892
+ * deletion / clear) is rejected at compile time via `ReadonlySet`;
893
+ * charter proposition 4 explicit failure + proposition 5 consistent
894
+ * abstraction.
895
+ */
896
+ readonly features: ReadonlySet<GPUFeatureName>;
897
+ /**
898
+ * Read-only numeric-limits map (K-5).
899
+ *
900
+ * Aligned with `GPUAdapter.limits` projection of `GPUSupportedLimits`. The
901
+ * forgeax form flattens to a `Readonly<Record<string, number>>` so AI users
902
+ * can do `adapter.limits.maxTextureDimension2D` lookups without holding the
903
+ * spec object handle.
904
+ *
905
+ * F-1 ai-user-review: mutation of values (`limits.x = 0`) is rejected at
906
+ * compile time.
907
+ */
908
+ readonly limits: Readonly<Record<string, number>>;
909
+ /**
910
+ * Request a GPU device.
911
+ *
912
+ * Spec anchor: W3C WebGPU §3.2 `GPUAdapter.requestDevice` /
913
+ * [@webgpu/types.GPUAdapter.requestDevice].
914
+ *
915
+ * Returns `Result<RhiDevice, RhiError>` (K-6: NOT a `(Device, Queue)` tuple
916
+ * — queue is exposed via `RhiDevice.queue`).
917
+ *
918
+ * Failure paths (research §F-5):
919
+ * - feature not enabled -> `Result.err({ code: 'feature-not-enabled' })`.
920
+ * - limit exceeded -> `Result.err({ code: 'limit-exceeded' })`.
921
+ */
922
+ requestDevice(opts?: RequestDeviceOptions | undefined): Promise<Result<RhiDevice, RhiError>>;
923
+ }
924
+ /**
925
+ * Canvas configuration descriptor (Pick<GPUCanvasConfiguration, 7 fields>).
926
+ *
927
+ * Spec anchor: W3C WebGPU §3.3 `GPUCanvasConfiguration` /
928
+ * [@webgpu/types.GPUCanvasConfiguration].
929
+ *
930
+ * 7 fields (research §3.2):
931
+ * - `device` (required): the GPUDevice for the configured context.
932
+ * - `format` (required): one of `{'bgra8unorm', 'rgba8unorm', 'rgba16float'}`
933
+ * (the spec normative supported context formats).
934
+ * - `usage` (default `0x10` = RENDER_ATTACHMENT): bitmask of GPUTextureUsage
935
+ * for the swap-chain textures.
936
+ * - `viewFormats` (default `[]`): list of formats createView may yield;
937
+ * **the spec sRGB-render-target idiom** uses `format='bgra8unorm'` +
938
+ * `viewFormats=['bgra8unorm-srgb']` + `device.createTextureView` (research
939
+ * §3.2 normative).
940
+ * - `colorSpace` (default `'srgb'`): predefined color space for the canvas.
941
+ * - `toneMapping` (default `{}` ≅ `{ mode: 'standard' }`): HDR tone-mapping
942
+ * descriptor; the spec NOTE in research §3.2 says implementations
943
+ * without tone-mapping support **omit** this from `getConfiguration()`.
944
+ * - `alphaMode` (default `'opaque'`): canvas compositing mode.
945
+ *
946
+ * Field NAMES align byte-for-byte with the spec; the forgeax `?: T |
947
+ * undefined` shape (S-7 / hard-constraint 10) lets writers omit or pass
948
+ * `undefined` explicitly while the shim distinguishes via `'x' in src`.
949
+ */
950
+ export type CanvasConfiguration = ExplicitUndefined<Omit<Pick<GPUCanvasConfiguration, 'device' | 'format' | 'usage' | 'viewFormats' | 'colorSpace' | 'toneMapping' | 'alphaMode'>, 'device'>> & {
951
+ /**
952
+ * The forgeax RhiDevice the configured context binds to (D-S5 pattern: spec
953
+ * `device: GPUDevice` is replaced by the forgeax brand so AI users pass the
954
+ * device they got from `rhi.requestAdapter().requestDevice()`).
955
+ */
956
+ device: RhiDevice;
957
+ };
958
+ declare const RhiSurfaceBrand: unique symbol;
959
+ /**
960
+ * RhiSurface — opaque abstraction over a canvas surface
961
+ * (HTMLCanvasElement / OffscreenCanvas).
962
+ *
963
+ * Spec couples GPUCanvasContext to a canvas (research §3.1); the forgeax
964
+ * abstraction wraps the raw GPUCanvasContext in an opaque brand. AI users
965
+ * obtain the `RhiCanvasContext` via
966
+ * `rhi.acquireCanvasContext(canvas)` (returns `Result<RhiCanvasContext, RhiError>`).
967
+ *
968
+ * Charter proposition 5 consistent abstraction: the surface brand decouples
969
+ * AI-user code from the DOM canvas zoo (HTMLCanvasElement / OffscreenCanvas /
970
+ * native Window).
971
+ */
972
+ export interface RhiSurface {
973
+ readonly [RhiSurfaceBrand]: void;
974
+ }
975
+ /**
976
+ * RhiCanvasContext — forgeax canvas-context abstraction (M3 / K-4).
977
+ *
978
+ * Spec anchor: W3C WebGPU §3.3 `GPUCanvasContext` /
979
+ * [@webgpu/types.GPUCanvasContext]. 4 methods (research §3.1) match the spec
980
+ * names; the return types differ:
981
+ * - `configure` returns `Result<void, RhiError>` (the spec returns void; the
982
+ * forgeax form surfaces `webgpu-runtime-error` on format-gate / device-
983
+ * lost paths via Result, charter proposition 4 explicit failure).
984
+ * - `unconfigure` returns void (spec literal alignment).
985
+ * - `getConfiguration` returns `CanvasConfiguration | undefined` (the spec
986
+ * returns `GPUCanvasConfiguration?`; forgeax uses `undefined`).
987
+ * - `getCurrentTexture` returns `Result<Texture, RhiError>` (K-4: Texture
988
+ * brand, NOT TextureView; AI users go two-step
989
+ * `device.createTextureView(canvasContext.getCurrentTexture().unwrap(), {})`).
990
+ *
991
+ * Lifecycle (research §3.3 [[Expire the current texture]]): currentTexture
992
+ * **must NOT be cached across frames** — every frame must call
993
+ * `getCurrentTexture()` afresh.
994
+ */
995
+ export interface RhiCanvasContext {
996
+ /**
997
+ * Configure the canvas context with a forgeax CanvasConfiguration.
998
+ *
999
+ * Returns `Result<void, RhiError>` (charter proposition 4 explicit failure).
1000
+ *
1001
+ * Failure paths (research §3.3):
1002
+ * - `format` not in supported context formats (`{'bgra8unorm',
1003
+ * 'rgba8unorm', 'rgba16float'}`) -> `'webgpu-runtime-error'` with
1004
+ * `.expected = 'one of bgra8unorm/rgba8unorm/rgba16float'`.
1005
+ * - `device` invalid | lost -> `'rhi-not-available'`.
1006
+ *
1007
+ * @example
1008
+ * const out = canvasContext.configure({
1009
+ * device,
1010
+ * format: 'bgra8unorm',
1011
+ * usage: GPUTextureUsage.RENDER_ATTACHMENT,
1012
+ * viewFormats: ['rgba8unorm-srgb'],
1013
+ * });
1014
+ * if (!out.ok) {
1015
+ * // route via switch (out.error.code)
1016
+ * }
1017
+ */
1018
+ configure(desc: CanvasConfiguration): Result<void, RhiError>;
1019
+ /**
1020
+ * Unconfigure the canvas context (spec literal void return).
1021
+ *
1022
+ * Idempotent (already-unconfigured contexts continue to be unconfigured;
1023
+ * Operation is silent).
1024
+ */
1025
+ unconfigure(): void;
1026
+ /**
1027
+ * Return the current canvas configuration, or `undefined` if the context is
1028
+ * unconfigured.
1029
+ *
1030
+ * Feature-detection entry (research §3.2 spec NOTE): when an implementation
1031
+ * does not support a configuration field (e.g. tone-mapping), the field is
1032
+ * **omitted** from the returned record (NOT defaulted) so AI users can use
1033
+ * `'toneMapping' in conf` to detect support.
1034
+ */
1035
+ getConfiguration(): CanvasConfiguration | undefined;
1036
+ /**
1037
+ * Get the current swap-chain texture (K-4: returns Texture brand, NOT
1038
+ * TextureView).
1039
+ *
1040
+ * Spec anchor: W3C WebGPU §3.3 `GPUCanvasContext.getCurrentTexture` /
1041
+ * [@webgpu/types.GPUCanvasContext.getCurrentTexture].
1042
+ *
1043
+ * Failure paths (research §3.3):
1044
+ * - context unconfigured -> `'webgpu-runtime-error'` (spec
1045
+ * `InvalidStateError` mapping).
1046
+ *
1047
+ * AI users typically pair this with `device.createTextureView` to get the
1048
+ * render-pass attachment view (charter proposition 5 consistent abstraction):
1049
+ * const tex = canvasContext.getCurrentTexture().unwrap();
1050
+ * const view = device.createTextureView(tex, {}).unwrap();
1051
+ * pass.beginRenderPass({ colorAttachments: [{ view, ... }] });
1052
+ *
1053
+ * Lifecycle (research §3.3 [[Expire the current texture]]): each frame
1054
+ * **must call this fresh**; the forgeax shim does NOT cache across frames.
1055
+ */
1056
+ getCurrentTexture(): Result<Texture, RhiError>;
1057
+ }
1058
+ /** GPU device - sole entry point for resource creation + capability probing. */
1059
+ export interface RhiDevice {
1060
+ /** Hardware-probe layer (charter proposition 5). */
1061
+ readonly caps: RhiCaps;
1062
+ /** Enabled-features layer. */
1063
+ readonly features: RhiFeatures;
1064
+ /** Numeric-limits layer. */
1065
+ readonly limits: RhiLimits;
1066
+ /** Create GPU buffer. */
1067
+ createBuffer(desc: BufferDescriptor): Result<Buffer, RhiError>;
1068
+ /** Create GPU texture. */
1069
+ createTexture(desc: TextureDescriptor): Result<Texture, RhiError>;
1070
+ /**
1071
+ * Destroy a GPU buffer obtained from `createBuffer`.
1072
+ *
1073
+ * Spec anchor: W3C WebGPU §gpubuffer-destroy /
1074
+ * [@webgpu/types.GPUBuffer.destroy]; wgpu wasm
1075
+ * `RhiWgpuBuffer::destroy` (research §F-1; both surfaces are idempotent
1076
+ * void at the underlying GPU).
1077
+ *
1078
+ * The forgeax form prefers fail-fast over the spec idempotent void:
1079
+ * the shim layer (rhi-webgpu / rhi-wgpu) tracks per-handle
1080
+ * `destroyed: boolean` and surfaces a second destroy as
1081
+ * `Result.err({ code: 'destroy-after-destroy' })` rather than silently
1082
+ * succeeding. Double destroy is almost always a lifecycle bug — caching
1083
+ * a stale handle, a forgotten registry slot, a race between dispose
1084
+ * paths — and surfacing it early at the call site is more useful than
1085
+ * swallowing it (plan-strategy D-7 + architecture-principles §5 Fail
1086
+ * Fast). Charter proposition 4 explicit failure.
1087
+ *
1088
+ * Failure paths:
1089
+ * - second destroy on the same handle ->
1090
+ * `Result.err({ code: 'destroy-after-destroy' })`.
1091
+ *
1092
+ * @example
1093
+ * const r = device.destroyBuffer(buf);
1094
+ * if (!r.ok) {
1095
+ * // route via switch (r.error.code)
1096
+ * }
1097
+ */
1098
+ destroyBuffer(buf: Buffer): Result<void, RhiError>;
1099
+ /**
1100
+ * Destroy a GPU query set obtained from `createQuerySet`.
1101
+ *
1102
+ * Query sets are device-owned resources even though the WebGPU surface does
1103
+ * not expose them through the Buffer/Texture families. The explicit RHI
1104
+ * seam lets Render release timestamp query sets exactly once after queue
1105
+ * completion, including failure and disposal paths.
1106
+ */
1107
+ destroyQuerySet(querySet: QuerySet): Result<void, RhiError>;
1108
+ /**
1109
+ * Destroy a GPU texture obtained from `createTexture`.
1110
+ *
1111
+ * Spec anchor: W3C WebGPU §gputexture-destroy /
1112
+ * [@webgpu/types.GPUTexture.destroy]; wgpu wasm idempotent void at the
1113
+ * underlying GPU.
1114
+ *
1115
+ * Same fail-fast contract as `destroyBuffer`: the shim layer tracks
1116
+ * per-handle `destroyed: boolean` and surfaces a second destroy as
1117
+ * `Result.err({ code: 'destroy-after-destroy' })`.
1118
+ *
1119
+ * @example
1120
+ * const r = device.destroyTexture(tex);
1121
+ * if (!r.ok) {
1122
+ * // route via switch (r.error.code)
1123
+ * }
1124
+ */
1125
+ destroyTexture(tex: Texture): Result<void, RhiError>;
1126
+ /**
1127
+ * Create a GPU texture view of an existing texture.
1128
+ *
1129
+ * Spec anchor: W3C WebGPU §texture-view-creation /
1130
+ * [@webgpu/types.GPUTexture.createView].
1131
+ *
1132
+ * Introduced in feat-20260510-rhi-resource-creation (M1). Cross-resource
1133
+ * validation is performed fast-path by the shim before forwarding to raw
1134
+ * GPUTexture.createView (research §1.1):
1135
+ * - `format` must be in `source.format ∪ source.viewFormats`; violation
1136
+ * returns Result.err({ code: 'webgpu-runtime-error' }).
1137
+ * - `usage` must be a subset of source.usage (bitmask); violation returns
1138
+ * the same code.
1139
+ *
1140
+ * @example
1141
+ * const r = device.createTextureView(tex, { format: 'rgba8unorm', dimension: '2d' });
1142
+ * if (!r.ok) {
1143
+ * // route via switch (r.error.code)
1144
+ * }
1145
+ */
1146
+ createTextureView(texture: Texture, desc: TextureViewDescriptor): Result<TextureView, RhiError>;
1147
+ /** Create sampler (spec defaults are applied by the shim). */
1148
+ createSampler(desc?: SamplerDescriptor | undefined): Result<Sampler, RhiError>;
1149
+ /** Create bind group layout. */
1150
+ createBindGroupLayout(desc: BindGroupLayoutDescriptor): Result<BindGroupLayout, RhiError>;
1151
+ /**
1152
+ * Create a bind group (instantiated layout + resource bindings).
1153
+ *
1154
+ * Spec anchor: W3C WebGPU 10 Resource binding /
1155
+ * [@webgpu/types.GPUDevice.createBindGroup].
1156
+ *
1157
+ * Introduced in feat-20260509-ecs-render-bridge-mvp (D-S1) — additive
1158
+ * extension; reuses the existing 17-member `RhiErrorCode` union
1159
+ * ('feature-not-enabled' / 'limit-exceeded' / 'webgpu-runtime-error').
1160
+ * No new error code is introduced (AGENTS.md evolution contract no-op,
1161
+ * breakage list stays empty).
1162
+ *
1163
+ * @example
1164
+ * const out = device.createBindGroup({ label: 'view-bg', layout: bgl, entries: [...] });
1165
+ * if (!out.ok) {
1166
+ * // route via switch (out.error.code)
1167
+ * }
1168
+ */
1169
+ createBindGroup(desc: BindGroupDescriptor): Result<BindGroup, RhiError>;
1170
+ /**
1171
+ * Create a pipeline layout (aggregates BindGroupLayouts).
1172
+ *
1173
+ * Spec anchor: W3C WebGPU 10.3 Pipeline layout /
1174
+ * [@webgpu/types.GPUDevice.createPipelineLayout].
1175
+ *
1176
+ * Introduced in feat-20260509-ecs-render-bridge-mvp (D-S1) — additive
1177
+ * extension; reuses the existing 17-member `RhiErrorCode` union.
1178
+ *
1179
+ * @example
1180
+ * const out = device.createPipelineLayout({ label: 'pbr-pl', bindGroupLayouts: [viewBgl, materialBgl, meshArrayBgl] });
1181
+ * if (!out.ok) {
1182
+ * // route via switch (out.error.code)
1183
+ * }
1184
+ */
1185
+ createPipelineLayout(desc: PipelineLayoutDescriptor): Result<PipelineLayout, RhiError>;
1186
+ /** Create render pipeline (synchronous path). */
1187
+ createRenderPipeline(desc: RenderPipelineDescriptor): Result<RenderPipeline, RhiError>;
1188
+ /**
1189
+ * Create a compute pipeline (synchronous path).
1190
+ *
1191
+ * Spec anchor: W3C WebGPU §compute-pipeline-creation /
1192
+ * [@webgpu/types.GPUDevice.createComputePipeline].
1193
+ *
1194
+ * Introduced in feat-20260510-rhi-resource-creation (M1). Capability gate
1195
+ * (research §1.2 + plan-strategy §4.3 boundary case row 1):
1196
+ * `caps.compute === false` -> Result.err({ code: 'feature-not-enabled' }).
1197
+ * The MVP WebGPU path always has caps.compute=true; the gate exists for
1198
+ * potential future backends that lack compute.
1199
+ *
1200
+ * @example
1201
+ * const r = device.createComputePipeline({
1202
+ * layout: 'auto',
1203
+ * compute: { module, entryPoint: 'cs_main' },
1204
+ * });
1205
+ * if (!r.ok) {
1206
+ * // route via switch (r.error.code)
1207
+ * }
1208
+ */
1209
+ createComputePipeline(desc: ComputePipelineDescriptor): Result<ComputePipeline, RhiError>;
1210
+ /**
1211
+ * Create a query set (occlusion or timestamp).
1212
+ *
1213
+ * Spec anchor: W3C WebGPU §queries / [@webgpu/types.GPUDevice.createQuerySet].
1214
+ *
1215
+ * Introduced in feat-20260510-rhi-resource-creation (M1). Hard constraints
1216
+ * (research §1.3):
1217
+ * - `count <= 4096` (spec normative); violation -> 'limit-exceeded'.
1218
+ * - `type === 'timestamp'` requires caps.timestampQuery; otherwise ->
1219
+ * 'feature-not-enabled'.
1220
+ * - `count = 0` is legal.
1221
+ *
1222
+ * @example
1223
+ * const r = device.createQuerySet({ type: 'occlusion', count: 4 });
1224
+ * if (!r.ok) {
1225
+ * // route via switch (r.error.code)
1226
+ * }
1227
+ */
1228
+ createQuerySet(desc: QuerySetDescriptor): Result<QuerySet, RhiError>;
1229
+ /**
1230
+ * Create a command encoder.
1231
+ *
1232
+ * Spec anchor: W3C WebGPU 21.2 createCommandEncoder /
1233
+ * [@webgpu/types.GPUDevice.createCommandEncoder].
1234
+ *
1235
+ * @example
1236
+ * const encResult = device.createCommandEncoder({ label: 'frame' });
1237
+ * if (!encResult.ok) {
1238
+ * // route via switch (encResult.error.code)
1239
+ * } else {
1240
+ * const enc = encResult.value;
1241
+ * // ... record commands ...
1242
+ * }
1243
+ */
1244
+ createCommandEncoder(desc?: CommandEncoderDescriptor | undefined): Result<RhiCommandEncoder, RhiError>;
1245
+ /** Queue for command submission. */
1246
+ readonly queue: RhiQueue;
1247
+ /**
1248
+ * Spec-style device.lost Promise (research F-4 / R2 mitigation). The engine
1249
+ * layer performs single-source subscription + dual-form fan-out without a
1250
+ * second cache. `reason` is a binary union ('destroyed' / 'unknown').
1251
+ */
1252
+ readonly lost: Promise<{
1253
+ readonly reason: 'destroyed' | 'unknown';
1254
+ readonly message: string;
1255
+ }>;
1256
+ }
1257
+ /** GPU command queue - writeBuffer / submit + M5 writeTexture /
1258
+ * copyExternalImageToTexture / onSubmittedWorkDone. */
1259
+ export interface RhiQueue {
1260
+ /**
1261
+ * Direct write to a buffer (POD + ArrayBufferView, math-free).
1262
+ *
1263
+ * For typed-array sources, dataOffset and size are element counts, matching
1264
+ * GPUQueue.writeBuffer. For ArrayBuffer and DataView sources they are byte
1265
+ * counts. Every backend and the debug tape normalize the selected source
1266
+ * bytes through resolveBufferWriteRange before validation or recording.
1267
+ */
1268
+ writeBuffer(buffer: Buffer, bufferOffset: number, data: ArrayBufferView | ArrayBuffer, dataOffset?: number | undefined, size?: number | undefined): Result<void, RhiError>;
1269
+ /**
1270
+ * Direct write to a texture region.
1271
+ *
1272
+ * Spec anchor: W3C WebGPU §queue-writetexture /
1273
+ * [@webgpu/types.GPUQueue.writeTexture]. Field NAMES align byte-for-byte
1274
+ * with the spec; the forgeax form returns Result<void, RhiError> instead
1275
+ * of void so AI users can route alignment failures (research §1.3 +
1276
+ * plan-strategy 2 K-2: bytesPerRow % 256 != 0 maps to
1277
+ * 'queue-write-buffer-out-of-bounds').
1278
+ *
1279
+ * @example
1280
+ * const out = device.queue.writeTexture(
1281
+ * { texture: tex, mipLevel: 0, origin: [0, 0, 0] },
1282
+ * pixels,
1283
+ * { offset: 0, bytesPerRow: 256, rowsPerImage: H },
1284
+ * { width: W, height: H, depthOrArrayLayers: 1 },
1285
+ * );
1286
+ * if (!out.ok) {
1287
+ * // route via switch (out.error.code)
1288
+ * }
1289
+ */
1290
+ writeTexture(destination: Pick<GPUTexelCopyTextureInfo, 'texture' | 'mipLevel' | 'origin' | 'aspect'>, data: ArrayBufferView | ArrayBuffer, dataLayout: Pick<GPUTexelCopyBufferLayout, 'offset' | 'bytesPerRow' | 'rowsPerImage'>, size: GPUExtent3DStrict): Result<void, RhiError>;
1291
+ /**
1292
+ * Copy an external image source (ImageBitmap / canvas / video) into a
1293
+ * GPUTexture region.
1294
+ *
1295
+ * Spec anchor: W3C WebGPU §queue-copyexternalimagetotexture /
1296
+ * [@webgpu/types.GPUQueue.copyExternalImageToTexture]. The forgeax form
1297
+ * returns Result<void, RhiError>.
1298
+ *
1299
+ * dawn-node note: dawn-node lacks HTMLCanvasElement / VideoFrame /
1300
+ * HTMLImageElement; only the ImageBitmap subset reachable from
1301
+ * createImageBitmap is exercised in dawn tests (research §7.1).
1302
+ */
1303
+ copyExternalImageToTexture(source: Pick<GPUCopyExternalImageSourceInfo, 'source' | 'origin' | 'flipY'>, destination: Pick<GPUCopyExternalImageDestInfo, 'texture' | 'mipLevel' | 'origin' | 'aspect' | 'colorSpace' | 'premultipliedAlpha'>, copySize: GPUExtent3DStrict): Result<void, RhiError>;
1304
+ /** Submit command buffers (single-use). */
1305
+ submit(commandBuffers: readonly CommandBuffer[]): Result<void, RhiError>;
1306
+ /**
1307
+ * Resolve when all currently-enqueued operations have completed.
1308
+ *
1309
+ * Spec anchor: W3C WebGPU §queue-onsubmittedworkdone /
1310
+ * [@webgpu/types.GPUQueue.onSubmittedWorkDone]. Returns
1311
+ * `Promise<undefined>` per spec normative (research §5.1: no reject path;
1312
+ * device-lost flows through `RhiDevice.lost` instead). Ordering
1313
+ * constraints (research §5.2):
1314
+ * - constraint #1 (FIFO): if p1 = q.onSubmittedWorkDone() is called
1315
+ * before p2 = q.onSubmittedWorkDone(), p1 must settle before p2.
1316
+ * - constraint #2 (mapAsync vs onSubmittedWorkDone): if p1 =
1317
+ * b.mapAsync() is called before p2 = q.onSubmittedWorkDone(), p1 must
1318
+ * settle before p2.
1319
+ *
1320
+ * @example Pattern A read-back idiom:
1321
+ * const cb = enc.finish().value;
1322
+ * queue.submit([cb]);
1323
+ * await queue.onSubmittedWorkDone();
1324
+ * await readBuf.mapAsync(GPUMapMode.READ);
1325
+ * const range = readBuf.getMappedRange().value;
1326
+ */
1327
+ onSubmittedWorkDone(): Promise<undefined>;
1328
+ }
1329
+ /** GPU command encoder - records render / compute passes + resource copies.
1330
+ *
1331
+ * Method NAMES align byte-for-byte with `@webgpu/types.GPUCommandEncoder` +
1332
+ * `GPUDebugCommandsMixin` (research F-1 / D-S4). 12 methods total:
1333
+ * - 9 direct: beginRenderPass / beginComputePass / copyBufferToBuffer /
1334
+ * copyBufferToTexture / copyTextureToBuffer / copyTextureToTexture /
1335
+ * clearBuffer / resolveQuerySet / finish
1336
+ * - 3 mixin (GPUDebugCommandsMixin): pushDebugGroup / popDebugGroup /
1337
+ * insertDebugMarker
1338
+ *
1339
+ * Lifecycle: after `finish()`, all subsequent recording calls return
1340
+ * Result.err({ code: 'command-encoder-finished' }) per D-S3 template 1
1341
+ * (where the method returns Result; void-returning methods throw the
1342
+ * structured error so AI users observe the failure consistently).
1343
+ */
1344
+ export interface RhiCommandEncoder {
1345
+ /** Begin render pass (auto-closes on end()).
1346
+ *
1347
+ * Spec anchor: [@webgpu/types.GPUCommandEncoder.beginRenderPass].
1348
+ *
1349
+ * @throws RhiError code === 'command-encoder-finished' when invoked on a finished encoder (dual-channel: spec-aligned void return throws on finished state; AI users wrap call sites with `try / catch (e: unknown) { if (e instanceof RhiError && e.code === 'command-encoder-finished') ... }`).
1350
+ * @example
1351
+ * const pass = encoder.beginRenderPass({ colorAttachments: [{ ... }] });
1352
+ */
1353
+ beginRenderPass(desc: GPURenderPassDescriptor): RhiRenderPassEncoder;
1354
+ /** Begin compute pass.
1355
+ *
1356
+ * Spec anchor: [@webgpu/types.GPUCommandEncoder.beginComputePass].
1357
+ *
1358
+ * @throws RhiError code === 'command-encoder-finished' when invoked on a finished encoder (dual-channel: see beginRenderPass for the recovery pattern).
1359
+ * @example
1360
+ * const pass = encoder.beginComputePass();
1361
+ */
1362
+ beginComputePass(desc?: ComputePassDescriptor | undefined): RhiComputePassEncoder;
1363
+ /** Copy a sub-region of a Buffer to another Buffer (5-arg full form).
1364
+ *
1365
+ * Spec anchor: [@webgpu/types.GPUCommandEncoder.copyBufferToBuffer].
1366
+ *
1367
+ * @throws RhiError code === 'command-encoder-finished' when invoked on a finished encoder (dual-channel; see beginRenderPass JSDoc for the recovery pattern). Both overloads share the same throw contract.
1368
+ * @example
1369
+ * encoder.copyBufferToBuffer(src, 0, dst, 0, 256);
1370
+ */
1371
+ copyBufferToBuffer(source: Buffer, sourceOffset: number, destination: Buffer, destinationOffset: number, size: number): void;
1372
+ /** Copy a Buffer to another Buffer (3-arg shorthand).
1373
+ *
1374
+ * Spec anchor: [@webgpu/types.GPUCommandEncoder.copyBufferToBuffer].
1375
+ *
1376
+ * @throws RhiError code === 'command-encoder-finished' when invoked on a finished encoder (dual-channel; see beginRenderPass JSDoc for the recovery pattern). Both overloads share the same throw contract.
1377
+ * @example
1378
+ * encoder.copyBufferToBuffer(src, dst, 256);
1379
+ */
1380
+ copyBufferToBuffer(source: Buffer, destination: Buffer, size?: number | undefined): void;
1381
+ /** Copy a Buffer sub-region to a Texture sub-region.
1382
+ *
1383
+ * Spec anchor: [@webgpu/types.GPUCommandEncoder.copyBufferToTexture].
1384
+ *
1385
+ * @throws RhiError code === 'command-encoder-finished' when invoked on a finished encoder (dual-channel; see beginRenderPass JSDoc for the recovery pattern).
1386
+ * @example
1387
+ * encoder.copyBufferToTexture(srcInfo, dstInfo, [w, h, 1]);
1388
+ */
1389
+ copyBufferToTexture(source: GPUTexelCopyBufferInfo, destination: GPUTexelCopyTextureInfo, copySize: GPUExtent3DStrict): void;
1390
+ /** Copy a Texture sub-region to a Buffer sub-region.
1391
+ *
1392
+ * Spec anchor: [@webgpu/types.GPUCommandEncoder.copyTextureToBuffer].
1393
+ *
1394
+ * @throws RhiError code === 'command-encoder-finished' when invoked on a finished encoder (dual-channel; see beginRenderPass JSDoc for the recovery pattern).
1395
+ * @example
1396
+ * encoder.copyTextureToBuffer(srcInfo, dstInfo, [w, h, 1]);
1397
+ */
1398
+ copyTextureToBuffer(source: GPUTexelCopyTextureInfo, destination: GPUTexelCopyBufferInfo, copySize: GPUExtent3DStrict): void;
1399
+ /** Copy a Texture sub-region to a Texture sub-region.
1400
+ *
1401
+ * Spec anchor: [@webgpu/types.GPUCommandEncoder.copyTextureToTexture].
1402
+ *
1403
+ * @throws RhiError code === 'command-encoder-finished' when invoked on a finished encoder (dual-channel; see beginRenderPass JSDoc for the recovery pattern).
1404
+ * @example
1405
+ * encoder.copyTextureToTexture(srcInfo, dstInfo, [w, h, 1]);
1406
+ */
1407
+ copyTextureToTexture(source: GPUTexelCopyTextureInfo, destination: GPUTexelCopyTextureInfo, copySize: GPUExtent3DStrict): void;
1408
+ /** Fill a Buffer sub-region with zeros.
1409
+ *
1410
+ * Spec anchor: [@webgpu/types.GPUCommandEncoder.clearBuffer].
1411
+ *
1412
+ * @throws RhiError code === 'command-encoder-finished' when invoked on a finished encoder (dual-channel; see beginRenderPass JSDoc for the recovery pattern).
1413
+ * @example
1414
+ * encoder.clearBuffer(buf, 0, 256);
1415
+ */
1416
+ clearBuffer(buffer: Buffer, offset?: number | undefined, size?: number | undefined): void;
1417
+ /** Resolve query results from a QuerySet to a Buffer.
1418
+ *
1419
+ * Real implementation (M3 / w26): writes 8-byte query results in 256-byte
1420
+ * aligned strides into `destination` starting at `destinationOffset`.
1421
+ * Validates `destination.usage & GPUBufferUsage.QUERY_RESOLVE` and the
1422
+ * 256-byte alignment up front; on misuse returns
1423
+ * `Result.err({ code: 'webgpu-runtime-error', ... })` with a structured
1424
+ * .expected / .hint pair.
1425
+ *
1426
+ * Spec anchor: [@webgpu/types.GPUCommandEncoder.resolveQuerySet].
1427
+ *
1428
+ * @example
1429
+ * const out = encoder.resolveQuerySet(qs, 0, 4, dstBuf, 0);
1430
+ * if (!out.ok) {
1431
+ * // switch (out.error.code) { case 'webgpu-runtime-error': ... }
1432
+ * }
1433
+ */
1434
+ resolveQuerySet(querySet: QuerySet, firstQuery: number, queryCount: number, destination: Buffer, destinationOffset: number): Result<void, RhiError>;
1435
+ /**
1436
+ * Write a u64 GPU timestamp into a timestamp QuerySet at queryIndex.
1437
+ *
1438
+ * Spec anchor: W3C WebGPU §queries / [@webgpu/types.GPUCommandEncoder].
1439
+ * dawn `TimestampOnCommandEncoder` reference (research §2.4): the entry is
1440
+ * gated on the `'timestamp-query'` device feature. K-3 decision
1441
+ * (plan-strategy §2): the forgeax form ships ONLY this CommandEncoder
1442
+ * entry; CPE/RPE inside-pass timestamp writes are deferred to
1443
+ * `feat-future-rhi-perf-timestamp-pass` (additive minor evolution).
1444
+ *
1445
+ * Return shape: `void` per spec literal alignment. When
1446
+ * `caps.timestampQuery === false` the shim fans out a structured
1447
+ * `'feature-not-enabled'` RhiError through the engine `onError` channel
1448
+ * (the forgeax form keeps the spec void return; AI users probe the gate
1449
+ * via `device.caps.timestampQuery` BEFORE calling this entry, charter
1450
+ * proposition 4 explicit failure forward-reachable).
1451
+ *
1452
+ * @throws RhiError code === 'command-encoder-finished' when invoked on a finished encoder (dual-channel; see beginRenderPass JSDoc for the recovery pattern).
1453
+ * @example
1454
+ * if (device.caps.timestampQuery) {
1455
+ * encoder.writeTimestamp(qs, 0);
1456
+ * // ... draw / dispatch ...
1457
+ * encoder.writeTimestamp(qs, 1);
1458
+ * }
1459
+ */
1460
+ writeTimestamp(querySet: QuerySet, queryIndex: number): void;
1461
+ /** Push a labelled debug group (GPUDebugCommandsMixin).
1462
+ *
1463
+ * Spec anchor: [@webgpu/types.GPUDebugCommandsMixin.pushDebugGroup].
1464
+ *
1465
+ * @note silent delegate to raw GPU encoder on finished encoder; matches W3C spec lenience (the debug-commands mixin permits a no-op pass-through after finish() so the signal stays real instead of forging a `@throws` contract; charter proposition 4).
1466
+ * @example
1467
+ * encoder.pushDebugGroup('frame-setup');
1468
+ */
1469
+ pushDebugGroup(groupLabel: string): void;
1470
+ /** Pop the most recent debug group (GPUDebugCommandsMixin).
1471
+ *
1472
+ * Spec anchor: [@webgpu/types.GPUDebugCommandsMixin.popDebugGroup].
1473
+ *
1474
+ * @note silent delegate to raw GPU encoder on finished encoder; matches W3C spec lenience (see pushDebugGroup JSDoc for the dual-channel rationale).
1475
+ * @example
1476
+ * encoder.popDebugGroup();
1477
+ */
1478
+ popDebugGroup(): void;
1479
+ /** Insert a labelled debug marker (GPUDebugCommandsMixin).
1480
+ *
1481
+ * Spec anchor: [@webgpu/types.GPUDebugCommandsMixin.insertDebugMarker].
1482
+ *
1483
+ * @note silent delegate to raw GPU encoder on finished encoder; matches W3C spec lenience (see pushDebugGroup JSDoc for the dual-channel rationale).
1484
+ * @example
1485
+ * encoder.insertDebugMarker('post-resolve');
1486
+ */
1487
+ insertDebugMarker(markerLabel: string): void;
1488
+ /** Finish recording -> CommandBuffer. After finish() any further recording
1489
+ * call returns Result.err({ code: 'command-encoder-finished' }).
1490
+ *
1491
+ * Spec anchor: [@webgpu/types.GPUCommandEncoder.finish].
1492
+ *
1493
+ * @example
1494
+ * const cb = encoder.finish();
1495
+ * if (cb.ok) device.queue.submit([cb.value]);
1496
+ */
1497
+ finish(): Result<CommandBuffer, RhiError>;
1498
+ }
1499
+ /** GPU render pass encoder - records draw calls + state changes.
1500
+ *
1501
+ * Method NAMES align byte-for-byte with @webgpu/types.GPURenderPassEncoder +
1502
+ * GPURenderCommandsMixin + GPUBindingCommandsMixin + GPUDebugCommandsMixin
1503
+ * (research F-2 / D-S4): 17 spec stable + 1 setBindGroup overload + 1
1504
+ * remaining capability-gated placeholder (`executeBundles` returns
1505
+ * Result.err({ code: 'rhi-not-available', hint: 'see
1506
+ * feat-future-rhi-render-bundle' })). The 2 occlusion-query methods
1507
+ * (`beginOcclusionQuery` / `endOcclusionQuery`) shipped real implementations
1508
+ * in M3 (w23) backed by `RenderPassDescriptor.occlusionQuerySet`.
1509
+ *
1510
+ * `setImmediates` (PROPOSED) is intentionally NOT exposed (charter
1511
+ * proposition 4: untested features hide behind caps, not surfaces).
1512
+ *
1513
+ * Lifecycle: encoder.finish() while a pass is unfinished returns
1514
+ * Result.err({ code: 'render-pass-not-ended' }) per D-S3 template 2.
1515
+ */
1516
+ export interface RhiRenderPassEncoder {
1517
+ /** Set the bound render pipeline.
1518
+ *
1519
+ * Spec anchor: [@webgpu/types.GPURenderCommandsMixin.setPipeline].
1520
+ *
1521
+ * @example pass.setPipeline(pipeline);
1522
+ */
1523
+ setPipeline(pipeline: RenderPipeline): void;
1524
+ /** Set a vertex buffer.
1525
+ *
1526
+ * Spec anchor: [@webgpu/types.GPURenderCommandsMixin.setVertexBuffer].
1527
+ *
1528
+ * @example pass.setVertexBuffer(0, vbo);
1529
+ */
1530
+ setVertexBuffer(slot: number, buffer: Buffer, offset?: number | undefined, size?: number | undefined): void;
1531
+ /** Set the index buffer.
1532
+ *
1533
+ * Spec anchor: [@webgpu/types.GPURenderCommandsMixin.setIndexBuffer].
1534
+ *
1535
+ * @example pass.setIndexBuffer(ibo, 'uint32');
1536
+ */
1537
+ setIndexBuffer(buffer: Buffer, format: 'uint16' | 'uint32', offset?: number | undefined, size?: number | undefined): void;
1538
+ /** Set a bind group with optional dynamic offsets array (overload (a)).
1539
+ *
1540
+ * Spec anchor: [@webgpu/types.GPUBindingCommandsMixin.setBindGroup].
1541
+ *
1542
+ * @example pass.setBindGroup(0, bg, [0, 256]);
1543
+ */
1544
+ setBindGroup(index: number, bindGroup: BindGroup, dynamicOffsets?: readonly number[] | undefined): void;
1545
+ /** Set a bind group with a Uint32Array slice for dynamic offsets (overload (b)).
1546
+ *
1547
+ * Spec anchor: [@webgpu/types.GPUBindingCommandsMixin.setBindGroup].
1548
+ *
1549
+ * @example
1550
+ * pass.setBindGroup(0, bg, dynamicOffsetsData, dynamicOffsetsDataStart, dynamicOffsetsDataLength);
1551
+ */
1552
+ setBindGroup(index: number, bindGroup: BindGroup, dynamicOffsetsData: Uint32Array, dynamicOffsetsDataStart: number, dynamicOffsetsDataLength: number): void;
1553
+ /** Issue a draw.
1554
+ *
1555
+ * Spec anchor: [@webgpu/types.GPURenderCommandsMixin.draw].
1556
+ *
1557
+ * @example pass.draw(3);
1558
+ */
1559
+ draw(vertexCount: number, instanceCount?: number | undefined, firstVertex?: number | undefined, firstInstance?: number | undefined): void;
1560
+ /** Issue an indexed draw.
1561
+ *
1562
+ * Spec anchor: [@webgpu/types.GPURenderCommandsMixin.drawIndexed].
1563
+ *
1564
+ * @example pass.drawIndexed(36);
1565
+ */
1566
+ drawIndexed(indexCount: number, instanceCount?: number | undefined, firstIndex?: number | undefined, baseVertex?: number | undefined, firstInstance?: number | undefined): void;
1567
+ /** End the render pass.
1568
+ *
1569
+ * Spec anchor: [@webgpu/types.GPURenderPassEncoder.end].
1570
+ *
1571
+ * @example pass.end();
1572
+ */
1573
+ end(): void;
1574
+ /** Set the viewport.
1575
+ *
1576
+ * Spec anchor: [@webgpu/types.GPURenderPassEncoder.setViewport].
1577
+ *
1578
+ * @example pass.setViewport(0, 0, 800, 600, 0, 1);
1579
+ */
1580
+ setViewport(x: number, y: number, w: number, h: number, minDepth: number, maxDepth: number): void;
1581
+ /** Set the scissor rect.
1582
+ *
1583
+ * Spec anchor: [@webgpu/types.GPURenderPassEncoder.setScissorRect].
1584
+ *
1585
+ * @example pass.setScissorRect(0, 0, 800, 600);
1586
+ */
1587
+ setScissorRect(x: number, y: number, w: number, h: number): void;
1588
+ /** Set the blend constant color.
1589
+ *
1590
+ * Spec anchor: [@webgpu/types.GPURenderPassEncoder.setBlendConstant].
1591
+ *
1592
+ * @example pass.setBlendConstant({ r: 1, g: 0, b: 0, a: 1 });
1593
+ */
1594
+ setBlendConstant(color: GPUColor): void;
1595
+ /** Set the stencil reference value.
1596
+ *
1597
+ * Spec anchor: [@webgpu/types.GPURenderPassEncoder.setStencilReference].
1598
+ *
1599
+ * @example pass.setStencilReference(0xff);
1600
+ */
1601
+ setStencilReference(reference: number): void;
1602
+ /** Issue an indirect draw.
1603
+ *
1604
+ * Spec anchor: [@webgpu/types.GPURenderCommandsMixin.drawIndirect].
1605
+ *
1606
+ * @example pass.drawIndirect(indirectBuf, 0);
1607
+ */
1608
+ drawIndirect(indirectBuffer: Buffer, indirectOffset: number): void;
1609
+ /** Issue an indexed indirect draw.
1610
+ *
1611
+ * Spec anchor: [@webgpu/types.GPURenderCommandsMixin.drawIndexedIndirect].
1612
+ *
1613
+ * @example pass.drawIndexedIndirect(indirectBuf, 0);
1614
+ */
1615
+ drawIndexedIndirect(indirectBuffer: Buffer, indirectOffset: number): void;
1616
+ /** Push a labelled debug group (GPUDebugCommandsMixin).
1617
+ *
1618
+ * Spec anchor: [@webgpu/types.GPUDebugCommandsMixin.pushDebugGroup].
1619
+ *
1620
+ * @note silent delegate to raw GPU encoder on finished encoder; matches W3C spec lenience (the debug-commands mixin permits a no-op pass-through after finish() so the signal stays real instead of forging a `@throws` contract; charter proposition 4).
1621
+ * @example pass.pushDebugGroup('lighting');
1622
+ */
1623
+ pushDebugGroup(groupLabel: string): void;
1624
+ /** Pop the most recent debug group (GPUDebugCommandsMixin).
1625
+ *
1626
+ * Spec anchor: [@webgpu/types.GPUDebugCommandsMixin.popDebugGroup].
1627
+ *
1628
+ * @note silent delegate to raw GPU encoder on finished encoder; matches W3C spec lenience (see pushDebugGroup JSDoc for the dual-channel rationale).
1629
+ * @example pass.popDebugGroup();
1630
+ */
1631
+ popDebugGroup(): void;
1632
+ /** Insert a labelled debug marker (GPUDebugCommandsMixin).
1633
+ *
1634
+ * Spec anchor: [@webgpu/types.GPUDebugCommandsMixin.insertDebugMarker].
1635
+ *
1636
+ * @note silent delegate to raw GPU encoder on finished encoder; matches W3C spec lenience (see pushDebugGroup JSDoc for the dual-channel rationale).
1637
+ * @example pass.insertDebugMarker('post-shadow');
1638
+ */
1639
+ insertDebugMarker(markerLabel: string): void;
1640
+ /** Execute render bundles. Capability-gated placeholder per D-S4: returns
1641
+ * Result.err({ code: 'rhi-not-available', hint: 'see feat-future-rhi-render-bundle' })
1642
+ * until that closure lands RenderBundle creation.
1643
+ *
1644
+ * Spec anchor: [@webgpu/types.GPURenderPassEncoder.executeBundles].
1645
+ *
1646
+ * @example
1647
+ * const out = pass.executeBundles([bundle]);
1648
+ * if (!out.ok) { ... route via switch (out.error.code) ... }
1649
+ */
1650
+ executeBundles(bundles: Iterable<unknown>): Result<void, RhiError>;
1651
+ /** Begin an occlusion query. Real implementation (M3 / w23): pairs with
1652
+ * `endOcclusionQuery()` against the `RenderPassDescriptor.occlusionQuerySet`
1653
+ * and validates the spec [[occlusion_query_active]] state machine
1654
+ * (queries cannot nest; missing occlusionQuerySet returns
1655
+ * Result.err({ code: 'webgpu-runtime-error' }) with structured
1656
+ * .expected / .hint fields).
1657
+ *
1658
+ * Spec anchor: [@webgpu/types.GPURenderPassEncoder.beginOcclusionQuery].
1659
+ *
1660
+ * @example
1661
+ * const out = pass.beginOcclusionQuery(0);
1662
+ * if (!out.ok) { ... route via switch (out.error.code) ... }
1663
+ */
1664
+ beginOcclusionQuery(queryIndex: number): Result<void, RhiError>;
1665
+ /** End an occlusion query. Real implementation (M3 / w23): finalizes the
1666
+ * matching `beginOcclusionQuery(idx)` slot; emits
1667
+ * Result.err({ code: 'render-pass-not-ended', ... }) if no active begin
1668
+ * is pending.
1669
+ *
1670
+ * Spec anchor: [@webgpu/types.GPURenderPassEncoder.endOcclusionQuery].
1671
+ *
1672
+ * @example
1673
+ * const out = pass.endOcclusionQuery();
1674
+ * if (!out.ok) { ... route via switch (out.error.code) ... }
1675
+ */
1676
+ endOcclusionQuery(): Result<void, RhiError>;
1677
+ }
1678
+ /** GPU compute pass encoder - records dispatch calls. */
1679
+ export interface RhiComputePassEncoder {
1680
+ setPipeline(pipeline: ComputePipeline): void;
1681
+ setBindGroup(index: number, bindGroup: BindGroup, dynamicOffsets?: readonly number[] | undefined): void;
1682
+ dispatchWorkgroups(x: number, y?: number | undefined, z?: number | undefined): void;
1683
+ /** Dispatch dimensions read from three consecutive u32 values in an indirect buffer. */
1684
+ dispatchWorkgroupsIndirect(indirectBuffer: Buffer, indirectOffset: number): void;
1685
+ end(): void;
1686
+ }
1687
+ /** GPU render pipeline operations - returned by RhiDevice.createRenderPipeline. */
1688
+ export interface RhiRenderPipelineOps {
1689
+ /** Get bind group layout (used for dynamic bind-group creation). */
1690
+ getBindGroupLayout(index: number): BindGroupLayout;
1691
+ }
1692
+ /** GPU compute pipeline operations. */
1693
+ export interface RhiComputePipelineOps {
1694
+ /** Get bind group layout. */
1695
+ getBindGroupLayout(index: number): BindGroupLayout;
1696
+ }
1697
+ export type { DrawOwnerSplit, LimitExceededDetail, Result, ResultErr, ResultOk, RhiAssetNotRegisteredDetail, RhiErrorCode, RhiErrorDetail, RhiOwnerOutOfRangeDetail, RhiShaderCompileDetail, RhiWebgpuRuntimeDetail, } from './errors';
1698
+ export { err, ok, RhiError, validateDrawArgs } from './errors';
1699
+ export type { AddressMode, CompareFunction, FilterMode, TextureFormat };
1700
+ export interface Rgba16floatRenderTargetProbe {
1701
+ readonly format: 'rgba16float';
1702
+ readonly pixel: readonly [number, number, number, number];
1703
+ readonly timestampQuery: boolean;
1704
+ }
1705
+ /** Execute an rgba16float attachment clear and texture-to-buffer readback on a live device. */
1706
+ export declare function probeRgba16floatRenderTarget(device: RhiDevice): Promise<Result<Rgba16floatRenderTargetProbe, RhiError>>;
1707
+ /**
1708
+ * Build a TextureDescriptor for a cube-array depth texture suitable for
1709
+ * point-light shadow atlas (texture_depth_cube_array).
1710
+ *
1711
+ * Usage: `device.createTexture(cubeArrayDepthDescriptor(512, 4))`
1712
+ *
1713
+ * @param faceSize - width and height of each cube face in pixels (default 512)
1714
+ * @param layers - number of cube layers (= max shadow-casting point lights, default 4)
1715
+ * @param usage - texture usage flags (default RENDER_ATTACHMENT | TEXTURE_BINDING)
1716
+ */
1717
+ export declare function cubeArrayDepthDescriptor(faceSize?: number, layers?: number, usage?: number): TextureDescriptor;
1718
+ /**
1719
+ * Build a SamplerDescriptor for a depth-comparison sampler used with
1720
+ * texture_depth_2d or texture_depth_cube_array.
1721
+ *
1722
+ * The returned descriptor uses clamp-to-edge addressing, linear filtering
1723
+ * (required for comparison sampling on some backends), and `compare: 'less'`.
1724
+ *
1725
+ * Usage: `device.createSampler(comparisonSamplerDescriptor())`
1726
+ */
1727
+ export declare function comparisonSamplerDescriptor(): SamplerDescriptor;
1728
+ export { type BufferWriteRange, resolveBufferWriteRange } from './queue-writes';
1729
+ /**
1730
+ * Build a TextureViewDescriptor to view a single cube layer + face as a 2D
1731
+ * depth attachment during shadow-caster rendering.
1732
+ *
1733
+ * Each shadow-caster pass renders to one face of one cube layer. This helper
1734
+ * produces the view descriptor that selects `baseArrayLayer = layerIndex * 6 + faceIndex`
1735
+ * with `arrayLayerCount = 1` and `dimension = '2d'`, which satisfies WebGPU's
1736
+ * requirement that render pass attachments are 2D views (cube views cannot be
1737
+ * bound as render targets).
1738
+ *
1739
+ * Usage:
1740
+ * ```
1741
+ * const viewDesc = cubeArrayDepthFaceView(lightIndex, faceIndex);
1742
+ * const view = device.createTextureView(atlas, viewDesc).unwrap();
1743
+ * ```
1744
+ *
1745
+ * @param layerIndex - 0-based shadow-casting light index (0..3)
1746
+ * @param faceIndex - 0-based cube face index (0..5, +X/-X/+Y/-Y/+Z/-Z per §5.3)
1747
+ */
1748
+ export declare function cubeArrayDepthFaceView(layerIndex: number, faceIndex: number): TextureViewDescriptor;
1749
+ //# sourceMappingURL=index.d.ts.map