@forgeax/engine-rhi 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.d.ts +32 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1 -170
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/descriptor-mirror.test-d.ts +24 -7
- package/src/__tests__/queue-writes.test-d.ts +17 -16
- package/src/index.ts +52 -174
- package/dist/__tests__/queue-writes.unit.test.d.ts +0 -2
- package/dist/__tests__/queue-writes.unit.test.d.ts.map +0 -1
- package/dist/queue-writes.d.ts +0 -20
- package/dist/queue-writes.d.ts.map +0 -1
- package/src/__tests__/queue-writes.unit.test.ts +0 -30
- package/src/queue-writes.ts +0 -62
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-rhi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"LICENSE"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@forgeax/engine-types": "0.1.
|
|
29
|
+
"@forgeax/engine-types": "0.1.6",
|
|
30
30
|
"@webgpu/types": "^0.1.71"
|
|
31
31
|
},
|
|
32
32
|
"forgeax": {
|
|
@@ -34,13 +34,17 @@ import type {
|
|
|
34
34
|
BindGroupLayoutDescriptor,
|
|
35
35
|
BufferDescriptor,
|
|
36
36
|
CommandEncoderDescriptor,
|
|
37
|
+
PipelineLayout,
|
|
37
38
|
RenderPassColorAttachment,
|
|
38
39
|
RenderPassDepthStencilAttachment,
|
|
39
40
|
RenderPassDescriptor,
|
|
40
41
|
RenderPassTimestampWrites,
|
|
41
42
|
RenderPipelineDescriptor,
|
|
43
|
+
RenderPipelineFragmentState,
|
|
44
|
+
RenderPipelineVertexState,
|
|
42
45
|
SamplerDescriptor,
|
|
43
46
|
TextureDescriptor,
|
|
47
|
+
TextureFormat,
|
|
44
48
|
TextureView,
|
|
45
49
|
} from '../index';
|
|
46
50
|
|
|
@@ -140,18 +144,31 @@ describe('MVP-1.1 - 5 descriptors mirror @webgpu/types', () => {
|
|
|
140
144
|
| 'multisample'
|
|
141
145
|
| 'fragment';
|
|
142
146
|
expectTypeOf<keyof RenderPipelineDescriptor>().toEqualTypeOf<RppKeys>();
|
|
143
|
-
expectTypeOf<
|
|
144
|
-
ValueOf<
|
|
145
|
-
>();
|
|
146
|
-
expectTypeOf<
|
|
147
|
-
ValueOf<
|
|
148
|
-
>();
|
|
147
|
+
expectTypeOf<
|
|
148
|
+
ValueOf<RenderPipelineDescriptor, 'vertex'>
|
|
149
|
+
>().toEqualTypeOf<RenderPipelineVertexState>();
|
|
150
|
+
expectTypeOf<
|
|
151
|
+
ValueOf<RenderPipelineDescriptor, 'fragment'>
|
|
152
|
+
>().toEqualTypeOf<RenderPipelineFragmentState>();
|
|
149
153
|
expectTypeOf<ValueOf<RenderPipelineDescriptor, 'layout'>>().toEqualTypeOf<
|
|
150
|
-
|
|
154
|
+
'auto' | PipelineLayout
|
|
151
155
|
>();
|
|
152
156
|
});
|
|
153
157
|
});
|
|
154
158
|
|
|
159
|
+
describe('M4 RHI descriptor mirror', () => {
|
|
160
|
+
it('uses the spec TextureFormat union for texture descriptors', () => {
|
|
161
|
+
expectTypeOf<NonNullable<TextureDescriptor['format']>>().toEqualTypeOf<TextureFormat>();
|
|
162
|
+
const invalid: TextureDescriptor = {
|
|
163
|
+
// @ts-expect-error -- RHI descriptors reject arbitrary format strings.
|
|
164
|
+
format: 'not-a-texture-format',
|
|
165
|
+
size: { width: 1, height: 1 },
|
|
166
|
+
usage: 0,
|
|
167
|
+
};
|
|
168
|
+
void invalid;
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
155
172
|
// w8: 4 new descriptors (D-S5) - field NAMES align byte-for-byte with spec;
|
|
156
173
|
// the `view` field TYPE is tightened to `Texture` (spec has
|
|
157
174
|
// `GPUTexture | GPUTextureView`).
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// dataLayout is Pick<GPUTexelCopyBufferLayout, ...>.
|
|
19
19
|
// (b) copyExternalImageToTexture(source, destination, copySize) where:
|
|
20
20
|
// source is Pick<GPUCopyExternalImageSourceInfo, ...> (spec rename);
|
|
21
|
-
// destination is
|
|
21
|
+
// destination is the opaque-handle ExternalImageTextureDestination.
|
|
22
22
|
// (c) r12-lint Pick<GPU*Descriptor count >= 20 follow-on (verified by w22
|
|
23
23
|
// gate, not this test-d).
|
|
24
24
|
//
|
|
@@ -30,7 +30,16 @@
|
|
|
30
30
|
/// <reference types="@webgpu/types" />
|
|
31
31
|
|
|
32
32
|
import { describe, expectTypeOf, it } from 'vitest';
|
|
33
|
-
import type {
|
|
33
|
+
import type {
|
|
34
|
+
Buffer,
|
|
35
|
+
ExternalImageTextureDestination,
|
|
36
|
+
Result,
|
|
37
|
+
RhiError,
|
|
38
|
+
RhiQueue,
|
|
39
|
+
Texture,
|
|
40
|
+
TextureView,
|
|
41
|
+
TextureWriteDestination,
|
|
42
|
+
} from '../index';
|
|
34
43
|
|
|
35
44
|
/** Strict structural equality helper. */
|
|
36
45
|
type Equals<X, Y> =
|
|
@@ -42,15 +51,11 @@ describe('AC-08 — RhiQueue.writeTexture Pick<spec> narrow', () => {
|
|
|
42
51
|
expectTypeOf<Method>().toBeFunction();
|
|
43
52
|
});
|
|
44
53
|
|
|
45
|
-
it('writeTexture destination
|
|
54
|
+
it('writeTexture destination keeps the opaque Texture resource boundary', () => {
|
|
46
55
|
type DestParam = Parameters<RhiQueue['writeTexture']>[0];
|
|
47
|
-
type ExpectedShape =
|
|
48
|
-
|
|
49
|
-
|
|
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.
|
|
56
|
+
type ExpectedShape = TextureWriteDestination;
|
|
57
|
+
// Strict equality — the descriptor has exactly the four spec fields, but
|
|
58
|
+
// its resource is the RHI-opaque Texture rather than GPUTexture.
|
|
54
59
|
type IsExact = Equals<DestParam, ExpectedShape>;
|
|
55
60
|
expectTypeOf<IsExact>().toEqualTypeOf<true>();
|
|
56
61
|
});
|
|
@@ -82,13 +87,9 @@ describe('AC-08 — RhiQueue.copyExternalImageToTexture Pick<spec> narrow + spec
|
|
|
82
87
|
expectTypeOf<IsExact>().toEqualTypeOf<true>();
|
|
83
88
|
});
|
|
84
89
|
|
|
85
|
-
it('destination param
|
|
90
|
+
it('destination param keeps the RHI Texture opaque (D-P5)', () => {
|
|
86
91
|
type DestParam = Parameters<RhiQueue['copyExternalImageToTexture']>[1];
|
|
87
|
-
type
|
|
88
|
-
GPUCopyExternalImageDestInfo,
|
|
89
|
-
'texture' | 'mipLevel' | 'origin' | 'aspect' | 'colorSpace' | 'premultipliedAlpha'
|
|
90
|
-
>;
|
|
91
|
-
type IsExact = Equals<DestParam, ExpectedShape>;
|
|
92
|
+
type IsExact = Equals<DestParam, ExternalImageTextureDestination>;
|
|
92
93
|
expectTypeOf<IsExact>().toEqualTypeOf<true>();
|
|
93
94
|
});
|
|
94
95
|
|
package/src/index.ts
CHANGED
|
@@ -56,7 +56,7 @@ import type {
|
|
|
56
56
|
FilterMode,
|
|
57
57
|
TextureFormat,
|
|
58
58
|
} from '@forgeax/engine-types';
|
|
59
|
-
import {
|
|
59
|
+
import type { Result, RhiError } from './errors';
|
|
60
60
|
|
|
61
61
|
// ============================================================================
|
|
62
62
|
// 14 opaque handles (MVP-1.3)
|
|
@@ -211,6 +211,25 @@ export interface Texture {
|
|
|
211
211
|
readonly [RhiTextureBrand]: void;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/** RHI-owned destination for queue.writeTexture. The resource handle remains opaque. */
|
|
215
|
+
export interface TextureWriteDestination {
|
|
216
|
+
readonly texture: Texture;
|
|
217
|
+
readonly mipLevel?: number | undefined;
|
|
218
|
+
readonly origin?: GPUOrigin3D | undefined;
|
|
219
|
+
readonly aspect?: GPUTextureAspect | undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** RHI-owned destination for queue.copyExternalImageToTexture. */
|
|
223
|
+
export type ExternalImageTextureDestination = Omit<
|
|
224
|
+
Pick<
|
|
225
|
+
GPUCopyExternalImageDestInfo,
|
|
226
|
+
'texture' | 'mipLevel' | 'origin' | 'aspect' | 'colorSpace' | 'premultipliedAlpha'
|
|
227
|
+
>,
|
|
228
|
+
'texture'
|
|
229
|
+
> & {
|
|
230
|
+
readonly texture: Texture;
|
|
231
|
+
};
|
|
232
|
+
|
|
214
233
|
declare const RhiTextureViewBrand: unique symbol;
|
|
215
234
|
/** GPU texture view opaque handle. */
|
|
216
235
|
export interface TextureView {
|
|
@@ -588,14 +607,36 @@ export type PipelineLayoutDescriptor = ExplicitUndefined<
|
|
|
588
607
|
bindGroupLayouts: Iterable<BindGroupLayout>;
|
|
589
608
|
};
|
|
590
609
|
|
|
591
|
-
/** GPU render pipeline
|
|
592
|
-
|
|
610
|
+
/** GPU render pipeline vertex stage with an opaque forgeax shader module. */
|
|
611
|
+
export type RenderPipelineVertexState = ExplicitUndefined<
|
|
612
|
+
Omit<GPUVertexState, 'module' | 'buffers'>
|
|
613
|
+
> & {
|
|
614
|
+
module: ShaderModule;
|
|
615
|
+
buffers: NonNullable<GPUVertexState['buffers']>;
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
/** GPU render pipeline fragment stage with an opaque forgeax shader module. */
|
|
619
|
+
export type RenderPipelineFragmentState = ExplicitUndefined<
|
|
620
|
+
Omit<GPUFragmentState, 'module' | 'targets'>
|
|
621
|
+
> & {
|
|
622
|
+
module: ShaderModule;
|
|
623
|
+
targets: NonNullable<GPUFragmentState['targets']>;
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
/** GPU render pipeline descriptor with opaque forgeax layout and shader handles. */
|
|
593
627
|
export type RenderPipelineDescriptor = ExplicitUndefined<
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
628
|
+
Omit<
|
|
629
|
+
Pick<
|
|
630
|
+
GPURenderPipelineDescriptor,
|
|
631
|
+
'label' | 'layout' | 'vertex' | 'primitive' | 'depthStencil' | 'multisample' | 'fragment'
|
|
632
|
+
>,
|
|
633
|
+
'layout' | 'vertex' | 'fragment'
|
|
597
634
|
>
|
|
598
|
-
|
|
635
|
+
> & {
|
|
636
|
+
layout: 'auto' | PipelineLayout;
|
|
637
|
+
vertex: RenderPipelineVertexState;
|
|
638
|
+
fragment?: RenderPipelineFragmentState | undefined;
|
|
639
|
+
};
|
|
599
640
|
|
|
600
641
|
// ============================================================================
|
|
601
642
|
// 4 new descriptors (feat-20260508-rhi-surface-completion w8 / D-S5,
|
|
@@ -1606,14 +1647,7 @@ export interface RhiDevice {
|
|
|
1606
1647
|
/** GPU command queue - writeBuffer / submit + M5 writeTexture /
|
|
1607
1648
|
* copyExternalImageToTexture / onSubmittedWorkDone. */
|
|
1608
1649
|
export interface RhiQueue {
|
|
1609
|
-
/**
|
|
1610
|
-
* Direct write to a buffer (POD + ArrayBufferView, math-free).
|
|
1611
|
-
*
|
|
1612
|
-
* For typed-array sources, dataOffset and size are element counts, matching
|
|
1613
|
-
* GPUQueue.writeBuffer. For ArrayBuffer and DataView sources they are byte
|
|
1614
|
-
* counts. Every backend and the debug tape normalize the selected source
|
|
1615
|
-
* bytes through resolveBufferWriteRange before validation or recording.
|
|
1616
|
-
*/
|
|
1650
|
+
/** Direct write to a buffer (POD + ArrayBufferView, math-free). */
|
|
1617
1651
|
writeBuffer(
|
|
1618
1652
|
buffer: Buffer,
|
|
1619
1653
|
bufferOffset: number,
|
|
@@ -1643,7 +1677,7 @@ export interface RhiQueue {
|
|
|
1643
1677
|
* }
|
|
1644
1678
|
*/
|
|
1645
1679
|
writeTexture(
|
|
1646
|
-
destination:
|
|
1680
|
+
destination: TextureWriteDestination,
|
|
1647
1681
|
data: ArrayBufferView | ArrayBuffer,
|
|
1648
1682
|
dataLayout: Pick<GPUTexelCopyBufferLayout, 'offset' | 'bytesPerRow' | 'rowsPerImage'>,
|
|
1649
1683
|
size: GPUExtent3DStrict,
|
|
@@ -1662,10 +1696,7 @@ export interface RhiQueue {
|
|
|
1662
1696
|
*/
|
|
1663
1697
|
copyExternalImageToTexture(
|
|
1664
1698
|
source: Pick<GPUCopyExternalImageSourceInfo, 'source' | 'origin' | 'flipY'>,
|
|
1665
|
-
destination:
|
|
1666
|
-
GPUCopyExternalImageDestInfo,
|
|
1667
|
-
'texture' | 'mipLevel' | 'origin' | 'aspect' | 'colorSpace' | 'premultipliedAlpha'
|
|
1668
|
-
>,
|
|
1699
|
+
destination: ExternalImageTextureDestination,
|
|
1669
1700
|
copySize: GPUExtent3DStrict,
|
|
1670
1701
|
): Result<void, RhiError>;
|
|
1671
1702
|
/** Submit command buffers (single-use). */
|
|
@@ -1721,7 +1752,7 @@ export interface RhiCommandEncoder {
|
|
|
1721
1752
|
* @example
|
|
1722
1753
|
* const pass = encoder.beginRenderPass({ colorAttachments: [{ ... }] });
|
|
1723
1754
|
*/
|
|
1724
|
-
beginRenderPass(desc:
|
|
1755
|
+
beginRenderPass(desc: RenderPassDescriptor): RhiRenderPassEncoder;
|
|
1725
1756
|
/** Begin compute pass.
|
|
1726
1757
|
*
|
|
1727
1758
|
* Spec anchor: [@webgpu/types.GPUCommandEncoder.beginComputePass].
|
|
@@ -2156,157 +2187,6 @@ export { err, ok, RhiError, validateDrawArgs } from './errors';
|
|
|
2156
2187
|
// re-export common descriptor-related aliases for single-entry consumption.
|
|
2157
2188
|
export type { AddressMode, CompareFunction, FilterMode, TextureFormat };
|
|
2158
2189
|
|
|
2159
|
-
export interface Rgba16floatRenderTargetProbe {
|
|
2160
|
-
readonly format: 'rgba16float';
|
|
2161
|
-
readonly pixel: readonly [number, number, number, number];
|
|
2162
|
-
readonly timestampQuery: boolean;
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
function halfToFloat(value: number): number {
|
|
2166
|
-
const sign = (value & 0x8000) === 0 ? 1 : -1;
|
|
2167
|
-
const exponent = (value >>> 10) & 0x1f;
|
|
2168
|
-
const mantissa = value & 0x03ff;
|
|
2169
|
-
if (exponent === 0) return sign * 2 ** -14 * (mantissa / 1024);
|
|
2170
|
-
if (exponent === 0x1f) return mantissa === 0 ? sign * Number.POSITIVE_INFINITY : Number.NaN;
|
|
2171
|
-
return sign * 2 ** (exponent - 15) * (1 + mantissa / 1024);
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
/** Execute an rgba16float attachment clear and texture-to-buffer readback on a live device. */
|
|
2175
|
-
export async function probeRgba16floatRenderTarget(
|
|
2176
|
-
device: RhiDevice,
|
|
2177
|
-
): Promise<Result<Rgba16floatRenderTargetProbe, RhiError>> {
|
|
2178
|
-
if (!device.caps.rgba16floatRenderable) {
|
|
2179
|
-
return err(
|
|
2180
|
-
new RhiError({
|
|
2181
|
-
code: 'feature-not-enabled',
|
|
2182
|
-
expected: 'device.caps.rgba16floatRenderable is true',
|
|
2183
|
-
hint: 'select a device that can render rgba16float attachments',
|
|
2184
|
-
}),
|
|
2185
|
-
);
|
|
2186
|
-
}
|
|
2187
|
-
const texture = device.createTexture({
|
|
2188
|
-
label: 'rgba16float-render-target-probe',
|
|
2189
|
-
size: { width: 1, height: 1, depthOrArrayLayers: 1 },
|
|
2190
|
-
mipLevelCount: 1,
|
|
2191
|
-
sampleCount: 1,
|
|
2192
|
-
dimension: '2d',
|
|
2193
|
-
format: 'rgba16float',
|
|
2194
|
-
usage: 0x10 | 0x01,
|
|
2195
|
-
textureBindingViewDimension: undefined,
|
|
2196
|
-
viewFormats: [],
|
|
2197
|
-
});
|
|
2198
|
-
if (!texture.ok) return texture;
|
|
2199
|
-
const readback = device.createBuffer({
|
|
2200
|
-
label: 'rgba16float-render-target-probe-readback',
|
|
2201
|
-
size: 256,
|
|
2202
|
-
usage: 0x01 | 0x08,
|
|
2203
|
-
mappedAtCreation: false,
|
|
2204
|
-
});
|
|
2205
|
-
if (!readback.ok) {
|
|
2206
|
-
device.destroyTexture(texture.value);
|
|
2207
|
-
return readback;
|
|
2208
|
-
}
|
|
2209
|
-
const view = device.createTextureView(texture.value, {});
|
|
2210
|
-
if (!view.ok) {
|
|
2211
|
-
device.destroyBuffer(readback.value);
|
|
2212
|
-
device.destroyTexture(texture.value);
|
|
2213
|
-
return view;
|
|
2214
|
-
}
|
|
2215
|
-
const encoder = device.createCommandEncoder({ label: 'rgba16float-render-target-probe' });
|
|
2216
|
-
if (!encoder.ok) {
|
|
2217
|
-
device.destroyBuffer(readback.value);
|
|
2218
|
-
device.destroyTexture(texture.value);
|
|
2219
|
-
return encoder;
|
|
2220
|
-
}
|
|
2221
|
-
try {
|
|
2222
|
-
const pass = encoder.value.beginRenderPass({
|
|
2223
|
-
label: 'rgba16float-render-target-probe',
|
|
2224
|
-
colorAttachments: [
|
|
2225
|
-
{
|
|
2226
|
-
view: view.value as unknown as GPUTextureView,
|
|
2227
|
-
clearValue: { r: 0.25, g: 0.5, b: 0.75, a: 1 },
|
|
2228
|
-
loadOp: 'clear',
|
|
2229
|
-
storeOp: 'store',
|
|
2230
|
-
},
|
|
2231
|
-
],
|
|
2232
|
-
});
|
|
2233
|
-
pass.end();
|
|
2234
|
-
encoder.value.copyTextureToBuffer(
|
|
2235
|
-
{
|
|
2236
|
-
texture: texture.value as unknown as GPUTexture,
|
|
2237
|
-
mipLevel: 0,
|
|
2238
|
-
origin: { x: 0, y: 0, z: 0 },
|
|
2239
|
-
},
|
|
2240
|
-
{
|
|
2241
|
-
buffer: readback.value as unknown as GPUBuffer,
|
|
2242
|
-
offset: 0,
|
|
2243
|
-
bytesPerRow: 256,
|
|
2244
|
-
rowsPerImage: 1,
|
|
2245
|
-
},
|
|
2246
|
-
{ width: 1, height: 1, depthOrArrayLayers: 1 },
|
|
2247
|
-
);
|
|
2248
|
-
} catch (cause) {
|
|
2249
|
-
device.destroyBuffer(readback.value);
|
|
2250
|
-
device.destroyTexture(texture.value);
|
|
2251
|
-
return err(
|
|
2252
|
-
new RhiError({
|
|
2253
|
-
code: 'webgpu-runtime-error',
|
|
2254
|
-
expected: 'rgba16float render pass and texture-to-buffer copy encode successfully',
|
|
2255
|
-
hint: cause instanceof Error ? cause.message : String(cause),
|
|
2256
|
-
}),
|
|
2257
|
-
);
|
|
2258
|
-
}
|
|
2259
|
-
const command = encoder.value.finish();
|
|
2260
|
-
if (!command.ok) {
|
|
2261
|
-
device.destroyBuffer(readback.value);
|
|
2262
|
-
device.destroyTexture(texture.value);
|
|
2263
|
-
return command;
|
|
2264
|
-
}
|
|
2265
|
-
const submitted = device.queue.submit([command.value]);
|
|
2266
|
-
if (!submitted.ok) {
|
|
2267
|
-
device.destroyBuffer(readback.value);
|
|
2268
|
-
device.destroyTexture(texture.value);
|
|
2269
|
-
return submitted;
|
|
2270
|
-
}
|
|
2271
|
-
try {
|
|
2272
|
-
await device.queue.onSubmittedWorkDone();
|
|
2273
|
-
} catch (cause) {
|
|
2274
|
-
device.destroyBuffer(readback.value);
|
|
2275
|
-
device.destroyTexture(texture.value);
|
|
2276
|
-
return err(
|
|
2277
|
-
new RhiError({
|
|
2278
|
-
code: 'webgpu-runtime-error',
|
|
2279
|
-
expected: 'rgba16float probe submission completes',
|
|
2280
|
-
hint: cause instanceof Error ? cause.message : String(cause),
|
|
2281
|
-
}),
|
|
2282
|
-
);
|
|
2283
|
-
}
|
|
2284
|
-
const mapped = await readback.value.mapAsync(0x01);
|
|
2285
|
-
if (!mapped.ok) {
|
|
2286
|
-
device.destroyBuffer(readback.value);
|
|
2287
|
-
device.destroyTexture(texture.value);
|
|
2288
|
-
return mapped;
|
|
2289
|
-
}
|
|
2290
|
-
const range = mapped.value.getMappedRange(0, 8);
|
|
2291
|
-
if (!range.ok) {
|
|
2292
|
-
mapped.value.unmap();
|
|
2293
|
-
device.destroyBuffer(readback.value);
|
|
2294
|
-
device.destroyTexture(texture.value);
|
|
2295
|
-
return range;
|
|
2296
|
-
}
|
|
2297
|
-
const words = new Uint16Array(range.value.slice(0));
|
|
2298
|
-
const pixel = [
|
|
2299
|
-
halfToFloat(words[0] ?? 0),
|
|
2300
|
-
halfToFloat(words[1] ?? 0),
|
|
2301
|
-
halfToFloat(words[2] ?? 0),
|
|
2302
|
-
halfToFloat(words[3] ?? 0),
|
|
2303
|
-
] as const;
|
|
2304
|
-
mapped.value.unmap();
|
|
2305
|
-
device.destroyBuffer(readback.value);
|
|
2306
|
-
device.destroyTexture(texture.value);
|
|
2307
|
-
return ok({ format: 'rgba16float', pixel, timestampQuery: device.caps.timestampQuery });
|
|
2308
|
-
}
|
|
2309
|
-
|
|
2310
2190
|
// ============================================================================
|
|
2311
2191
|
// Descriptor builder helpers (feat-20260612-point-light-shadows-urp-hdrp M0)
|
|
2312
2192
|
// ============================================================================
|
|
@@ -2361,8 +2241,6 @@ export function comparisonSamplerDescriptor(): SamplerDescriptor {
|
|
|
2361
2241
|
};
|
|
2362
2242
|
}
|
|
2363
2243
|
|
|
2364
|
-
export { type BufferWriteRange, resolveBufferWriteRange } from './queue-writes';
|
|
2365
|
-
|
|
2366
2244
|
/**
|
|
2367
2245
|
* Build a TextureViewDescriptor to view a single cube layer + face as a 2D
|
|
2368
2246
|
* depth attachment during shadow-caster rendering.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"queue-writes.unit.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/queue-writes.unit.test.ts"],"names":[],"mappings":""}
|
package/dist/queue-writes.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve the source range of a queue.writeBuffer call.
|
|
3
|
-
*
|
|
4
|
-
* WebGPU defines dataOffset and size in elements for typed-array sources, but
|
|
5
|
-
* in bytes for ArrayBuffer and DataView sources. Keeping that conversion in
|
|
6
|
-
* the shared RHI package makes the browser, native, and debug paths agree.
|
|
7
|
-
*/
|
|
8
|
-
export interface BufferWriteRange {
|
|
9
|
-
readonly sourceOffset: number;
|
|
10
|
-
readonly sourceSize: number;
|
|
11
|
-
readonly byteOffset: number;
|
|
12
|
-
readonly byteSize: number;
|
|
13
|
-
readonly bytes: Uint8Array;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Return the exact source bytes selected by a queue.writeBuffer range.
|
|
17
|
-
* Undefined means the supplied element range is invalid.
|
|
18
|
-
*/
|
|
19
|
-
export declare function resolveBufferWriteRange(data: ArrayBufferView | ArrayBuffer, dataOffset?: number, size?: number): BufferWriteRange | undefined;
|
|
20
|
-
//# sourceMappingURL=queue-writes.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"queue-writes.d.ts","sourceRoot":"","sources":["../src/queue-writes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAeD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,eAAe,GAAG,WAAW,EACnC,UAAU,CAAC,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,MAAM,GACZ,gBAAgB,GAAG,SAAS,CAyB9B"}
|
|
@@ -1,30 +0,0 @@
|
|
|
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
|
-
});
|
package/src/queue-writes.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve the source range of a queue.writeBuffer call.
|
|
3
|
-
*
|
|
4
|
-
* WebGPU defines dataOffset and size in elements for typed-array sources, but
|
|
5
|
-
* in bytes for ArrayBuffer and DataView sources. Keeping that conversion in
|
|
6
|
-
* the shared RHI package makes the browser, native, and debug paths agree.
|
|
7
|
-
*/
|
|
8
|
-
export interface BufferWriteRange {
|
|
9
|
-
readonly sourceOffset: number;
|
|
10
|
-
readonly sourceSize: number;
|
|
11
|
-
readonly byteOffset: number;
|
|
12
|
-
readonly byteSize: number;
|
|
13
|
-
readonly bytes: Uint8Array;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function elementSize(data: ArrayBufferView | ArrayBuffer): number {
|
|
17
|
-
if (data instanceof ArrayBuffer) return 1;
|
|
18
|
-
const bytesPerElement = (data as ArrayBufferView & { BYTES_PER_ELEMENT?: number })
|
|
19
|
-
.BYTES_PER_ELEMENT;
|
|
20
|
-
return typeof bytesPerElement === 'number' && bytesPerElement > 0 ? bytesPerElement : 1;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function sourceByteView(data: ArrayBufferView | ArrayBuffer): Uint8Array {
|
|
24
|
-
return data instanceof ArrayBuffer
|
|
25
|
-
? new Uint8Array(data)
|
|
26
|
-
: new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Return the exact source bytes selected by a queue.writeBuffer range.
|
|
31
|
-
* Undefined means the supplied element range is invalid.
|
|
32
|
-
*/
|
|
33
|
-
export function resolveBufferWriteRange(
|
|
34
|
-
data: ArrayBufferView | ArrayBuffer,
|
|
35
|
-
dataOffset?: number,
|
|
36
|
-
size?: number,
|
|
37
|
-
): BufferWriteRange | undefined {
|
|
38
|
-
const bytes = sourceByteView(data);
|
|
39
|
-
const bytesPerElement = elementSize(data);
|
|
40
|
-
const elementCount = bytes.byteLength / bytesPerElement;
|
|
41
|
-
const sourceOffset = dataOffset ?? 0;
|
|
42
|
-
const sourceSize = size ?? elementCount - sourceOffset;
|
|
43
|
-
if (
|
|
44
|
-
!Number.isInteger(sourceOffset) ||
|
|
45
|
-
!Number.isInteger(sourceSize) ||
|
|
46
|
-
sourceOffset < 0 ||
|
|
47
|
-
sourceSize < 0 ||
|
|
48
|
-
sourceOffset > elementCount ||
|
|
49
|
-
sourceSize > elementCount - sourceOffset
|
|
50
|
-
) {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
const byteOffset = sourceOffset * bytesPerElement;
|
|
54
|
-
const byteSize = sourceSize * bytesPerElement;
|
|
55
|
-
return {
|
|
56
|
-
sourceOffset,
|
|
57
|
-
sourceSize,
|
|
58
|
-
byteOffset,
|
|
59
|
-
byteSize,
|
|
60
|
-
bytes: bytes.subarray(byteOffset, byteOffset + byteSize),
|
|
61
|
-
};
|
|
62
|
-
}
|