@forgeax/engine-rhi-webgpu 0.1.4 → 0.1.7

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 (33) hide show
  1. package/README.md +12 -3
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/__mocks__/gpu-device.d.ts +0 -7
  4. package/dist/__tests__/__mocks__/gpu-device.d.ts.map +1 -1
  5. package/dist/device.d.ts +1 -1
  6. package/dist/device.d.ts.map +1 -1
  7. package/dist/errors.d.ts +2 -0
  8. package/dist/errors.d.ts.map +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.mjs +144 -103
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/internal/timestamp-query.d.ts +0 -24
  13. package/dist/internal/timestamp-query.d.ts.map +1 -1
  14. package/package.json +3 -3
  15. package/src/__tests__/__mocks__/gpu-device.ts +1 -10
  16. package/src/__tests__/dawn-real-gpu.dawn.test.ts +0 -43
  17. package/src/__tests__/rhi-webgpu.unit.test.ts +32 -43
  18. package/src/device.ts +164 -36
  19. package/src/errors.ts +40 -2
  20. package/src/index.ts +8 -10
  21. package/src/internal/timestamp-query.ts +2 -110
  22. package/dist/__tests__/queue-write-range.browser.test.d.ts +0 -2
  23. package/dist/__tests__/queue-write-range.browser.test.d.ts.map +0 -1
  24. package/dist/__tests__/rgba16float-live-probe.browser.test.d.ts +0 -2
  25. package/dist/__tests__/rgba16float-live-probe.browser.test.d.ts.map +0 -1
  26. package/dist/__tests__/rgba16float-live-probe.d.ts +0 -12
  27. package/dist/__tests__/rgba16float-live-probe.d.ts.map +0 -1
  28. package/dist/__tests__/rgba16float-live-probe.dawn.test.d.ts +0 -2
  29. package/dist/__tests__/rgba16float-live-probe.dawn.test.d.ts.map +0 -1
  30. package/src/__tests__/queue-write-range.browser.test.ts +0 -60
  31. package/src/__tests__/rgba16float-live-probe.browser.test.ts +0 -13
  32. package/src/__tests__/rgba16float-live-probe.dawn.test.ts +0 -15
  33. package/src/__tests__/rgba16float-live-probe.ts +0 -44
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { err, RhiError, ok, resolveBufferWriteRange } from '@forgeax/engine-rhi';
1
+ import { err, RhiError, ok } from '@forgeax/engine-rhi';
2
2
  export { RhiError as RhiErrorClass, err, ok } from '@forgeax/engine-rhi';
3
3
 
4
4
  // src/index.ts
@@ -6,8 +6,36 @@ function adapterUnavailable() {
6
6
  return err(
7
7
  new RhiError({
8
8
  code: "adapter-unavailable",
9
- expected: "an available WebGPU adapter",
10
- hint: "check whether the browser supports WebGPU or enable the relevant flag"
9
+ expected: "an available browser-native WebGPU adapter",
10
+ hint: "this only reports the browser-native WebGPU channel; ForgeaX may continue through its wgpu/WebGL2 fallback, so do not conclude that the browser or machine is unsupported unless both backend causes fail"
11
+ })
12
+ );
13
+ }
14
+ function requestAdapterFailed(cause) {
15
+ const record = cause !== null && typeof cause === "object" ? cause : void 0;
16
+ const name = typeof record?.name === "string" && record.name.length > 0 ? record.name : void 0;
17
+ let message = typeof record?.message === "string" && record.message.length > 0 ? record.message : typeof cause === "string" ? cause : "";
18
+ if (message.length === 0 && cause !== void 0) {
19
+ try {
20
+ const serialized = JSON.stringify(cause);
21
+ message = serialized && serialized !== "{}" ? serialized : "unknown thrown object";
22
+ } catch {
23
+ message = "unserializable thrown object";
24
+ }
25
+ }
26
+ if (message.length === 0) message = "unknown requestAdapter failure";
27
+ return err(
28
+ new RhiError({
29
+ code: "webgpu-runtime-error",
30
+ expected: "navigator.gpu.requestAdapter() resolves with an adapter or null",
31
+ hint: "inspect detail.error before assigning the failure to WebGPU capability; the ForgeaX runtime can still attempt its wgpu/WebGL2 fallback",
32
+ detail: {
33
+ error: {
34
+ code: "request-adapter-threw",
35
+ ...name === void 0 ? {} : { name },
36
+ message
37
+ }
38
+ }
11
39
  })
12
40
  );
13
41
  }
@@ -80,62 +108,7 @@ function queueWriteBufferOutOfBounds(args) {
80
108
  })
