@forgeax/engine-rhi-webgpu 0.1.20 → 0.1.23
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/README.md +15 -7
- package/dist/__tests__/r32float-capability-generation.integration.test.d.ts +2 -0
- package/dist/__tests__/r32float-capability-generation.integration.test.d.ts.map +1 -0
- package/dist/__tests__/r32float-format-capability.browser.test.d.ts +2 -0
- package/dist/__tests__/r32float-format-capability.browser.test.d.ts.map +1 -0
- package/dist/__tests__/r32float-format-capability.dawn.test.d.ts +2 -0
- package/dist/__tests__/r32float-format-capability.dawn.test.d.ts.map +1 -0
- package/dist/device.d.ts.map +1 -1
- package/dist/index.mjs +159 -43
- package/dist/index.mjs.map +1 -1
- package/dist/internal/r32float-capability.d.ts +6 -0
- package/dist/internal/r32float-capability.d.ts.map +1 -0
- package/dist/internal/timestamp-query.d.ts +0 -5
- package/dist/internal/timestamp-query.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/dawn-real-gpu.dawn.test.ts +170 -3
- package/src/__tests__/r32float-capability-generation.integration.test.ts +35 -0
- package/src/__tests__/r32float-format-capability.browser.test.ts +22 -0
- package/src/__tests__/r32float-format-capability.dawn.test.ts +31 -0
- package/src/__tests__/rhi-webgpu.unit.test.ts +73 -101
- package/src/device.ts +49 -54
- package/src/internal/__tests__/timestamp-query.unit.test.ts +110 -78
- package/src/internal/r32float-capability.ts +153 -0
- package/src/internal/timestamp-query.ts +0 -27
- package/dist/.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -51,12 +51,20 @@ Real-GPU integration is in [`src/__tests__/dawn-real-gpu.dawn.test.ts`](./src/__
|
|
|
51
51
|
|
|
52
52
|
## Timestamp write contract
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
that
|
|
59
|
-
|
|
54
|
+
Timestamp capture uses the WebGPU pass descriptor `timestampWrites` field on
|
|
55
|
+
`beginRenderPass` / `beginComputePass`. The shim maps the opaque ForgeaX
|
|
56
|
+
`QuerySet` to the native `GPUQuerySet` at the pass boundary. The obsolete
|
|
57
|
+
command-encoder `writeTimestamp` entry is intentionally not exposed: current
|
|
58
|
+
Dawn rejects that method even when `timestamp-query` is advertised. A device
|
|
59
|
+
without a usable timestamp period remains capability-negative and produces no
|
|
60
|
+
synthetic ticks.
|
|
61
|
+
When `device.caps.timestampQuery` is true, render and compute pass descriptors
|
|
62
|
+
carry the mapped query set and timestamp indices. The WebGPU adapter does not
|
|
63
|
+
expose the removed command-encoder timestamp entry; callers that need an
|
|
64
|
+
encoder-position marker use an empty timestamp-enabled compute pass on the same
|
|
65
|
+
encoder. A marker failure is a structured `webgpu-runtime-error`; the shim
|
|
66
|
+
never treats it as a no-op or synthesizes timestamp ticks. Capability-disabled
|
|
67
|
+
devices retain the existing refusal path.
|
|
60
68
|
|
|
61
69
|
## Query forwarding contract
|
|
62
70
|
|
|
@@ -68,7 +76,7 @@ The shim forwards the query surface from [`@forgeax/engine-rhi`](../rhi):
|
|
|
68
76
|
| `occlusionQuerySet` + `beginOcclusionQuery` / `endOcclusionQuery` | The pass owns the query-set state; missing set, out-of-range index, nested begin, and unmatched end produce structured errors | [`device.ts`](./src/device.ts#L542), [Dawn state/round-trip coverage](./src/__tests__/dawn-real-gpu.dawn.test.ts#L619) |
|
|
69
77
|
| `resolveQuerySet` | Validates 256-byte destination alignment, `QUERY_RESOLVE` usage, range, and destination size before raw forwarding | [`device.ts`](./src/device.ts#L966), [Dawn coverage](./src/__tests__/dawn-real-gpu.dawn.test.ts#L820) |
|
|
70
78
|
| Timestamp pass writes | `timestampWrites` maps opaque `QuerySet` handles to raw `GPUQuerySet`; a capability-negative device refuses creation and does not synthesize ticks | [`device.ts`](./src/device.ts#L745), [Dawn admission/readback coverage](./src/__tests__/dawn-real-gpu.dawn.test.ts#L1233) |
|
|
71
|
-
|
|
|
79
|
+
| Pass descriptor `timestampWrites` | Capability-positive devices map opaque query sets at the real render/compute pass boundary; unavailable periods remain fail-closed | [`device.ts`](./src/device.ts#L745), [unit coverage](./src/internal/__tests__/timestamp-query.unit.test.ts) |
|
|
72
80
|
| `executeBundles` | Still unavailable: the public RHI has no constructible `RenderBundle` handle or creation path, so the shim returns `rhi-not-available` | [`device.ts`](./src/device.ts#L533) |
|
|
73
81
|
|
|
74
82
|
> [!NOTE]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r32float-capability-generation.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/r32float-capability-generation.integration.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r32float-format-capability.browser.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/r32float-format-capability.browser.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r32float-format-capability.dawn.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/r32float-format-capability.dawn.test.ts"],"names":[],"mappings":""}
|
package/dist/device.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"device.d.ts","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAwBV,gBAAgB,EAIhB,SAAS,EAcV,MAAM,qBAAqB,CAAC;AAqM7B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAE/E;AA6qCD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,SAAS,GAAG;IACnD,MAAM,EAAE,SAAS,CAAC;IAClB;6CACyC;IACzC,GAAG,EAAE,SAAS,CAAC;CAChB,CA2bA;AA0BD;;iFAEiF;AACjF,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,aAAa,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACvD,WAAW,IAAI,IAAI,CAAC;IACpB,gBAAgB,IAAI,sBAAsB,GAAG,IAAI,CAAC;IAClD,iBAAiB,IAAI,UAAU,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,oBAAoB,GAAG,gBAAgB,CAqGpF"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { err, RhiError, ok } from '@forgeax/engine-rhi';
|
|
1
|
+
import { err, RhiError, ok, R32FLOAT_PROBE_STAGES, validateR32FloatReceipt, createUnavailableR32FloatReceipt } from '@forgeax/engine-rhi';
|
|
2
2
|
export { RhiError as RhiErrorClass, err, ok } from '@forgeax/engine-rhi';
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
@@ -108,6 +108,131 @@ function queueWriteBufferOutOfBounds(args) {
|
|
|
108
108
|
})
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
|
+
var TEXTURE_BINDING = 4;
|
|
112
|
+
var STORAGE_BINDING = 8;
|
|
113
|
+
var COPY_SRC = 1;
|
|
114
|
+
var COPY_DST = 2;
|
|
115
|
+
var MAP_READ = 1;
|
|
116
|
+
var BUFFER_COPY_DST = 8;
|
|
117
|
+
async function probeR32FloatCapability(rawDevice, deviceGeneration) {
|
|
118
|
+
let stage = "texture-create";
|
|
119
|
+
let texture;
|
|
120
|
+
let readback;
|
|
121
|
+
rawDevice.pushErrorScope("validation");
|
|
122
|
+
try {
|
|
123
|
+
texture = rawDevice.createTexture({
|
|
124
|
+
size: { width: 2, height: 2, depthOrArrayLayers: 1 },
|
|
125
|
+
format: "r32float",
|
|
126
|
+
mipLevelCount: 2,
|
|
127
|
+
usage: TEXTURE_BINDING | STORAGE_BINDING | COPY_SRC | COPY_DST
|
|
128
|
+
});
|
|
129
|
+
stage = "mip-view";
|
|
130
|
+
const sourceView = texture.createView({ baseMipLevel: 0, mipLevelCount: 1 });
|
|
131
|
+
const destinationView = texture.createView({ baseMipLevel: 1, mipLevelCount: 1 });
|
|
132
|
+
stage = "sampled-storage-bind-group";
|
|
133
|
+
const layout = rawDevice.createBindGroupLayout({
|
|
134
|
+
entries: [
|
|
135
|
+
{
|
|
136
|
+
binding: 0,
|
|
137
|
+
visibility: 4,
|
|
138
|
+
texture: { sampleType: "unfilterable-float", viewDimension: "2d" }
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
binding: 1,
|
|
142
|
+
visibility: 4,
|
|
143
|
+
storageTexture: { access: "write-only", format: "r32float", viewDimension: "2d" }
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
});
|
|
147
|
+
const bindGroup = rawDevice.createBindGroup({
|
|
148
|
+
layout,
|
|
149
|
+
entries: [
|
|
150
|
+
{ binding: 0, resource: sourceView },
|
|
151
|
+
{ binding: 1, resource: destinationView }
|
|
152
|
+
]
|
|
153
|
+
});
|
|
154
|
+
stage = "pipeline-bind";
|
|
155
|
+
const shader = rawDevice.createShaderModule({
|
|
156
|
+
code: `
|
|
157
|
+
@group(0) @binding(0) var source: texture_2d<f32>;
|
|
158
|
+
@group(0) @binding(1) var destination: texture_storage_2d<r32float, write>;
|
|
159
|
+
@compute @workgroup_size(1) fn main() {
|
|
160
|
+
let value = textureLoad(source, vec2i(0, 0), 0).r;
|
|
161
|
+
textureStore(destination, vec2i(0, 0), vec4f(value));
|
|
162
|
+
}
|
|
163
|
+
`
|
|
164
|
+
});
|
|
165
|
+
const pipeline = rawDevice.createComputePipeline({
|
|
166
|
+
layout: rawDevice.createPipelineLayout({ bindGroupLayouts: [layout] }),
|
|
167
|
+
compute: { module: shader, entryPoint: "main" }
|
|
168
|
+
});
|
|
169
|
+
const encoder = rawDevice.createCommandEncoder({ label: "r32float-profile" });
|
|
170
|
+
const pass = encoder.beginComputePass();
|
|
171
|
+
pass.setPipeline(pipeline);
|
|
172
|
+
pass.setBindGroup(0, bindGroup);
|
|
173
|
+
pass.dispatchWorkgroups(1);
|
|
174
|
+
pass.end();
|
|
175
|
+
stage = "finish";
|
|
176
|
+
readback = rawDevice.createBuffer({ size: 256, usage: MAP_READ | BUFFER_COPY_DST });
|
|
177
|
+
encoder.copyTextureToBuffer(
|
|
178
|
+
{ texture, mipLevel: 1 },
|
|
179
|
+
{ buffer: readback, bytesPerRow: 256, rowsPerImage: 1 },
|
|
180
|
+
{ width: 1, height: 1, depthOrArrayLayers: 1 }
|
|
181
|
+
);
|
|
182
|
+
const commandBuffer = encoder.finish();
|
|
183
|
+
stage = "submit";
|
|
184
|
+
rawDevice.queue.submit([commandBuffer]);
|
|
185
|
+
stage = "completion";
|
|
186
|
+
await rawDevice.queue.onSubmittedWorkDone();
|
|
187
|
+
const validationError = await rawDevice.popErrorScope();
|
|
188
|
+
if (validationError !== null) {
|
|
189
|
+
throw new Error(`WebGPU validation: ${validationError.message}`);
|
|
190
|
+
}
|
|
191
|
+
stage = "readback";
|
|
192
|
+
await readback.mapAsync(MAP_READ);
|
|
193
|
+
const values = Array.from(new Float32Array(readback.getMappedRange(0, 4).slice(0)));
|
|
194
|
+
readback.unmap();
|
|
195
|
+
const receipt = {
|
|
196
|
+
profile: "r32float-mip-sampled-storage",
|
|
197
|
+
verdict: "admitted",
|
|
198
|
+
evidence: "real",
|
|
199
|
+
deviceGeneration,
|
|
200
|
+
stages: R32FLOAT_PROBE_STAGES.map((entry) => ({
|
|
201
|
+
stage: entry,
|
|
202
|
+
verdict: "admitted",
|
|
203
|
+
evidence: "real"
|
|
204
|
+
})),
|
|
205
|
+
sampleType: "unfilterable-float",
|
|
206
|
+
usages: ["texture-binding", "storage-binding", "copy-src"],
|
|
207
|
+
readback: { byteLength: 4, values },
|
|
208
|
+
probeExecutions: 1
|
|
209
|
+
};
|
|
210
|
+
const validated = validateR32FloatReceipt(receipt);
|
|
211
|
+
return validated.ok ? ok(validated.value) : validated;
|
|
212
|
+
} catch (cause) {
|
|
213
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
214
|
+
createUnavailableR32FloatReceipt({
|
|
215
|
+
deviceGeneration,
|
|
216
|
+
failedStage: stage,
|
|
217
|
+
detail: message
|
|
218
|
+
});
|
|
219
|
+
return err(
|
|
220
|
+
new RhiError({
|
|
221
|
+
code: "rhi-texture-format-capability-unavailable",
|
|
222
|
+
expected: `r32float profile stage ${stage} to complete on the live WebGPU device`,
|
|
223
|
+
hint: "retain fallback-only rendering and inspect the device validation error",
|
|
224
|
+
detail: {
|
|
225
|
+
stage,
|
|
226
|
+
deviceGeneration,
|
|
227
|
+
reason: message
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
} finally {
|
|
232
|
+
texture?.destroy();
|
|
233
|
+
readback?.destroy();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
111
236
|
function resolveTimestampQueries(args) {
|
|
112
237
|
try {
|
|
113
238
|
args.rawEncoder.resolveQuerySet(
|
|
@@ -129,26 +254,6 @@ function resolveTimestampQueries(args) {
|
|
|
129
254
|
);
|
|
130
255
|
}
|
|
131
256
|
}
|
|
132
|
-
function writeTimestamp(args) {
|
|
133
|
-
const encoderWithTimestamp = args.rawEncoder;
|
|
134
|
-
if (typeof encoderWithTimestamp.writeTimestamp !== "function") {
|
|
135
|
-
throw new RhiError({
|
|
136
|
-
code: "webgpu-runtime-error",
|
|
137
|
-
expected: "underlying GPUCommandEncoder.writeTimestamp to be callable",
|
|
138
|
-
hint: "timestamp-query is advertised but the raw encoder has no writeTimestamp method"
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
try {
|
|
142
|
-
encoderWithTimestamp.writeTimestamp(args.rawQuerySet, args.queryIndex);
|
|
143
|
-
} catch (error) {
|
|
144
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
145
|
-
throw new RhiError({
|
|
146
|
-
code: "webgpu-runtime-error",
|
|
147
|
-
expected: "underlying GPUCommandEncoder.writeTimestamp to succeed",
|
|
148
|
-
hint: `writeTimestamp raised: ${message}`
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
257
|
|
|
153
258
|
// src/device.ts
|
|
154
259
|
function mirror(src, keys) {
|
|
@@ -203,6 +308,9 @@ var CP_KEYS = ["label", "layout", "compute"];
|
|
|
203
308
|
var QS_KEYS = ["label", "type", "count"];
|
|
204
309
|
var QUERY_SET_COUNT_LIMIT = 4096;
|
|
205
310
|
var RAW_DEVICE_MAP = /* @__PURE__ */ new WeakMap();
|
|
311
|
+
var RAW_DEVICE_GENERATION_MAP = /* @__PURE__ */ new WeakMap();
|
|
312
|
+
var R32FLOAT_PROBE_CACHE = /* @__PURE__ */ new WeakMap();
|
|
313
|
+
var NEXT_DEVICE_GENERATION = 1;
|
|
206
314
|
var BUFFER_RAW_MAP = /* @__PURE__ */ new WeakMap();
|
|
207
315
|
var TEXTURE_VIEW_RAW_MAP = /* @__PURE__ */ new WeakMap();
|
|
208
316
|
var ENCODER_STATE = /* @__PURE__ */ new WeakMap();
|
|
@@ -578,7 +686,7 @@ function mirrorRenderPipelineDescriptor(desc) {
|
|
|
578
686
|
...fragment === void 0 ? {} : { fragment }
|
|
579
687
|
};
|
|
580
688
|
}
|
|
581
|
-
function makeCommandEncoder(rawEncoder
|
|
689
|
+
function makeCommandEncoder(rawEncoder) {
|
|
582
690
|
function mirrorComputePassDescriptor(desc) {
|
|
583
691
|
if (desc === void 0) return void 0;
|
|
584
692
|
const out = mirror(desc, ["label"]);
|
|
@@ -631,6 +739,12 @@ function makeCommandEncoder(rawEncoder, caps, fireFeatureNotEnabled) {
|
|
|
631
739
|
};
|
|
632
740
|
return pass;
|
|
633
741
|
},
|
|
742
|
+
encodeEmptyComputePass(desc) {
|
|
743
|
+
const state = ENCODER_STATE.get(enc);
|
|
744
|
+
throwIfFinished(state);
|
|
745
|
+
const rawPass = rawEncoder.beginComputePass(mirrorComputePassDescriptor(desc));
|
|
746
|
+
rawPass.end();
|
|
747
|
+
},
|
|
634
748
|
copyBufferToBuffer(source, arg2, arg3, arg4, arg5) {
|
|
635
749
|
const state = ENCODER_STATE.get(enc);
|
|
636
750
|
throwIfFinished(state);
|
|
@@ -657,11 +771,19 @@ function makeCommandEncoder(rawEncoder, caps, fireFeatureNotEnabled) {
|
|
|
657
771
|
copyTextureToBuffer(source, destination, copySize) {
|
|
658
772
|
const state = ENCODER_STATE.get(enc);
|
|
659
773
|
throwIfFinished(state);
|
|
774
|
+
const rawSource = {
|
|
775
|
+
texture: source.texture
|
|
776
|
+
};
|
|
777
|
+
if (source.mipLevel !== void 0) rawSource.mipLevel = source.mipLevel;
|
|
778
|
+
if (source.origin !== void 0) rawSource.origin = source.origin;
|
|
779
|
+
if (source.aspect !== void 0) rawSource.aspect = source.aspect;
|
|
660
780
|
const rawDst = {
|
|
661
|
-
...destination,
|
|
662
781
|
buffer: BUFFER_RAW_MAP.get(destination.buffer) ?? destination.buffer
|
|
663
782
|
};
|
|
664
|
-
|
|
783
|
+
if (destination.offset !== void 0) rawDst.offset = destination.offset;
|
|
784
|
+
if (destination.bytesPerRow !== void 0) rawDst.bytesPerRow = destination.bytesPerRow;
|
|
785
|
+
if (destination.rowsPerImage !== void 0) rawDst.rowsPerImage = destination.rowsPerImage;
|
|
786
|
+
rawEncoder.copyTextureToBuffer(rawSource, rawDst, copySize);
|
|
665
787
|
},
|
|
666
788
|
copyTextureToTexture(source, destination, copySize) {
|
|
667
789
|
const state = ENCODER_STATE.get(enc);
|
|
@@ -741,19 +863,6 @@ function makeCommandEncoder(rawEncoder, caps, fireFeatureNotEnabled) {
|
|
|
741
863
|
insertDebugMarker(markerLabel) {
|
|
742
864
|
rawEncoder.insertDebugMarker(markerLabel);
|
|
743
865
|
},
|
|
744
|
-
writeTimestamp(querySet, queryIndex) {
|
|
745
|
-
const state = ENCODER_STATE.get(enc);
|
|
746
|
-
throwIfFinished(state);
|
|
747
|
-
if (caps.timestampQuery !== true) {
|
|
748
|
-
fireFeatureNotEnabled(
|
|
749
|
-
"timestamp-query",
|
|
750
|
-
"check device.caps.timestampQuery before calling writeTimestamp"
|
|
751
|
-
);
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
const rawQs = QUERY_SET_RAW_MAP.get(querySet) ?? querySet;
|
|
755
|
-
writeTimestamp({ rawEncoder, rawQuerySet: rawQs, queryIndex });
|
|
756
|
-
},
|
|
757
866
|
finish() {
|
|
758
867
|
const state = ENCODER_STATE.get(enc);
|
|
759
868
|
if (state === void 0) {
|
|
@@ -1049,10 +1158,22 @@ function makeRhiDevice(rawDevice) {
|
|
|
1049
1158
|
const features = rawDevice.features;
|
|
1050
1159
|
const limits = rawDevice.limits;
|
|
1051
1160
|
const queue = makeQueue(rawDevice.queue);
|
|
1161
|
+
const deviceGeneration = RAW_DEVICE_GENERATION_MAP.get(rawDevice) ?? (() => {
|
|
1162
|
+
const generation = NEXT_DEVICE_GENERATION++;
|
|
1163
|
+
RAW_DEVICE_GENERATION_MAP.set(rawDevice, generation);
|
|
1164
|
+
return generation;
|
|
1165
|
+
})();
|
|
1052
1166
|
const device = {
|
|
1053
1167
|
caps,
|
|
1054
1168
|
features,
|
|
1055
1169
|
limits,
|
|
1170
|
+
probeTextureFormatCapability() {
|
|
1171
|
+
const cached = R32FLOAT_PROBE_CACHE.get(device);
|
|
1172
|
+
if (cached !== void 0) return cached;
|
|
1173
|
+
const probe = probeR32FloatCapability(rawDevice, deviceGeneration);
|
|
1174
|
+
R32FLOAT_PROBE_CACHE.set(device, probe);
|
|
1175
|
+
return probe;
|
|
1176
|
+
},
|
|
1056
1177
|
queue,
|
|
1057
1178
|
lost: rawDevice.lost,
|
|
1058
1179
|
createBuffer(desc) {
|
|
@@ -1383,12 +1504,7 @@ function makeRhiDevice(rawDevice) {
|
|
|
1383
1504
|
const rawEnc = desc === void 0 ? rawDevice.createCommandEncoder() : rawDevice.createCommandEncoder(
|
|
1384
1505
|
mirror(desc, ENC_KEYS)
|
|
1385
1506
|
);
|
|
1386
|
-
|
|
1387
|
-
console.error(
|
|
1388
|
-
`[RhiError feature-not-enabled] expected: device.features.has('${featureName}') === true; hint: ${hint}`
|
|
1389
|
-
);
|
|
1390
|
-
};
|
|
1391
|
-
return ok(makeCommandEncoder(rawEnc, caps, fireFeatureNotEnabled));
|
|
1507
|
+
return ok(makeCommandEncoder(rawEnc));
|
|
1392
1508
|
}
|
|
1393
1509
|
// fix-f3: synchronous createShaderModule placeholder removed; the
|
|
1394
1510
|
// shader-compile-failed path lives in the top-level async factory
|