@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.
- package/LICENSE +202 -0
- package/README.md +234 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/async-form.test-d.d.ts +2 -0
- package/dist/__tests__/async-form.test-d.d.ts.map +1 -0
- package/dist/__tests__/binding-resource.test-d.d.ts +2 -0
- package/dist/__tests__/binding-resource.test-d.d.ts.map +1 -0
- package/dist/__tests__/buffer-mapping.test-d.d.ts +2 -0
- package/dist/__tests__/buffer-mapping.test-d.d.ts.map +1 -0
- package/dist/__tests__/canvasContext.test-d.d.ts +2 -0
- package/dist/__tests__/canvasContext.test-d.d.ts.map +1 -0
- package/dist/__tests__/caps-three-way.unit.test.d.ts +2 -0
- package/dist/__tests__/caps-three-way.unit.test.d.ts.map +1 -0
- package/dist/__tests__/caps.test-d.d.ts +2 -0
- package/dist/__tests__/caps.test-d.d.ts.map +1 -0
- package/dist/__tests__/command-encoder-spec-alignment.test-d.d.ts +2 -0
- package/dist/__tests__/command-encoder-spec-alignment.test-d.d.ts.map +1 -0
- package/dist/__tests__/createComputePipeline.test-d.d.ts +2 -0
- package/dist/__tests__/createComputePipeline.test-d.d.ts.map +1 -0
- package/dist/__tests__/createQuerySet.test-d.d.ts +2 -0
- package/dist/__tests__/createQuerySet.test-d.d.ts.map +1 -0
- package/dist/__tests__/createTextureView.test-d.d.ts +2 -0
- package/dist/__tests__/createTextureView.test-d.d.ts.map +1 -0
- package/dist/__tests__/descriptor-mirror.test-d.d.ts +2 -0
- package/dist/__tests__/descriptor-mirror.test-d.d.ts.map +1 -0
- package/dist/__tests__/draw-validation-errors.test.d.ts +2 -0
- package/dist/__tests__/draw-validation-errors.test.d.ts.map +1 -0
- package/dist/__tests__/errors-instancing.unit.test.d.ts +2 -0
- package/dist/__tests__/errors-instancing.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test-d.d.ts +2 -0
- package/dist/__tests__/errors.test-d.d.ts.map +1 -0
- package/dist/__tests__/limit-exceeded-detail.test-d.d.ts +2 -0
- package/dist/__tests__/limit-exceeded-detail.test-d.d.ts.map +1 -0
- package/dist/__tests__/mapped-buffer.test-d.d.ts +2 -0
- package/dist/__tests__/mapped-buffer.test-d.d.ts.map +1 -0
- package/dist/__tests__/opaque-handle.test-d.d.ts +2 -0
- package/dist/__tests__/opaque-handle.test-d.d.ts.map +1 -0
- package/dist/__tests__/owner-out-of-range-role.test.d.ts +2 -0
- package/dist/__tests__/owner-out-of-range-role.test.d.ts.map +1 -0
- package/dist/__tests__/queue-writes.test-d.d.ts +2 -0
- package/dist/__tests__/queue-writes.test-d.d.ts.map +1 -0
- package/dist/__tests__/queue-writes.unit.test.d.ts +2 -0
- package/dist/__tests__/queue-writes.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-pass-encoder-spec-alignment.test-d.d.ts +2 -0
- package/dist/__tests__/render-pass-encoder-spec-alignment.test-d.d.ts.map +1 -0
- package/dist/__tests__/requestAdapter.test-d.d.ts +2 -0
- package/dist/__tests__/requestAdapter.test-d.d.ts.map +1 -0
- package/dist/errors.d.ts +315 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.mjs +56 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.d.ts +1749 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +256 -0
- package/dist/index.mjs.map +1 -0
- package/dist/queue-writes.d.ts +20 -0
- package/dist/queue-writes.d.ts.map +1 -0
- package/package.json +61 -0
- package/src/__tests__/async-form.test-d.ts +40 -0
- package/src/__tests__/binding-resource.test-d.ts +66 -0
- package/src/__tests__/buffer-mapping.test-d.ts +84 -0
- package/src/__tests__/canvasContext.test-d.ts +106 -0
- package/src/__tests__/caps-three-way.unit.test.ts +144 -0
- package/src/__tests__/caps.test-d.ts +83 -0
- package/src/__tests__/command-encoder-spec-alignment.test-d.ts +117 -0
- package/src/__tests__/createComputePipeline.test-d.ts +149 -0
- package/src/__tests__/createQuerySet.test-d.ts +81 -0
- package/src/__tests__/createTextureView.test-d.ts +127 -0
- package/src/__tests__/descriptor-mirror.test-d.ts +267 -0
- package/src/__tests__/draw-validation-errors.test.ts +155 -0
- package/src/__tests__/errors-instancing.unit.test.ts +167 -0
- package/src/__tests__/errors.test-d.ts +199 -0
- package/src/__tests__/limit-exceeded-detail.test-d.ts +33 -0
- package/src/__tests__/mapped-buffer.test-d.ts +65 -0
- package/src/__tests__/opaque-handle.test-d.ts +121 -0
- package/src/__tests__/owner-out-of-range-role.test.ts +84 -0
- package/src/__tests__/queue-writes.test-d.ts +107 -0
- package/src/__tests__/queue-writes.unit.test.ts +30 -0
- package/src/__tests__/render-pass-encoder-spec-alignment.test-d.ts +202 -0
- package/src/__tests__/requestAdapter.test-d.ts +123 -0
- package/src/errors.ts +504 -0
- package/src/index.ts +2398 -0
- package/src/queue-writes.ts +62 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// AC-08 (Q5) — RhiQueue.writeTexture + copyExternalImageToTexture Pick<spec>
|
|
2
|
+
// narrow type-layer assertions.
|
|
3
|
+
//
|
|
4
|
+
// D-P5 (plan-strategy §spec-rename) — the spec evolved field-info wrappers;
|
|
5
|
+
// pre-rename names (authored Jan 2024, retired Apr 2026) are no longer
|
|
6
|
+
// present in @webgpu/types 0.1.69 and the rhi interface now uses the
|
|
7
|
+
// authoritative new names (GPUCopyExternalImageSourceInfo +
|
|
8
|
+
// GPUCopyExternalImageDestInfo per W3C WebGPU CR Apr 2026); any regression
|
|
9
|
+
// would surface as TS2304 at type lookup. Historical rename context lives
|
|
10
|
+
// in .forgeax-harness/forgeax-loop/feat-20260511-asset-system-v1/research.md
|
|
11
|
+
// D-P5 + requirements research Finding 1(a) + Finding 3(a); the pre-rename
|
|
12
|
+
// names are deliberately NOT quoted here so the `GPUImg*` grep gate
|
|
13
|
+
// (feat-20260511-asset-system-v1 / w32) returns 0 hits repo-wide.
|
|
14
|
+
//
|
|
15
|
+
// w17 contract:
|
|
16
|
+
// (a) writeTexture(destination, data, dataLayout, size) where:
|
|
17
|
+
// destination is Pick<GPUTexelCopyTextureInfo, ...> (NOT verbatim spec);
|
|
18
|
+
// dataLayout is Pick<GPUTexelCopyBufferLayout, ...>.
|
|
19
|
+
// (b) copyExternalImageToTexture(source, destination, copySize) where:
|
|
20
|
+
// source is Pick<GPUCopyExternalImageSourceInfo, ...> (spec rename);
|
|
21
|
+
// destination is Pick<GPUCopyExternalImageDestInfo, ...> (spec rename).
|
|
22
|
+
// (c) r12-lint Pick<GPU*Descriptor count >= 20 follow-on (verified by w22
|
|
23
|
+
// gate, not this test-d).
|
|
24
|
+
//
|
|
25
|
+
// Charter: proposition 4 (explicit failure - excess fields trip TS2375 at the
|
|
26
|
+
// call site) + proposition 5 (consistent abstraction - dual backends mirror
|
|
27
|
+
// the same Pick<> narrowing). Anchors: requirements AC-08; research R-04
|
|
28
|
+
// §4.1-§4.3; plan-strategy D-P5 spec rename critical fact.
|
|
29
|
+
|
|
30
|
+
/// <reference types="@webgpu/types" />
|
|
31
|
+
|
|
32
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
33
|
+
import type { Buffer, Result, RhiError, RhiQueue, Texture, TextureView } from '../index';
|
|
34
|
+
|
|
35
|
+
/** Strict structural equality helper. */
|
|
36
|
+
type Equals<X, Y> =
|
|
37
|
+
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
|
|
38
|
+
|
|
39
|
+
describe('AC-08 — RhiQueue.writeTexture Pick<spec> narrow', () => {
|
|
40
|
+
it('writeTexture method exists on RhiQueue', () => {
|
|
41
|
+
type Method = RhiQueue['writeTexture'];
|
|
42
|
+
expectTypeOf<Method>().toBeFunction();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('writeTexture destination param strictly equals Pick<GPUTexelCopyTextureInfo, ...> (no extra fields)', () => {
|
|
46
|
+
type DestParam = Parameters<RhiQueue['writeTexture']>[0];
|
|
47
|
+
type ExpectedShape = Pick<
|
|
48
|
+
GPUTexelCopyTextureInfo,
|
|
49
|
+
'texture' | 'mipLevel' | 'origin' | 'aspect'
|
|
50
|
+
>;
|
|
51
|
+
// Strict equality — DestParam has EXACTLY the four spec fields. AI users
|
|
52
|
+
// passing extra forgeax-invented fields trip TS2375 at call site;
|
|
53
|
+
// missing required `texture` field trips TS2741.
|
|
54
|
+
type IsExact = Equals<DestParam, ExpectedShape>;
|
|
55
|
+
expectTypeOf<IsExact>().toEqualTypeOf<true>();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('writeTexture dataLayout param strictly equals Pick<GPUTexelCopyBufferLayout, ...>', () => {
|
|
59
|
+
type LayoutParam = Parameters<RhiQueue['writeTexture']>[2];
|
|
60
|
+
type ExpectedShape = Pick<GPUTexelCopyBufferLayout, 'offset' | 'bytesPerRow' | 'rowsPerImage'>;
|
|
61
|
+
type IsExact = Equals<LayoutParam, ExpectedShape>;
|
|
62
|
+
expectTypeOf<IsExact>().toEqualTypeOf<true>();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('writeTexture returns Result<void, RhiError>', () => {
|
|
66
|
+
type Ret = ReturnType<RhiQueue['writeTexture']>;
|
|
67
|
+
type IsResult = Equals<Ret, Result<void, RhiError>>;
|
|
68
|
+
expectTypeOf<IsResult>().toEqualTypeOf<true>();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('AC-08 — RhiQueue.copyExternalImageToTexture Pick<spec> narrow + spec rename', () => {
|
|
73
|
+
it('copyExternalImageToTexture method exists on RhiQueue', () => {
|
|
74
|
+
type Method = RhiQueue['copyExternalImageToTexture'];
|
|
75
|
+
expectTypeOf<Method>().toBeFunction();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('source param strictly equals Pick<GPUCopyExternalImageSourceInfo, ...> (spec rename D-P5)', () => {
|
|
79
|
+
type SourceParam = Parameters<RhiQueue['copyExternalImageToTexture']>[0];
|
|
80
|
+
type ExpectedShape = Pick<GPUCopyExternalImageSourceInfo, 'source' | 'origin' | 'flipY'>;
|
|
81
|
+
type IsExact = Equals<SourceParam, ExpectedShape>;
|
|
82
|
+
expectTypeOf<IsExact>().toEqualTypeOf<true>();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('destination param strictly equals Pick<GPUCopyExternalImageDestInfo, ...> (spec rename D-P5)', () => {
|
|
86
|
+
type DestParam = Parameters<RhiQueue['copyExternalImageToTexture']>[1];
|
|
87
|
+
type ExpectedShape = Pick<
|
|
88
|
+
GPUCopyExternalImageDestInfo,
|
|
89
|
+
'texture' | 'mipLevel' | 'origin' | 'aspect' | 'colorSpace' | 'premultipliedAlpha'
|
|
90
|
+
>;
|
|
91
|
+
type IsExact = Equals<DestParam, ExpectedShape>;
|
|
92
|
+
expectTypeOf<IsExact>().toEqualTypeOf<true>();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('copyExternalImageToTexture returns Result<void, RhiError>', () => {
|
|
96
|
+
type Ret = ReturnType<RhiQueue['copyExternalImageToTexture']>;
|
|
97
|
+
type IsResult = Equals<Ret, Result<void, RhiError>>;
|
|
98
|
+
expectTypeOf<IsResult>().toEqualTypeOf<true>();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Sanity peg — keep Buffer / Texture / TextureView in scope so they don't get
|
|
103
|
+
// stripped by tree-shake import elision (the rhi entry rolls these up so
|
|
104
|
+
// downstream consumers can grep on the forgeax handle names).
|
|
105
|
+
type _Peg = { b: Buffer; t: Texture; tv: TextureView };
|
|
106
|
+
declare const _peg: _Peg;
|
|
107
|
+
void _peg;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { resolveBufferWriteRange } from '../queue-writes';
|
|
4
|
+
|
|
5
|
+
describe('resolveBufferWriteRange', () => {
|
|
6
|
+
it('uses typed-array elements and preserves a non-zero view byteOffset', () => {
|
|
7
|
+
const source = new Float32Array([10, 20, 30, 40, 50]);
|
|
8
|
+
const view = source.subarray(1);
|
|
9
|
+
const range = resolveBufferWriteRange(view, 1, 2);
|
|
10
|
+
|
|
11
|
+
expect(range).toBeDefined();
|
|
12
|
+
expect(range?.byteOffset).toBe(4);
|
|
13
|
+
expect(range?.byteSize).toBe(8);
|
|
14
|
+
expect([...((range?.bytes ?? new Uint8Array()) as Uint8Array)]).toEqual([
|
|
15
|
+
...new Uint8Array(view.buffer, view.byteOffset + 4, 8),
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('uses bytes for ArrayBuffer and DataView sources', () => {
|
|
20
|
+
const source = new ArrayBuffer(8);
|
|
21
|
+
const view = new DataView(source, 2, 4);
|
|
22
|
+
expect(resolveBufferWriteRange(source, 2, 3)?.byteSize).toBe(3);
|
|
23
|
+
expect(resolveBufferWriteRange(view, 1, 2)?.byteOffset).toBe(1);
|
|
24
|
+
expect(resolveBufferWriteRange(view, 1, 2)?.byteSize).toBe(2);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('rejects an element range outside the source view', () => {
|
|
28
|
+
expect(resolveBufferWriteRange(new Float32Array(2), 1, 2)).toBeUndefined();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// w4 type-level - RhiRenderPassEncoder spec method-set alignment (research F-2 /
|
|
2
|
+
// plan-strategy D-S4: 17 spec stable + 1 setBindGroup overload + 3 placeholder).
|
|
3
|
+
//
|
|
4
|
+
// Asserts (RED until w5 lands the impl):
|
|
5
|
+
// - 17 spec stable methods exist on RhiRenderPassEncoder
|
|
6
|
+
// - setBindGroup has both overloads (a) array form (b) Uint32Array form
|
|
7
|
+
// - 3 placeholder methods (executeBundles / beginOcclusionQuery /
|
|
8
|
+
// endOcclusionQuery) are present in the interface and return
|
|
9
|
+
// Result<void, RhiError> (so AI users can route 'rhi-not-available' at
|
|
10
|
+
// runtime; charter proposition 4 explicit failure).
|
|
11
|
+
// - setImmediates (PROPOSED) is NOT exposed (per D-S4 explicit non-receipt;
|
|
12
|
+
// charter proposition 4: untested features hide behind caps, not surfaces).
|
|
13
|
+
//
|
|
14
|
+
// Method NAMES align byte-for-byte with `@webgpu/types`
|
|
15
|
+
// GPURenderPassEncoder + GPURenderCommandsMixin + GPUBindingCommandsMixin +
|
|
16
|
+
// GPUDebugCommandsMixin (research F-2).
|
|
17
|
+
//
|
|
18
|
+
// Charter mapping: proposition 1 (progressive disclosure: AI sees the full
|
|
19
|
+
// spec surface in one read) + proposition 5 (consistent abstraction: spec
|
|
20
|
+
// stable methods land here regardless of capability).
|
|
21
|
+
|
|
22
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
23
|
+
import type {
|
|
24
|
+
BindGroup,
|
|
25
|
+
Buffer,
|
|
26
|
+
RenderPassColorAttachment,
|
|
27
|
+
RenderPassDepthStencilAttachment,
|
|
28
|
+
Result,
|
|
29
|
+
RhiError,
|
|
30
|
+
RhiRenderPassEncoder,
|
|
31
|
+
Texture,
|
|
32
|
+
TextureView,
|
|
33
|
+
} from '../index';
|
|
34
|
+
|
|
35
|
+
describe('w4 - RhiRenderPassEncoder spec method set (17 stable + 1 overload + 3 placeholders)', () => {
|
|
36
|
+
// 7 already-shipped methods (Round 1 baseline; locked in this closure):
|
|
37
|
+
it('keeps existing setPipeline / setIndexBuffer / setVertexBuffer', () => {
|
|
38
|
+
expectTypeOf<RhiRenderPassEncoder['setPipeline']>().toBeFunction();
|
|
39
|
+
expectTypeOf<RhiRenderPassEncoder['setIndexBuffer']>().toBeFunction();
|
|
40
|
+
expectTypeOf<RhiRenderPassEncoder['setVertexBuffer']>().toBeFunction();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('keeps existing draw / drawIndexed / end', () => {
|
|
44
|
+
expectTypeOf<RhiRenderPassEncoder['draw']>().toBeFunction();
|
|
45
|
+
expectTypeOf<RhiRenderPassEncoder['drawIndexed']>().toBeFunction();
|
|
46
|
+
expectTypeOf<RhiRenderPassEncoder['end']>().toBeFunction();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// 10 new spec stable methods (research F-2; D-S4 IN list):
|
|
50
|
+
it('exposes setViewport(x, y, w, h, minDepth, maxDepth)', () => {
|
|
51
|
+
expectTypeOf<RhiRenderPassEncoder['setViewport']>().toBeFunction();
|
|
52
|
+
type Expected = (
|
|
53
|
+
x: number,
|
|
54
|
+
y: number,
|
|
55
|
+
w: number,
|
|
56
|
+
h: number,
|
|
57
|
+
minDepth: number,
|
|
58
|
+
maxDepth: number,
|
|
59
|
+
) => void;
|
|
60
|
+
expectTypeOf<Expected>().toMatchTypeOf<RhiRenderPassEncoder['setViewport']>();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('exposes setScissorRect(x, y, w, h)', () => {
|
|
64
|
+
expectTypeOf<RhiRenderPassEncoder['setScissorRect']>().toBeFunction();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('exposes setBlendConstant(color)', () => {
|
|
68
|
+
expectTypeOf<RhiRenderPassEncoder['setBlendConstant']>().toBeFunction();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('exposes setStencilReference(reference)', () => {
|
|
72
|
+
expectTypeOf<RhiRenderPassEncoder['setStencilReference']>().toBeFunction();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('exposes drawIndirect(buffer, offset)', () => {
|
|
76
|
+
expectTypeOf<RhiRenderPassEncoder['drawIndirect']>().toBeFunction();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('exposes drawIndexedIndirect(buffer, offset)', () => {
|
|
80
|
+
expectTypeOf<RhiRenderPassEncoder['drawIndexedIndirect']>().toBeFunction();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('exposes pushDebugGroup / popDebugGroup / insertDebugMarker (DebugCommandsMixin)', () => {
|
|
84
|
+
expectTypeOf<RhiRenderPassEncoder['pushDebugGroup']>().toBeFunction();
|
|
85
|
+
expectTypeOf<RhiRenderPassEncoder['popDebugGroup']>().toBeFunction();
|
|
86
|
+
expectTypeOf<RhiRenderPassEncoder['insertDebugMarker']>().toBeFunction();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// setBindGroup has 2 overloads per spec:
|
|
90
|
+
it('setBindGroup overload (a): array form (existing)', () => {
|
|
91
|
+
type ArrayForm = (
|
|
92
|
+
index: number,
|
|
93
|
+
bindGroup: BindGroup,
|
|
94
|
+
dynamicOffsets?: readonly number[] | undefined,
|
|
95
|
+
) => void;
|
|
96
|
+
type HasArrayOverload = RhiRenderPassEncoder['setBindGroup'] extends ArrayForm ? true : false;
|
|
97
|
+
expectTypeOf<HasArrayOverload>().toEqualTypeOf<true>();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('setBindGroup overload (b): Uint32Array slice form (new in this closure)', () => {
|
|
101
|
+
type SliceForm = (
|
|
102
|
+
index: number,
|
|
103
|
+
bindGroup: BindGroup,
|
|
104
|
+
dynamicOffsetsData: Uint32Array,
|
|
105
|
+
dynamicOffsetsDataStart: number,
|
|
106
|
+
dynamicOffsetsDataLength: number,
|
|
107
|
+
) => void;
|
|
108
|
+
type HasSliceOverload = RhiRenderPassEncoder['setBindGroup'] extends SliceForm ? true : false;
|
|
109
|
+
expectTypeOf<HasSliceOverload>().toEqualTypeOf<true>();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// 3 placeholders: signature lives in the interface; shim returns Result.err
|
|
113
|
+
// ({ code: 'rhi-not-available' }) at runtime per D-S4.
|
|
114
|
+
it('exposes executeBundles (placeholder; capability-gated by RenderBundle)', () => {
|
|
115
|
+
type Method = RhiRenderPassEncoder['executeBundles'];
|
|
116
|
+
expectTypeOf<Method>().toBeFunction();
|
|
117
|
+
expectTypeOf<Method>().returns.toEqualTypeOf<Result<void, RhiError>>();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('exposes beginOcclusionQuery (placeholder; capability-gated by QuerySet)', () => {
|
|
121
|
+
type Method = RhiRenderPassEncoder['beginOcclusionQuery'];
|
|
122
|
+
expectTypeOf<Method>().toBeFunction();
|
|
123
|
+
expectTypeOf<Method>().returns.toEqualTypeOf<Result<void, RhiError>>();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('exposes endOcclusionQuery (placeholder; capability-gated by QuerySet)', () => {
|
|
127
|
+
type Method = RhiRenderPassEncoder['endOcclusionQuery'];
|
|
128
|
+
expectTypeOf<Method>().toBeFunction();
|
|
129
|
+
expectTypeOf<Method>().returns.toEqualTypeOf<Result<void, RhiError>>();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('does NOT expose setImmediates (PROPOSED; D-S4 explicit non-receipt)', () => {
|
|
133
|
+
// Type-level guard: the field must NOT exist on the interface so that
|
|
134
|
+
// AI users see a tsc red signal if they attempt to call it.
|
|
135
|
+
type HasSetImmediates = 'setImmediates' extends keyof RhiRenderPassEncoder ? true : false;
|
|
136
|
+
expectTypeOf<HasSetImmediates>().toEqualTypeOf<false>();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Buffer reference smoke (sanity: arg types use forgeax opaque handles).
|
|
140
|
+
it('drawIndirect uses forgeax Buffer (opaque handle, not raw GPUBuffer)', () => {
|
|
141
|
+
type Method = RhiRenderPassEncoder['drawIndirect'];
|
|
142
|
+
type Expected = (indirectBuffer: Buffer, indirectOffset: number) => void;
|
|
143
|
+
expectTypeOf<Expected>().toMatchTypeOf<Method>();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// w14 - view narrow Path X breakage point #1 (requirements IN-2 / AC-02 /
|
|
148
|
+
// research 8.2): the 4 view fields move from `Texture` (D-S5 temporary
|
|
149
|
+
// tightening) to `TextureView` (the spec-aligned target after M1 shipped
|
|
150
|
+
// `device.createTextureView`).
|
|
151
|
+
//
|
|
152
|
+
// Field count breakdown (research 8.2 IMPORTANT box):
|
|
153
|
+
// - 1 direct: RenderPassColorAttachment.view
|
|
154
|
+
// - 1 direct: RenderPassColorAttachment.resolveTarget?
|
|
155
|
+
// - 1 direct: RenderPassDepthStencilAttachment.view
|
|
156
|
+
// - 1 indirect: RenderPassDescriptor.colorAttachments[N].view propagates
|
|
157
|
+
// through the element type of RenderPassColorAttachment (covered by the
|
|
158
|
+
// direct assertion on RenderPassColorAttachment.view).
|
|
159
|
+
//
|
|
160
|
+
// Red expected state: this block fails tsc -b before w15 lands the narrow
|
|
161
|
+
// (current types are `Texture`; `expectAssignable<TextureView>` fails because
|
|
162
|
+
// Texture and TextureView are disjoint brand types).
|
|
163
|
+
// Green expected state (after w15 commit): all expectTypeOf assertions pass.
|
|
164
|
+
//
|
|
165
|
+
// Charter mapping: proposition 4 (explicit failure: AI users get a tsc red
|
|
166
|
+
// signal at the call site instead of a runtime swap) + proposition 5
|
|
167
|
+
// (consistent abstraction: the field type matches what `createTextureView`
|
|
168
|
+
// returns).
|
|
169
|
+
describe('w14 - view narrow Path X (breakage point #1): RenderPassColorAttachment.view / .resolveTarget / RenderPassDepthStencilAttachment.view = TextureView', () => {
|
|
170
|
+
it('RenderPassColorAttachment.view is TextureView (not Texture)', () => {
|
|
171
|
+
type ViewField = RenderPassColorAttachment['view'];
|
|
172
|
+
expectTypeOf<ViewField>().toEqualTypeOf<TextureView>();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('RenderPassColorAttachment.view rejects Texture brand', () => {
|
|
176
|
+
type ViewField = RenderPassColorAttachment['view'];
|
|
177
|
+
type TextureAssignsToView = Texture extends ViewField ? true : false;
|
|
178
|
+
expectTypeOf<TextureAssignsToView>().toEqualTypeOf<false>();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('RenderPassColorAttachment.resolveTarget is TextureView | undefined (not Texture | undefined)', () => {
|
|
182
|
+
type ResolveTargetField = RenderPassColorAttachment['resolveTarget'];
|
|
183
|
+
expectTypeOf<ResolveTargetField>().toEqualTypeOf<TextureView | undefined>();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('RenderPassColorAttachment.resolveTarget rejects Texture brand', () => {
|
|
187
|
+
type ResolveTargetField = NonNullable<RenderPassColorAttachment['resolveTarget']>;
|
|
188
|
+
type TextureAssignsToResolveTarget = Texture extends ResolveTargetField ? true : false;
|
|
189
|
+
expectTypeOf<TextureAssignsToResolveTarget>().toEqualTypeOf<false>();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('RenderPassDepthStencilAttachment.view is TextureView (not Texture)', () => {
|
|
193
|
+
type ViewField = RenderPassDepthStencilAttachment['view'];
|
|
194
|
+
expectTypeOf<ViewField>().toEqualTypeOf<TextureView>();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('RenderPassDepthStencilAttachment.view rejects Texture brand', () => {
|
|
198
|
+
type ViewField = RenderPassDepthStencilAttachment['view'];
|
|
199
|
+
type TextureAssignsToView = Texture extends ViewField ? true : false;
|
|
200
|
+
expectTypeOf<TextureAssignsToView>().toEqualTypeOf<false>();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// w17 - RhiInstance + RhiAdapter type-level contract test (TDD red).
|
|
2
|
+
//
|
|
3
|
+
// Locks M3 break-point #2 surface (plan-tasks w17 / requirements IN-4 / AC-04;
|
|
4
|
+
// plan-strategy K-5 + K-6):
|
|
5
|
+
// RhiInstance.requestAdapter(opts?): Promise<Result<RhiAdapter, RhiError>>
|
|
6
|
+
// RhiAdapter.features: ReadonlySet<GPUFeatureName> (Round 3 fix-up F-P1-2:
|
|
7
|
+
// was ReadonlyArray<string>; aligned with RhiDevice.features for cross-
|
|
8
|
+
// tier shape uniformity — AI users use `.has(name)` on both layers)
|
|
9
|
+
// RhiAdapter.limits: Readonly<Record<string, number>>
|
|
10
|
+
// RhiAdapter.requestDevice(opts?): Promise<Result<RhiDevice, RhiError>>
|
|
11
|
+
//
|
|
12
|
+
// Strict two-step path mirrors wgpu / Dawn source (research §6); break-point
|
|
13
|
+
// #2 deprecates the top-level `rhi.requestDevice(opts)` factory in favour of
|
|
14
|
+
// `rhi.requestAdapter() -> adapter.requestDevice(opts)`.
|
|
15
|
+
//
|
|
16
|
+
// Red expected: tsc -b fails with TS2305 / TS2339 (missing RhiInstance /
|
|
17
|
+
// RhiAdapter / RequestAdapterOptions / RequestDeviceOptions). Turns green
|
|
18
|
+
// after w18 ships.
|
|
19
|
+
//
|
|
20
|
+
// F-1 ai-user-review absorption: readonly REVERSE assertions ensure mutation
|
|
21
|
+
// of `features[0]` / `limits['x']` is rejected at compile time (charter
|
|
22
|
+
// proposition 4 explicit failure + proposition 5 consistent abstraction).
|
|
23
|
+
//
|
|
24
|
+
// Anchors: requirements §IN-4 / §AC-04 / break-point #2; research §6.1 + §6.2
|
|
25
|
+
// + §6.3 wgpu / Dawn strict two-step source; plan-strategy §2 K-5 +
|
|
26
|
+
// K-6 + §6 M3 + K-10.
|
|
27
|
+
|
|
28
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
29
|
+
import type {
|
|
30
|
+
RequestAdapterOptions,
|
|
31
|
+
RequestDeviceOptions,
|
|
32
|
+
Result,
|
|
33
|
+
RhiAdapter,
|
|
34
|
+
RhiDevice,
|
|
35
|
+
RhiError,
|
|
36
|
+
RhiInstance,
|
|
37
|
+
} from '../index';
|
|
38
|
+
|
|
39
|
+
describe('w17 type-level - RhiInstance.requestAdapter signature (K-5 / break-point #2)', () => {
|
|
40
|
+
it('returns Promise<Result<RhiAdapter, RhiError>>', () => {
|
|
41
|
+
type Sig = RhiInstance['requestAdapter'];
|
|
42
|
+
type Ret = ReturnType<Sig>;
|
|
43
|
+
expectTypeOf<Ret>().toEqualTypeOf<Promise<Result<RhiAdapter, RhiError>>>();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('takes optional RequestAdapterOptions + compatibleSurface parameters', () => {
|
|
47
|
+
type Sig = RhiInstance['requestAdapter'];
|
|
48
|
+
type Params = Parameters<Sig>;
|
|
49
|
+
expectTypeOf<Params>().toEqualTypeOf<
|
|
50
|
+
[
|
|
51
|
+
opts?: RequestAdapterOptions | undefined,
|
|
52
|
+
compatibleSurface?: HTMLCanvasElement | OffscreenCanvas | undefined,
|
|
53
|
+
]
|
|
54
|
+
>();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('w17 type-level - RhiAdapter.features is ReadonlySet<GPUFeatureName> (K-5 / F-P1-2)', () => {
|
|
59
|
+
it('features field type is ReadonlySet<GPUFeatureName>', () => {
|
|
60
|
+
expectTypeOf<RhiAdapter['features']>().toEqualTypeOf<ReadonlySet<GPUFeatureName>>();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('mutating features.add(...) is rejected at compile time (F-1 readonly REVERSE)', () => {
|
|
64
|
+
const adapter = {} as RhiAdapter;
|
|
65
|
+
// @ts-expect-error F-1 ai-user-review: ReadonlySet rejects .add()
|
|
66
|
+
adapter.features.add('timestamp-query');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('mutating features.delete(...) is rejected at compile time (F-1)', () => {
|
|
70
|
+
const adapter = {} as RhiAdapter;
|
|
71
|
+
// @ts-expect-error F-1 ai-user-review: ReadonlySet rejects .delete()
|
|
72
|
+
adapter.features.delete('timestamp-query');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('mutating features.clear() is rejected at compile time (F-1)', () => {
|
|
76
|
+
const adapter = {} as RhiAdapter;
|
|
77
|
+
// @ts-expect-error F-1 ai-user-review: ReadonlySet rejects .clear()
|
|
78
|
+
adapter.features.clear();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('reassigning features whole set is rejected at compile time (F-1)', () => {
|
|
82
|
+
const adapter = {} as RhiAdapter;
|
|
83
|
+
// @ts-expect-error features is a readonly property
|
|
84
|
+
adapter.features = new Set();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('.has(name) is the supported read API (cross-tier uniform with RhiDevice.features)', () => {
|
|
88
|
+
const adapter = {} as RhiAdapter;
|
|
89
|
+
expectTypeOf(adapter.features.has('timestamp-query')).toEqualTypeOf<boolean>();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('w17 type-level - RhiAdapter.limits is Readonly<Record<string, number>> (K-5)', () => {
|
|
94
|
+
it('limits field type is Readonly<Record<string, number>>', () => {
|
|
95
|
+
expectTypeOf<RhiAdapter['limits']>().toEqualTypeOf<Readonly<Record<string, number>>>();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('mutating limits["x"] is rejected at compile time (F-1 readonly REVERSE)', () => {
|
|
99
|
+
const adapter = {} as RhiAdapter;
|
|
100
|
+
// @ts-expect-error F-1 ai-user-review: limits['x'] write is rejected by TS
|
|
101
|
+
adapter.limits.x = 0;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('reassigning limits whole record is rejected at compile time (F-1)', () => {
|
|
105
|
+
const adapter = {} as RhiAdapter;
|
|
106
|
+
// @ts-expect-error limits is a readonly property
|
|
107
|
+
adapter.limits = { x: 0 };
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('w17 type-level - RhiAdapter.requestDevice signature (K-6)', () => {
|
|
112
|
+
it('returns Promise<Result<RhiDevice, RhiError>> (NOT a tuple)', () => {
|
|
113
|
+
type Sig = RhiAdapter['requestDevice'];
|
|
114
|
+
type Ret = ReturnType<Sig>;
|
|
115
|
+
expectTypeOf<Ret>().toEqualTypeOf<Promise<Result<RhiDevice, RhiError>>>();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('takes an optional RequestDeviceOptions parameter', () => {
|
|
119
|
+
type Sig = RhiAdapter['requestDevice'];
|
|
120
|
+
type Params = Parameters<Sig>;
|
|
121
|
+
expectTypeOf<Params>().toEqualTypeOf<[opts?: RequestDeviceOptions | undefined]>();
|
|
122
|
+
});
|
|
123
|
+
});
|