81
109
  );
82
110
  }
83
- function timestampQueryHealth(input) {
84
- if (!input.capability) {
85
- return {
86
- status: "fault",
87
- code: "timestamp-query-unsupported",
88
- expected: "device.caps.timestampQuery is true before timestamp queries are submitted",
89
- hint: "use the CPU timing path or recreate the renderer with timestamp-query capability",
90
- deviceGeneration: input.deviceGeneration,
91
- expectedGeneration: input.expectedGeneration
92
- };
93
- }
94
- if (input.expectedGeneration !== void 0 && input.deviceGeneration !== input.expectedGeneration) {
95
- return {
96
- status: "fault",
97
- code: "timestamp-query-generation-mismatch",
98
- expected: "timestamp encoder generation matches the live device generation",
99
- hint: "discard the stale encoder and rebuild timestamp resources during device recovery",
100
- deviceGeneration: input.deviceGeneration,
101
- expectedGeneration: input.expectedGeneration
102
- };
103
- }
104
- if (!input.encoderWriteTimestamp) {
105
- return {
106
- status: "fault",
107
- code: "timestamp-query-encoder-missing",
108
- expected: "underlying GPUCommandEncoder.writeTimestamp to be callable",
109
- hint: "timestamp-query is advertised but the raw encoder has no writeTimestamp method",
110
- deviceGeneration: input.deviceGeneration,
111
- expectedGeneration: input.expectedGeneration
112
- };
113
- }
114
- return {
115
- status: "ready",
116
- code: void 0,
117
- expected: "timestamp-query capability and encoder are available for this device generation",
118
- hint: "continue with the timestamp query operation",
119
- deviceGeneration: input.deviceGeneration,
120
- expectedGeneration: input.expectedGeneration
121
- };
122
- }
123
111
  function resolveTimestampQueries(args) {
124
- if (args.deviceGeneration !== void 0 && args.expectedGeneration !== void 0 && args.deviceGeneration !== args.expectedGeneration) {
125
- const health = timestampQueryHealth({
126
- capability: true,
127
- encoderWriteTimestamp: true,
128
- deviceGeneration: args.deviceGeneration,
129
- expectedGeneration: args.expectedGeneration
130
- });
131
- return err(
132
- new RhiError({
133
- code: "webgpu-runtime-error",
134
- expected: health.expected,
135
- hint: `${health.code}: ${health.hint}`
136
- })
137
- );
138
- }
139
112
  try {
140
113
  args.rawEncoder.resolveQuerySet(
141
114
  args.rawQuerySet,
@@ -158,21 +131,7 @@ function resolveTimestampQueries(args) {
158
131
  }
159
132
  function writeTimestamp(args) {
160
133
  const encoderWithTimestamp = args.rawEncoder;
161
- const writeTimestamp2 = encoderWithTimestamp.writeTimestamp;
162
- const health = timestampQueryHealth({
163
- capability: true,
164
- encoderWriteTimestamp: typeof writeTimestamp2 === "function",
165
- deviceGeneration: args.deviceGeneration ?? 0,
166
- expectedGeneration: args.expectedGeneration
167
- });
168
- if (health.status === "fault") {
169
- throw new RhiError({
170
- code: "webgpu-runtime-error",
171
- expected: health.expected,
172
- hint: health.code === "timestamp-query-encoder-missing" ? health.hint : `${health.code}: ${health.hint}`
173
- });
174
- }
175
- if (typeof writeTimestamp2 !== "function") {
134
+ if (typeof encoderWithTimestamp.writeTimestamp !== "function") {
176
135
  throw new RhiError({
177
136
  code: "webgpu-runtime-error",
178
137
  expected: "underlying GPUCommandEncoder.writeTimestamp to be callable",
@@ -180,7 +139,7 @@ function writeTimestamp(args) {
180
139
  });
181
140
  }
182
141
  try {
183
- writeTimestamp2(args.rawQuerySet, args.queryIndex);
142
+ encoderWithTimestamp.writeTimestamp(args.rawQuerySet, args.queryIndex);
184
143
  } catch (error) {
185
144
  const message = error instanceof Error ? error.message : String(error);
186
145
  throw new RhiError({
@@ -228,15 +187,6 @@ var SAMPLER_KEYS = [
228
187
  ];
229
188
  var BGL_KEYS = ["label", "entries"];
230
189
  var PL_KEYS = ["label", "bindGroupLayouts"];
231
- var RPP_KEYS = [
232
- "label",
233
- "layout",
234
- "vertex",
235
- "primitive",
236
- "depthStencil",
237
- "multisample",
238
- "fragment"
239
- ];
240
190
  var ENC_KEYS = ["label"];
241
191
  var TEXTURE_VIEW_KEYS = [
242
192
  "label",
@@ -254,6 +204,7 @@ var QS_KEYS = ["label", "type", "count"];
254
204
  var QUERY_SET_COUNT_LIMIT = 4096;
255
205
  var RAW_DEVICE_MAP = /* @__PURE__ */ new WeakMap();
256
206
  var BUFFER_RAW_MAP = /* @__PURE__ */ new WeakMap();
207
+ var TEXTURE_VIEW_RAW_MAP = /* @__PURE__ */ new WeakMap();
257
208
  var ENCODER_STATE = /* @__PURE__ */ new WeakMap();
258
209
  var PASS_STATE = /* @__PURE__ */ new WeakMap();
259
210
  var COMMAND_BUFFER_RAW_MAP = /* @__PURE__ */ new WeakMap();
@@ -393,8 +344,7 @@ function makeRenderPassEncoder(rawPass, encoder, occlusionQuerySet) {
393
344
  rawPass.setViewport(x, y, w, h, minDepth, maxDepth);
394
345
  },
395
346
  setScissorRect(x, y, w, h) {
396
- const setScissorRect = rawPass.setScissorRect;
397
- setScissorRect?.call(rawPass, x, y, w, h);
347
+ rawPass.setScissorRect(x, y, w, h);
398
348
  },
399
349
  setBlendConstant(color) {
400
350
  rawPass.setBlendConstant(color);
@@ -554,6 +504,80 @@ function throwIfFinished(state) {
554
504
  throw new RhiError(ENCODER_FINISHED_ERROR_ARGS);
555
505
  }
556
506
  }
507
+ function rawTextureView(view) {
508
+ return TEXTURE_VIEW_RAW_MAP.get(view) ?? view;
509
+ }
510
+ function mirrorRenderPassDescriptor(desc) {
511
+ const colorAttachments = [];
512
+ for (const attachment of desc.colorAttachments) {
513
+ if (attachment === null || attachment === void 0) {
514
+ colorAttachments.push(null);
515
+ continue;
516
+ }
517
+ if (attachment.loadOp === void 0 || attachment.storeOp === void 0) {
518
+ throw new TypeError("RHI render-pass color attachments require loadOp and storeOp");
519
+ }
520
+ colorAttachments.push({
521
+ view: rawTextureView(attachment.view),
522
+ ...attachment.depthSlice === void 0 ? {} : { depthSlice: attachment.depthSlice },
523
+ ...attachment.resolveTarget === void 0 ? {} : { resolveTarget: rawTextureView(attachment.resolveTarget) },
524
+ ...attachment.clearValue === void 0 ? {} : { clearValue: attachment.clearValue },
525
+ loadOp: attachment.loadOp,
526
+ storeOp: attachment.storeOp
527
+ });
528
+ }
529
+ return {
530
+ ...desc.label === void 0 ? {} : { label: desc.label },
531
+ colorAttachments,
532
+ ...desc.depthStencilAttachment === void 0 ? {} : {
533
+ depthStencilAttachment: {
534
+ view: rawTextureView(desc.depthStencilAttachment.view),
535
+ ...desc.depthStencilAttachment.depthClearValue === void 0 ? {} : { depthClearValue: desc.depthStencilAttachment.depthClearValue },
536
+ ...desc.depthStencilAttachment.depthLoadOp === void 0 ? {} : { depthLoadOp: desc.depthStencilAttachment.depthLoadOp },
537
+ ...desc.depthStencilAttachment.depthStoreOp === void 0 ? {} : { depthStoreOp: desc.depthStencilAttachment.depthStoreOp },
538
+ ...desc.depthStencilAttachment.depthReadOnly === void 0 ? {} : { depthReadOnly: desc.depthStencilAttachment.depthReadOnly },
539
+ ...desc.depthStencilAttachment.stencilClearValue === void 0 ? {} : { stencilClearValue: desc.depthStencilAttachment.stencilClearValue },
540
+ ...desc.depthStencilAttachment.stencilLoadOp === void 0 ? {} : { stencilLoadOp: desc.depthStencilAttachment.stencilLoadOp },
541
+ ...desc.depthStencilAttachment.stencilStoreOp === void 0 ? {} : { stencilStoreOp: desc.depthStencilAttachment.stencilStoreOp },
542
+ ...desc.depthStencilAttachment.stencilReadOnly === void 0 ? {} : { stencilReadOnly: desc.depthStencilAttachment.stencilReadOnly }
543
+ }
544
+ },
545
+ ...desc.occlusionQuerySet === void 0 ? {} : {
546
+ occlusionQuerySet: QUERY_SET_RAW_MAP.get(desc.occlusionQuerySet) ?? desc.occlusionQuerySet
547
+ },
548
+ ...desc.timestampWrites === void 0 ? {} : {
549
+ timestampWrites: {
550
+ querySet: QUERY_SET_RAW_MAP.get(desc.timestampWrites.querySet) ?? desc.timestampWrites.querySet,
551
+ ...desc.timestampWrites.beginningOfPassWriteIndex === void 0 ? {} : { beginningOfPassWriteIndex: desc.timestampWrites.beginningOfPassWriteIndex },
552
+ ...desc.timestampWrites.endOfPassWriteIndex === void 0 ? {} : { endOfPassWriteIndex: desc.timestampWrites.endOfPassWriteIndex }
553
+ }
554
+ },
555
+ ...desc.maxDrawCount === void 0 ? {} : { maxDrawCount: desc.maxDrawCount }
556
+ };
557
+ }
558
+ function mirrorRenderPipelineDescriptor(desc) {
559
+ const vertex = {
560
+ module: desc.vertex.module,
561
+ ...desc.vertex.buffers === void 0 ? {} : { buffers: Array.from(desc.vertex.buffers) },
562
+ ...desc.vertex.entryPoint === void 0 ? {} : { entryPoint: desc.vertex.entryPoint },
563
+ ...desc.vertex.constants === void 0 ? {} : { constants: desc.vertex.constants }
564
+ };
565
+ const fragment = desc.fragment === void 0 ? void 0 : {
566
+ module: desc.fragment.module,
567
+ targets: Array.from(desc.fragment.targets),
568
+ ...desc.fragment.entryPoint === void 0 ? {} : { entryPoint: desc.fragment.entryPoint },
569
+ ...desc.fragment.constants === void 0 ? {} : { constants: desc.fragment.constants }
570
+ };
571
+ return {
572
+ ...desc.label === void 0 ? {} : { label: desc.label },
573
+ layout: desc.layout === "auto" ? "auto" : desc.layout,
574
+ vertex,
575
+ ...desc.primitive === void 0 ? {} : { primitive: desc.primitive },
576
+ ...desc.depthStencil === void 0 ? {} : { depthStencil: desc.depthStencil },
577
+ ...desc.multisample === void 0 ? {} : { multisample: desc.multisample },
578
+ ...fragment === void 0 ? {} : { fragment }
579
+ };
580
+ }
557
581
  function makeCommandEncoder(rawEncoder, caps, fireFeatureNotEnabled) {
558
582
  function mirrorComputePassDescriptor(desc) {
559
583
  if (desc === void 0) return void 0;
@@ -572,9 +596,8 @@ function makeCommandEncoder(rawEncoder, caps, fireFeatureNotEnabled) {
572
596
  beginRenderPass(desc) {
573
597
  const state = ENCODER_STATE.get(enc);
574
598
  throwIfFinished(state);
575
- const rawPass = rawEncoder.beginRenderPass(desc);
576
- const occQs = desc.occlusionQuerySet;
577
- const occlusionQuerySet = occQs === void 0 || occQs === null ? null : occQs;
599
+ const rawPass = rawEncoder.beginRenderPass(mirrorRenderPassDescriptor(desc));
600
+ const occlusionQuerySet = desc.occlusionQuerySet ?? null;
578
601
  const pass = makeRenderPassEncoder(rawPass, enc, occlusionQuerySet);
579
602
  if (state !== void 0) state.activePass = pass;
580
603
  return pass;
@@ -895,17 +918,18 @@ function makeQueue(rawQueue) {
895
918
  return {
896
919
  writeBuffer(buffer, bufferOffset, data, dataOffset, size) {
897
920
  const rawBuffer = BUFFER_RAW_MAP.get(buffer) ?? buffer;
898
- const range = resolveBufferWriteRange(data, dataOffset, size);
899
921
  const bufferSize = typeof rawBuffer.size === "number" ? rawBuffer.size : Number.MAX_SAFE_INTEGER;
900
922
  if (bufferOffset % 4 !== 0) {
901
923
  return queueWriteBufferOutOfBounds({
902
924
  offset: bufferOffset,
903
- byteLength: range?.byteSize ?? 0,
925
+ byteLength: data instanceof ArrayBuffer ? data.byteLength : data.byteLength,
904
926
  bufferSize
905
927
  });
906
928
  }
907
- const writeSize = range?.byteSize ?? 0;
908
- if (range === void 0 || bufferOffset + writeSize > bufferSize) {
929
+ const dataByteLength = data instanceof ArrayBuffer ? data.byteLength : data.byteLength;
930
+ const writeStart = dataOffset ?? 0;
931
+ const writeSize = size ?? dataByteLength - writeStart;
932
+ if (bufferOffset + writeSize > bufferSize) {
909
933
  return queueWriteBufferOutOfBounds({
910
934
  offset: bufferOffset,
911
935
  byteLength: writeSize,
@@ -918,15 +942,15 @@ function makeQueue(rawQueue) {
918
942
  rawBuffer,
919
943
  bufferOffset,
920
944
  data,
921
- range.sourceOffset,
922
- range.sourceSize
945
+ writeStart,
946
+ size
923
947
  );
924
948
  } else if (dataOffset !== void 0) {
925
949
  rawQueue.writeBuffer(
926
950
  rawBuffer,
927
951
  bufferOffset,
928
952
  data,
929
- range.sourceOffset
953
+ writeStart
930
954
  );
931
955
  } else {
932
956
  rawQueue.writeBuffer(rawBuffer, bufferOffset, data);
@@ -964,8 +988,14 @@ function makeQueue(rawQueue) {
964
988
  },
965
989
  writeTexture(destination, data, dataLayout, size) {
966
990
  try {
991
+ const rawDestination = {
992
+ texture: destination.texture
993
+ };
994
+ if (destination.mipLevel !== void 0) rawDestination.mipLevel = destination.mipLevel;
995
+ if (destination.origin !== void 0) rawDestination.origin = destination.origin;
996
+ if (destination.aspect !== void 0) rawDestination.aspect = destination.aspect;
967
997
  rawQueue.writeTexture(
968
- destination,
998
+ rawDestination,
969
999
  data,
970
1000
  dataLayout,
971
1001
  size
@@ -984,7 +1014,18 @@ function makeQueue(rawQueue) {
984
1014
  },
985
1015
  copyExternalImageToTexture(source, destination, copySize) {
986
1016
  try {
987
- rawQueue.copyExternalImageToTexture(source, destination, copySize);
1017
+ rawQueue.copyExternalImageToTexture(
1018
+ source,
1019
+ {
1020
+ texture: destination.texture,
1021
+ ...destination.mipLevel === void 0 ? {} : { mipLevel: destination.mipLevel },
1022
+ ...destination.origin === void 0 ? {} : { origin: destination.origin },
1023
+ ...destination.aspect === void 0 ? {} : { aspect: destination.aspect },
1024
+ ...destination.colorSpace === void 0 ? {} : { colorSpace: destination.colorSpace },
1025
+ ...destination.premultipliedAlpha === void 0 ? {} : { premultipliedAlpha: destination.premultipliedAlpha }
1026
+ },
1027
+ copySize
1028
+ );
988
1029
  return ok(void 0);
989
1030
  } catch (e) {
990
1031
  const message = e instanceof Error ? e.message : String(e);
@@ -1130,7 +1171,9 @@ function makeRhiDevice(rawDevice) {
1130
1171
  const rawView = rawTexture.createView(
1131
1172
  mirror(desc, TEXTURE_VIEW_KEYS)
1132
1173
  );
1133
- return ok(rawView);
1174
+ const handle = rawView;
1175
+ TEXTURE_VIEW_RAW_MAP.set(handle, rawView);
1176
+ return ok(handle);
1134
1177
  } catch (e) {
1135
1178
  const message = e instanceof Error ? e.message : String(e);
1136
1179
  return err(
@@ -1213,9 +1256,7 @@ function makeRhiDevice(rawDevice) {
1213
1256
  },
1214
1257
  createRenderPipeline(desc) {
1215
1258
  try {
1216
- const out = rawDevice.createRenderPipeline(
1217
- mirror(desc, RPP_KEYS)
1218
- );
1259
+ const out = rawDevice.createRenderPipeline(mirrorRenderPipelineDescriptor(desc));
1219
1260
  return ok(out);
1220
1261
  } catch (e) {
1221
1262
  const message = e instanceof Error ? e.message : String(e);
@@ -1663,8 +1704,8 @@ async function requestAdapter(opts, _compatibleSurface) {
1663
1704
  let adapter;
1664
1705
  try {
1665
1706
  adapter = await ambient.requestAdapter(opts);
1666
- } catch {
1667
- return adapterUnavailable();
1707
+ } catch (cause) {
1708
+ return requestAdapterFailed(cause);
1668
1709
  }
1669
1710
  if (adapter === null) {
1670
1711
  return adapterUnavailable();