@fjandin/react-shader 0.0.18 → 0.0.20
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 +248 -22
- package/dist/ReactGpuShader.d.ts +3 -4
- package/dist/ReactGpuShader.d.ts.map +1 -1
- package/dist/hooks/useWebGPU.d.ts +2 -2
- package/dist/hooks/useWebGPU.d.ts.map +1 -1
- package/dist/index.cjs +193 -145
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +193 -145
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -220,30 +220,15 @@ function isVec3(value) {
|
|
|
220
220
|
function isVec4(value) {
|
|
221
221
|
return Array.isArray(value) && value.length === 4 && typeof value[0] === "number";
|
|
222
222
|
}
|
|
223
|
-
function isVec4Array(value) {
|
|
224
|
-
return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 4;
|
|
225
|
-
}
|
|
226
223
|
function inferWgslType(value) {
|
|
227
|
-
if (typeof value === "number")
|
|
228
|
-
return
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
arrayLength: 100
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
if (isVec4(value)) {
|
|
239
|
-
return { wgslType: "vec4f", baseType: "vec4f", isArray: false, arrayLength: 0 };
|
|
240
|
-
}
|
|
241
|
-
if (isVec3(value)) {
|
|
242
|
-
return { wgslType: "vec3f", baseType: "vec3f", isArray: false, arrayLength: 0 };
|
|
243
|
-
}
|
|
244
|
-
if (isVec2(value)) {
|
|
245
|
-
return { wgslType: "vec2f", baseType: "vec2f", isArray: false, arrayLength: 0 };
|
|
246
|
-
}
|
|
224
|
+
if (typeof value === "number")
|
|
225
|
+
return "f32";
|
|
226
|
+
if (isVec4(value))
|
|
227
|
+
return "vec4f";
|
|
228
|
+
if (isVec3(value))
|
|
229
|
+
return "vec3f";
|
|
230
|
+
if (isVec2(value))
|
|
231
|
+
return "vec2f";
|
|
247
232
|
throw new Error(`Unsupported uniform value type: ${typeof value}`);
|
|
248
233
|
}
|
|
249
234
|
function getTypeAlignment(type) {
|
|
@@ -269,7 +254,6 @@ function getTypeSize(type) {
|
|
|
269
254
|
return 16;
|
|
270
255
|
}
|
|
271
256
|
}
|
|
272
|
-
var UNIFORM_ARRAY_STRIDE = 16;
|
|
273
257
|
var DEFAULT_UNIFORMS = [
|
|
274
258
|
{ name: "iTime", baseType: "f32" },
|
|
275
259
|
{ name: "iMouseLeftDown", baseType: "f32" },
|
|
@@ -284,49 +268,20 @@ function calculateUniformLayout(customUniforms) {
|
|
|
284
268
|
const alignment = getTypeAlignment(baseType);
|
|
285
269
|
const size = getTypeSize(baseType);
|
|
286
270
|
offset = Math.ceil(offset / alignment) * alignment;
|
|
287
|
-
fields.push({ name, type: baseType, offset, size
|
|
271
|
+
fields.push({ name, type: baseType, offset, size });
|
|
288
272
|
offset += size;
|
|
289
273
|
};
|
|
290
|
-
const addArrayField = (name, baseType, arrayLength) => {
|
|
291
|
-
offset = Math.ceil(offset / 16) * 16;
|
|
292
|
-
const totalSize = arrayLength * UNIFORM_ARRAY_STRIDE;
|
|
293
|
-
fields.push({
|
|
294
|
-
name,
|
|
295
|
-
type: `array<${baseType}, ${arrayLength}>`,
|
|
296
|
-
offset,
|
|
297
|
-
size: totalSize,
|
|
298
|
-
isArray: true,
|
|
299
|
-
arrayLength,
|
|
300
|
-
elementStride: UNIFORM_ARRAY_STRIDE
|
|
301
|
-
});
|
|
302
|
-
offset += totalSize;
|
|
303
|
-
};
|
|
304
274
|
for (const u of DEFAULT_UNIFORMS) {
|
|
305
275
|
addField(u.name, u.baseType);
|
|
306
276
|
}
|
|
307
277
|
if (customUniforms) {
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
scalarEntries.push({ name, inferred });
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
for (const { name } of arrayEntries) {
|
|
319
|
-
scalarEntries.push({
|
|
320
|
-
name: `${name}_count`,
|
|
321
|
-
inferred: { wgslType: "f32", baseType: "f32", isArray: false, arrayLength: 0 }
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
scalarEntries.sort((a, b) => getTypeAlignment(b.inferred.baseType) - getTypeAlignment(a.inferred.baseType));
|
|
325
|
-
for (const { name, inferred } of scalarEntries) {
|
|
326
|
-
addField(name, inferred.baseType);
|
|
327
|
-
}
|
|
328
|
-
for (const { name, inferred } of arrayEntries) {
|
|
329
|
-
addArrayField(name, inferred.baseType, inferred.arrayLength);
|
|
278
|
+
const entries = Object.entries(customUniforms).map(([name, value]) => ({
|
|
279
|
+
name,
|
|
280
|
+
baseType: inferWgslType(value)
|
|
281
|
+
}));
|
|
282
|
+
entries.sort((a, b) => getTypeAlignment(b.baseType) - getTypeAlignment(a.baseType));
|
|
283
|
+
for (const { name, baseType } of entries) {
|
|
284
|
+
addField(name, baseType);
|
|
330
285
|
}
|
|
331
286
|
}
|
|
332
287
|
const bufferSize = Math.ceil(offset / 16) * 16;
|
|
@@ -341,21 +296,9 @@ ${members}
|
|
|
341
296
|
}
|
|
342
297
|
function packUniformValue(field, value, floatData) {
|
|
343
298
|
const floatOffset = field.offset / 4;
|
|
344
|
-
if (
|
|
345
|
-
const stride = field.elementStride / 4;
|
|
346
|
-
const maxLen = field.arrayLength;
|
|
347
|
-
if (isVec4Array(value)) {
|
|
348
|
-
for (let i = 0;i < value.length && i < maxLen; i++) {
|
|
349
|
-
const elemOffset = floatOffset + i * stride;
|
|
350
|
-
floatData[elemOffset] = value[i][0];
|
|
351
|
-
floatData[elemOffset + 1] = value[i][1];
|
|
352
|
-
floatData[elemOffset + 2] = value[i][2];
|
|
353
|
-
floatData[elemOffset + 3] = value[i][3];
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
} else if (typeof value === "number") {
|
|
299
|
+
if (typeof value === "number") {
|
|
357
300
|
floatData[floatOffset] = value;
|
|
358
|
-
} else if (Array.isArray(value)
|
|
301
|
+
} else if (Array.isArray(value)) {
|
|
359
302
|
for (let i = 0;i < value.length; i++) {
|
|
360
303
|
floatData[floatOffset + i] = value[i];
|
|
361
304
|
}
|
|
@@ -383,11 +326,60 @@ fn main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
|
383
326
|
return output;
|
|
384
327
|
}
|
|
385
328
|
`;
|
|
386
|
-
function
|
|
329
|
+
function getStorageBufferNames(defs) {
|
|
330
|
+
if (!defs)
|
|
331
|
+
return [];
|
|
332
|
+
return Object.keys(defs).sort();
|
|
333
|
+
}
|
|
334
|
+
function createStorageBuffer(device, name, binding, data) {
|
|
335
|
+
const length = Math.max(data.length, 1);
|
|
336
|
+
const byteSize = length * 16;
|
|
337
|
+
const buffer = device.createBuffer({
|
|
338
|
+
label: `storage: ${name}`,
|
|
339
|
+
size: byteSize,
|
|
340
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
341
|
+
});
|
|
342
|
+
const packingArray = new Float32Array(length * 4);
|
|
343
|
+
for (let i = 0;i < data.length; i++) {
|
|
344
|
+
const off = i * 4;
|
|
345
|
+
packingArray[off] = data[i][0];
|
|
346
|
+
packingArray[off + 1] = data[i][1];
|
|
347
|
+
packingArray[off + 2] = data[i][2];
|
|
348
|
+
packingArray[off + 3] = data[i][3];
|
|
349
|
+
}
|
|
350
|
+
device.queue.writeBuffer(buffer, 0, packingArray);
|
|
351
|
+
return { name, binding, buffer, currentLength: length, dataLength: data.length, packingArray };
|
|
352
|
+
}
|
|
353
|
+
function packAndUploadStorageBuffer(device, entry, data) {
|
|
354
|
+
const arr = entry.packingArray;
|
|
355
|
+
for (let i = 0;i < data.length; i++) {
|
|
356
|
+
const off = i * 4;
|
|
357
|
+
arr[off] = data[i][0];
|
|
358
|
+
arr[off + 1] = data[i][1];
|
|
359
|
+
arr[off + 2] = data[i][2];
|
|
360
|
+
arr[off + 3] = data[i][3];
|
|
361
|
+
}
|
|
362
|
+
const uploadLength = Math.max(entry.dataLength, 1);
|
|
363
|
+
device.queue.writeBuffer(entry.buffer, 0, arr, 0, uploadLength * 4);
|
|
364
|
+
}
|
|
365
|
+
function rebuildBindGroup(state) {
|
|
366
|
+
const entries = [{ binding: 0, resource: { buffer: state.uniformBuffer } }];
|
|
367
|
+
for (const sb of state.storageBuffers) {
|
|
368
|
+
entries.push({ binding: sb.binding, resource: { buffer: sb.buffer } });
|
|
369
|
+
}
|
|
370
|
+
return state.device.createBindGroup({
|
|
371
|
+
layout: state.bindGroupLayout,
|
|
372
|
+
entries
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
function createFragmentShader(userCode, layout, storageBufferNames) {
|
|
376
|
+
const storageDeclarations = storageBufferNames.map((name, i) => `@group(0) @binding(${i + 1}) var<storage, read> ${name}: array<vec4f>;`).join(`
|
|
377
|
+
`);
|
|
387
378
|
return `
|
|
388
379
|
${generateUniformStruct(layout)}
|
|
389
380
|
|
|
390
381
|
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
382
|
+
${storageDeclarations}
|
|
391
383
|
|
|
392
384
|
${userCode}
|
|
393
385
|
|
|
@@ -400,7 +392,7 @@ fn main(@location(0) uv: vec2f) -> @location(0) vec4f {
|
|
|
400
392
|
}
|
|
401
393
|
`;
|
|
402
394
|
}
|
|
403
|
-
async function initializeWebGPU(canvas, fragmentSource, customUniforms) {
|
|
395
|
+
async function initializeWebGPU(canvas, fragmentSource, customUniforms, storageBufferDefs) {
|
|
404
396
|
if (!navigator.gpu) {
|
|
405
397
|
throw new Error("WebGPU not supported in this browser");
|
|
406
398
|
}
|
|
@@ -409,6 +401,9 @@ async function initializeWebGPU(canvas, fragmentSource, customUniforms) {
|
|
|
409
401
|
throw new Error("Failed to get WebGPU adapter");
|
|
410
402
|
}
|
|
411
403
|
const device = await adapter.requestDevice();
|
|
404
|
+
device.lost.then((info) => {
|
|
405
|
+
console.error(`WebGPU device lost: ${info.message}`);
|
|
406
|
+
});
|
|
412
407
|
const context = canvas.getContext("webgpu");
|
|
413
408
|
if (!context) {
|
|
414
409
|
throw new Error("Failed to get WebGPU context");
|
|
@@ -420,36 +415,43 @@ async function initializeWebGPU(canvas, fragmentSource, customUniforms) {
|
|
|
420
415
|
alphaMode: "premultiplied"
|
|
421
416
|
});
|
|
422
417
|
const uniformLayout = calculateUniformLayout(customUniforms);
|
|
418
|
+
const storageBufferNames = getStorageBufferNames(storageBufferDefs);
|
|
423
419
|
const vertexModule = device.createShaderModule({
|
|
424
420
|
label: "vertex shader",
|
|
425
421
|
code: VERTEX_SHADER
|
|
426
422
|
});
|
|
427
423
|
const fragmentModule = device.createShaderModule({
|
|
428
424
|
label: "fragment shader",
|
|
429
|
-
code: createFragmentShader(fragmentSource, uniformLayout)
|
|
425
|
+
code: createFragmentShader(fragmentSource, uniformLayout, storageBufferNames)
|
|
430
426
|
});
|
|
431
427
|
const uniformBuffer = device.createBuffer({
|
|
432
428
|
label: "uniforms",
|
|
433
429
|
size: uniformLayout.bufferSize,
|
|
434
430
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
435
431
|
});
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
432
|
+
const storageBuffers = storageBufferNames.map((name, i) => createStorageBuffer(device, name, i + 1, storageBufferDefs?.[name] ?? []));
|
|
433
|
+
const layoutEntries = [
|
|
434
|
+
{
|
|
435
|
+
binding: 0,
|
|
436
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
437
|
+
buffer: { type: "uniform" }
|
|
438
|
+
}
|
|
439
|
+
];
|
|
440
|
+
for (const sb of storageBuffers) {
|
|
441
|
+
layoutEntries.push({
|
|
442
|
+
binding: sb.binding,
|
|
443
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
444
|
+
buffer: { type: "read-only-storage" }
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
const bindGroupLayout = device.createBindGroupLayout({ entries: layoutEntries });
|
|
448
|
+
const bindGroupEntries = [{ binding: 0, resource: { buffer: uniformBuffer } }];
|
|
449
|
+
for (const sb of storageBuffers) {
|
|
450
|
+
bindGroupEntries.push({ binding: sb.binding, resource: { buffer: sb.buffer } });
|
|
451
|
+
}
|
|
445
452
|
const uniformBindGroup = device.createBindGroup({
|
|
446
453
|
layout: bindGroupLayout,
|
|
447
|
-
entries:
|
|
448
|
-
{
|
|
449
|
-
binding: 0,
|
|
450
|
-
resource: { buffer: uniformBuffer }
|
|
451
|
-
}
|
|
452
|
-
]
|
|
454
|
+
entries: bindGroupEntries
|
|
453
455
|
});
|
|
454
456
|
const pipelineLayout = device.createPipelineLayout({
|
|
455
457
|
bindGroupLayouts: [bindGroupLayout]
|
|
@@ -473,10 +475,26 @@ async function initializeWebGPU(canvas, fragmentSource, customUniforms) {
|
|
|
473
475
|
pipeline,
|
|
474
476
|
uniformBuffer,
|
|
475
477
|
uniformBindGroup,
|
|
476
|
-
uniformLayout
|
|
478
|
+
uniformLayout,
|
|
479
|
+
bindGroupLayout,
|
|
480
|
+
storageBuffers,
|
|
481
|
+
renderPassDescriptor: {
|
|
482
|
+
colorAttachments: [
|
|
483
|
+
{
|
|
484
|
+
view: undefined,
|
|
485
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
486
|
+
loadOp: "clear",
|
|
487
|
+
storeOp: "store"
|
|
488
|
+
}
|
|
489
|
+
]
|
|
490
|
+
},
|
|
491
|
+
submitArray: [null]
|
|
477
492
|
};
|
|
478
493
|
}
|
|
479
494
|
function cleanupWebGPU(state) {
|
|
495
|
+
for (const sb of state.storageBuffers) {
|
|
496
|
+
sb.buffer.destroy();
|
|
497
|
+
}
|
|
480
498
|
state.uniformBuffer.destroy();
|
|
481
499
|
state.device.destroy();
|
|
482
500
|
}
|
|
@@ -488,6 +506,7 @@ function useWebGPU(options) {
|
|
|
488
506
|
const lastFrameTimeRef = useRef2(0);
|
|
489
507
|
const mouseRef = useRef2([0, 0]);
|
|
490
508
|
const mouseNormalizedRef = useRef2([0, 0]);
|
|
509
|
+
const resolutionRef = useRef2([0, 0]);
|
|
491
510
|
const mouseLeftDownRef = useRef2(false);
|
|
492
511
|
const canvasRectRef = useRef2(null);
|
|
493
512
|
const onErrorRef = useRef2(options.onError);
|
|
@@ -500,7 +519,18 @@ function useWebGPU(options) {
|
|
|
500
519
|
const timeScaleRef = useRef2(options.timeScale ?? 1);
|
|
501
520
|
const fragmentRef = useRef2(options.fragment);
|
|
502
521
|
const uniformsRef = useRef2(options.uniforms);
|
|
522
|
+
const storageBuffersRef = useRef2(options.storageBuffers);
|
|
503
523
|
const dprRef = useRef2(window.devicePixelRatio || 1);
|
|
524
|
+
const uniformDataRef = useRef2(null);
|
|
525
|
+
const allValuesRef = useRef2({});
|
|
526
|
+
const frameInfoRef = useRef2({
|
|
527
|
+
deltaTime: 0,
|
|
528
|
+
time: 0,
|
|
529
|
+
resolution: [0, 0],
|
|
530
|
+
mouse: [0, 0],
|
|
531
|
+
mouseNormalized: [0, 0],
|
|
532
|
+
mouseLeftDown: false
|
|
533
|
+
});
|
|
504
534
|
onErrorRef.current = options.onError;
|
|
505
535
|
onFrameRef.current = options.onFrame;
|
|
506
536
|
onClickRef.current = options.onClick;
|
|
@@ -511,17 +541,7 @@ function useWebGPU(options) {
|
|
|
511
541
|
timeScaleRef.current = options.timeScale ?? 1;
|
|
512
542
|
fragmentRef.current = options.fragment;
|
|
513
543
|
uniformsRef.current = options.uniforms;
|
|
514
|
-
|
|
515
|
-
const canvas = canvasRef.current;
|
|
516
|
-
return {
|
|
517
|
-
deltaTime,
|
|
518
|
-
time: elapsedTimeRef.current,
|
|
519
|
-
resolution: [canvas?.width ?? 0, canvas?.height ?? 0],
|
|
520
|
-
mouse: mouseRef.current,
|
|
521
|
-
mouseNormalized: mouseNormalizedRef.current,
|
|
522
|
-
mouseLeftDown: mouseLeftDownRef.current
|
|
523
|
-
};
|
|
524
|
-
}, []);
|
|
544
|
+
storageBuffersRef.current = options.storageBuffers;
|
|
525
545
|
const render = useCallback2((time) => {
|
|
526
546
|
const state = stateRef.current;
|
|
527
547
|
const canvas = canvasRef.current;
|
|
@@ -531,10 +551,18 @@ function useWebGPU(options) {
|
|
|
531
551
|
const deltaTime = lastFrameTimeRef.current === 0 ? 0 : (time - lastFrameTimeRef.current) / 1000;
|
|
532
552
|
lastFrameTimeRef.current = time;
|
|
533
553
|
elapsedTimeRef.current += deltaTime * timeScaleRef.current;
|
|
554
|
+
const info = frameInfoRef.current;
|
|
555
|
+
info.deltaTime = deltaTime;
|
|
556
|
+
info.time = elapsedTimeRef.current;
|
|
557
|
+
info.resolution[0] = canvas.width;
|
|
558
|
+
info.resolution[1] = canvas.height;
|
|
559
|
+
info.mouse = mouseRef.current;
|
|
560
|
+
info.mouseNormalized = mouseNormalizedRef.current;
|
|
561
|
+
info.mouseLeftDown = mouseLeftDownRef.current;
|
|
534
562
|
if (onFrameRef.current) {
|
|
535
|
-
onFrameRef.current(
|
|
563
|
+
onFrameRef.current(frameInfoRef.current);
|
|
536
564
|
}
|
|
537
|
-
const { device, context, pipeline, uniformBuffer,
|
|
565
|
+
const { device, context, pipeline, uniformBuffer, uniformLayout } = state;
|
|
538
566
|
const dpr = dprRef.current;
|
|
539
567
|
const displayWidth = canvas.clientWidth;
|
|
540
568
|
const displayHeight = canvas.clientHeight;
|
|
@@ -548,22 +576,21 @@ function useWebGPU(options) {
|
|
|
548
576
|
canvas.width = bufferWidth;
|
|
549
577
|
canvas.height = bufferHeight;
|
|
550
578
|
}
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
}
|
|
579
|
+
const allValues = allValuesRef.current;
|
|
580
|
+
allValues.iTime = elapsedTimeRef.current;
|
|
581
|
+
allValues.iMouseLeftDown = mouseLeftDownRef.current ? 1 : 0;
|
|
582
|
+
resolutionRef.current[0] = canvas.width;
|
|
583
|
+
resolutionRef.current[1] = canvas.height;
|
|
584
|
+
allValues.iResolution = resolutionRef.current;
|
|
585
|
+
allValues.iMouse = mouseRef.current;
|
|
586
|
+
allValues.iMouseNormalized = mouseNormalizedRef.current;
|
|
587
|
+
const customs = uniformsRef.current;
|
|
588
|
+
if (customs) {
|
|
589
|
+
for (const name in customs) {
|
|
590
|
+
allValues[name] = customs[name];
|
|
564
591
|
}
|
|
565
592
|
}
|
|
566
|
-
const uniformData =
|
|
593
|
+
const uniformData = uniformDataRef.current;
|
|
567
594
|
for (const field of uniformLayout.fields) {
|
|
568
595
|
const value = allValues[field.name];
|
|
569
596
|
if (value === undefined) {
|
|
@@ -572,25 +599,43 @@ function useWebGPU(options) {
|
|
|
572
599
|
packUniformValue(field, value, uniformData);
|
|
573
600
|
}
|
|
574
601
|
device.queue.writeBuffer(uniformBuffer, 0, uniformData);
|
|
602
|
+
let needsBindGroupRebuild = false;
|
|
603
|
+
for (const entry of state.storageBuffers) {
|
|
604
|
+
const data = storageBuffersRef.current?.[entry.name];
|
|
605
|
+
if (!data)
|
|
606
|
+
continue;
|
|
607
|
+
const requiredLength = Math.max(data.length, 1);
|
|
608
|
+
if (requiredLength > entry.currentLength || requiredLength < entry.currentLength / 2) {
|
|
609
|
+
const allocLength = Math.max(Math.ceil(requiredLength * 1.5), 1);
|
|
610
|
+
entry.buffer.destroy();
|
|
611
|
+
const byteSize = allocLength * 16;
|
|
612
|
+
entry.buffer = device.createBuffer({
|
|
613
|
+
label: `storage: ${entry.name}`,
|
|
614
|
+
size: byteSize,
|
|
615
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
616
|
+
});
|
|
617
|
+
entry.packingArray = new Float32Array(allocLength * 4);
|
|
618
|
+
entry.currentLength = allocLength;
|
|
619
|
+
needsBindGroupRebuild = true;
|
|
620
|
+
}
|
|
621
|
+
entry.dataLength = data.length;
|
|
622
|
+
packAndUploadStorageBuffer(device, entry, data);
|
|
623
|
+
}
|
|
624
|
+
if (needsBindGroupRebuild) {
|
|
625
|
+
state.uniformBindGroup = rebuildBindGroup(state);
|
|
626
|
+
}
|
|
575
627
|
const commandEncoder = device.createCommandEncoder();
|
|
576
628
|
const textureView = context.getCurrentTexture().createView();
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
{
|
|
580
|
-
view: textureView,
|
|
581
|
-
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
582
|
-
loadOp: "clear",
|
|
583
|
-
storeOp: "store"
|
|
584
|
-
}
|
|
585
|
-
]
|
|
586
|
-
});
|
|
629
|
+
state.renderPassDescriptor.colorAttachments[0].view = textureView;
|
|
630
|
+
const renderPass = commandEncoder.beginRenderPass(state.renderPassDescriptor);
|
|
587
631
|
renderPass.setPipeline(pipeline);
|
|
588
|
-
renderPass.setBindGroup(0, uniformBindGroup);
|
|
632
|
+
renderPass.setBindGroup(0, state.uniformBindGroup);
|
|
589
633
|
renderPass.draw(3);
|
|
590
634
|
renderPass.end();
|
|
591
|
-
|
|
635
|
+
state.submitArray[0] = commandEncoder.finish();
|
|
636
|
+
device.queue.submit(state.submitArray);
|
|
592
637
|
animationFrameRef.current = requestAnimationFrame(render);
|
|
593
|
-
}, [
|
|
638
|
+
}, []);
|
|
594
639
|
useEffect(() => {
|
|
595
640
|
const canvas = canvasRef.current;
|
|
596
641
|
if (!canvas)
|
|
@@ -598,12 +643,13 @@ function useWebGPU(options) {
|
|
|
598
643
|
let mounted = true;
|
|
599
644
|
const initialize = async () => {
|
|
600
645
|
try {
|
|
601
|
-
const state = await initializeWebGPU(canvas, fragmentRef.current, uniformsRef.current);
|
|
646
|
+
const state = await initializeWebGPU(canvas, fragmentRef.current, uniformsRef.current, storageBuffersRef.current);
|
|
602
647
|
if (!mounted) {
|
|
603
648
|
cleanupWebGPU(state);
|
|
604
649
|
return;
|
|
605
650
|
}
|
|
606
651
|
stateRef.current = state;
|
|
652
|
+
uniformDataRef.current = new Float32Array(state.uniformLayout.bufferSize / 4);
|
|
607
653
|
elapsedTimeRef.current = 0;
|
|
608
654
|
lastFrameTimeRef.current = 0;
|
|
609
655
|
animationFrameRef.current = requestAnimationFrame(render);
|
|
@@ -658,25 +704,25 @@ function useWebGPU(options) {
|
|
|
658
704
|
(mouseRef.current[0] - canvas.width / 2) / minDimension,
|
|
659
705
|
(mouseRef.current[1] - canvas.height / 2) / minDimension
|
|
660
706
|
];
|
|
661
|
-
onMouseMoveRef.current?.(
|
|
707
|
+
onMouseMoveRef.current?.(frameInfoRef.current);
|
|
662
708
|
};
|
|
663
709
|
const handleMouseDown = (event) => {
|
|
664
710
|
if (event.button === 0) {
|
|
665
711
|
mouseLeftDownRef.current = true;
|
|
666
712
|
}
|
|
667
|
-
onMouseDownRef.current?.(
|
|
713
|
+
onMouseDownRef.current?.(frameInfoRef.current);
|
|
668
714
|
};
|
|
669
715
|
const handleMouseUp = (event) => {
|
|
670
716
|
if (event.button === 0) {
|
|
671
717
|
mouseLeftDownRef.current = false;
|
|
672
718
|
}
|
|
673
|
-
onMouseUpRef.current?.(
|
|
719
|
+
onMouseUpRef.current?.(frameInfoRef.current);
|
|
674
720
|
};
|
|
675
721
|
const handleClick = () => {
|
|
676
|
-
onClickRef.current?.(
|
|
722
|
+
onClickRef.current?.(frameInfoRef.current);
|
|
677
723
|
};
|
|
678
724
|
const handleMouseWheel = (event) => {
|
|
679
|
-
onMouseWheelRef.current?.(
|
|
725
|
+
onMouseWheelRef.current?.(frameInfoRef.current, event.deltaY);
|
|
680
726
|
};
|
|
681
727
|
window.addEventListener("mousemove", handleMouseMove);
|
|
682
728
|
window.addEventListener("mousedown", handleMouseDown);
|
|
@@ -692,7 +738,7 @@ function useWebGPU(options) {
|
|
|
692
738
|
canvas.removeEventListener("click", handleClick);
|
|
693
739
|
window.removeEventListener("wheel", handleMouseWheel);
|
|
694
740
|
};
|
|
695
|
-
}, [
|
|
741
|
+
}, []);
|
|
696
742
|
return { canvasRef, mouseRef };
|
|
697
743
|
}
|
|
698
744
|
|
|
@@ -720,6 +766,7 @@ function ReactGpuShader({
|
|
|
720
766
|
className,
|
|
721
767
|
fragment,
|
|
722
768
|
uniforms,
|
|
769
|
+
storageBuffers,
|
|
723
770
|
fullscreen = false,
|
|
724
771
|
timeScale,
|
|
725
772
|
onFrame,
|
|
@@ -740,6 +787,7 @@ function ReactGpuShader({
|
|
|
740
787
|
const { canvasRef } = useWebGPU({
|
|
741
788
|
fragment,
|
|
742
789
|
uniforms,
|
|
790
|
+
storageBuffers,
|
|
743
791
|
onError: handleError,
|
|
744
792
|
timeScale,
|
|
745
793
|
onFrame,
|
|
@@ -991,11 +1039,11 @@ function isVec2Array(value) {
|
|
|
991
1039
|
function isVec3Array(value) {
|
|
992
1040
|
return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 3;
|
|
993
1041
|
}
|
|
994
|
-
function
|
|
1042
|
+
function isVec4Array(value) {
|
|
995
1043
|
return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 4;
|
|
996
1044
|
}
|
|
997
1045
|
function isArrayUniform(value) {
|
|
998
|
-
return isFloatArray(value) || isVec2Array(value) || isVec3Array(value) ||
|
|
1046
|
+
return isFloatArray(value) || isVec2Array(value) || isVec3Array(value) || isVec4Array(value);
|
|
999
1047
|
}
|
|
1000
1048
|
function setUniform(gl, location, value) {
|
|
1001
1049
|
if (location === null) {
|
|
@@ -1003,7 +1051,7 @@ function setUniform(gl, location, value) {
|
|
|
1003
1051
|
}
|
|
1004
1052
|
if (typeof value === "number") {
|
|
1005
1053
|
gl.uniform1f(location, value);
|
|
1006
|
-
} else if (
|
|
1054
|
+
} else if (isVec4Array(value)) {
|
|
1007
1055
|
gl.uniform4fv(location, value.flat());
|
|
1008
1056
|
} else if (isVec3Array(value)) {
|
|
1009
1057
|
gl.uniform3fv(location, value.flat());
|
|
@@ -1059,7 +1107,7 @@ function getUniformType(value) {
|
|
|
1059
1107
|
if (typeof value === "number") {
|
|
1060
1108
|
return "float";
|
|
1061
1109
|
}
|
|
1062
|
-
if (
|
|
1110
|
+
if (isVec4Array(value)) {
|
|
1063
1111
|
return `vec4[${MAX_ARRAY_LENGTH}]`;
|
|
1064
1112
|
}
|
|
1065
1113
|
if (isVec3Array(value)) {
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export type FloatArray = number[];
|
|
|
5
5
|
export type Vec2Array = Vec2[];
|
|
6
6
|
export type Vec3Array = Vec3[];
|
|
7
7
|
export type Vec4Array = Vec4[];
|
|
8
|
+
export type GpuUniformValue = number | Vec2 | Vec3 | Vec4;
|
|
9
|
+
export type GpuStorageBuffers = Record<string, Vec4Array>;
|
|
8
10
|
export type TextureSource = HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap | ImageData | OffscreenCanvas;
|
|
9
11
|
export type TextureWrap = "repeat" | "clamp" | "mirror";
|
|
10
12
|
export type TextureMinFilter = "nearest" | "linear" | "mipmap";
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AACnC,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAC3C,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAEnD,MAAM,MAAM,UAAU,GAAG,MAAM,EAAE,CAAA;AACjC,MAAM,MAAM,SAAS,GAAG,IAAI,EAAE,CAAA;AAC9B,MAAM,MAAM,SAAS,GAAG,IAAI,EAAE,CAAA;AAC9B,MAAM,MAAM,SAAS,GAAG,IAAI,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|