wgsl-renderer 0.0.5 → 0.1.1
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 +188 -1
- package/README.zh-CN.md +208 -4
- package/dist/cjs/index.js +175 -14
- package/dist/esm/index.d.ts +76 -3
- package/dist/esm/index.js +175 -14
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -198,13 +198,14 @@ Add a render pass.
|
|
|
198
198
|
interface RenderPassOptions {
|
|
199
199
|
name: string;
|
|
200
200
|
shaderCode: string;
|
|
201
|
-
entryPoints?: {
|
|
201
|
+
entryPoints?: {
|
|
202
202
|
vertex?: string; // Default is 'vs_main' function
|
|
203
203
|
fragment?: string; // Default is 'fs_main' function
|
|
204
204
|
};
|
|
205
205
|
clearColor?: { r: number; g: number; b: number; a: number };
|
|
206
206
|
blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
|
|
207
207
|
resources?: GPUBindingResource[];
|
|
208
|
+
bindGroupSets?: { [setName: string]: GPUBindingResource[] }; // Multiple bind group sets
|
|
208
209
|
view?: GPUTextureView; // Optional custom view for this pass
|
|
209
210
|
format?: GPUTextureFormat; // Optional format for the view (required when using custom view with different format)
|
|
210
211
|
}
|
|
@@ -293,7 +294,193 @@ renderer.loopRender(time => {
|
|
|
293
294
|
#### renderer.stopLoop()
|
|
294
295
|
Stop loop rendering.
|
|
295
296
|
|
|
297
|
+
### Bind Group Switching
|
|
296
298
|
|
|
299
|
+
The renderer supports switching between different bind group sets at runtime. This is useful for:
|
|
300
|
+
- Switching between different textures
|
|
301
|
+
- Changing shader parameters dynamically
|
|
302
|
+
- Implementing multi-material rendering
|
|
303
|
+
|
|
304
|
+
#### renderer.switchBindGroupSet(passName, setName)
|
|
305
|
+
|
|
306
|
+
Switch to a different bind group set for a specific pass.
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// Add pass with multiple bind group sets
|
|
310
|
+
renderer.addPass({
|
|
311
|
+
name: 'main',
|
|
312
|
+
shaderCode: myShader,
|
|
313
|
+
resources: [uniforms, sampler, texture1], // Default resources
|
|
314
|
+
bindGroupSets: {
|
|
315
|
+
'material1': [uniforms, sampler, texture1],
|
|
316
|
+
'material2': [uniforms, sampler, texture2],
|
|
317
|
+
'material3': [uniforms, sampler, texture3],
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Switch between materials
|
|
322
|
+
renderer.switchBindGroupSet('main', 'material1');
|
|
323
|
+
renderer.switchBindGroupSet('main', 'material2');
|
|
324
|
+
renderer.switchBindGroupSet('main', 'material3');
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Example: Dynamic Texture Switching**
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
// Create multiple textures
|
|
331
|
+
const textures = [
|
|
332
|
+
await renderer.loadImageTexture('texture1.png'),
|
|
333
|
+
await renderer.loadImageTexture('texture2.png'),
|
|
334
|
+
await renderer.loadImageTexture('texture3.png'),
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
// Add pass with bind group sets
|
|
338
|
+
renderer.addPass({
|
|
339
|
+
name: 'renderer',
|
|
340
|
+
shaderCode: textureShader,
|
|
341
|
+
resources: [uniforms, sampler, textures[0]], // Default
|
|
342
|
+
bindGroupSets: {
|
|
343
|
+
'texture0': [uniforms, sampler, textures[0]],
|
|
344
|
+
'texture1': [uniforms, sampler, textures[1]],
|
|
345
|
+
'texture2': [uniforms, sampler, textures[2]],
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// User controls
|
|
350
|
+
document.getElementById('btn1').onclick = () => {
|
|
351
|
+
renderer.switchBindGroupSet('renderer', 'texture0');
|
|
352
|
+
};
|
|
353
|
+
document.getElementById('btn2').onclick = () => {
|
|
354
|
+
renderer.switchBindGroupSet('renderer', 'texture1');
|
|
355
|
+
};
|
|
356
|
+
document.getElementById('btn3').onclick = () => {
|
|
357
|
+
renderer.switchBindGroupSet('renderer', 'texture2');
|
|
358
|
+
};
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
#### renderer.updateBindGroupSetResources(passName, setName, resources)
|
|
362
|
+
|
|
363
|
+
Dynamically update or add a bind group set with new resources. This allows runtime modification of bind groups without recreating the entire pass.
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// Update a bind group set with new texture
|
|
367
|
+
const newTexture = renderer.createTexture({ /* options */ });
|
|
368
|
+
renderer.updateBindGroupSetResources('main', 'textureSet', [
|
|
369
|
+
uniforms,
|
|
370
|
+
sampler,
|
|
371
|
+
newTexture,
|
|
372
|
+
]);
|
|
373
|
+
|
|
374
|
+
// Create a new bind group set on the fly
|
|
375
|
+
renderer.updateBindGroupSetResources('main', 'newSet', [
|
|
376
|
+
newUniforms,
|
|
377
|
+
newSampler,
|
|
378
|
+
anotherTexture,
|
|
379
|
+
]);
|
|
380
|
+
renderer.switchBindGroupSet('main', 'newSet');
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
This is useful for:
|
|
384
|
+
- Streaming textures in real-time
|
|
385
|
+
- Updating shader parameters dynamically
|
|
386
|
+
- Creating procedural content at runtime
|
|
387
|
+
- Memory-efficient resource management
|
|
388
|
+
|
|
389
|
+
## Render Pass Management
|
|
390
|
+
|
|
391
|
+
The renderer provides flexible pass management capabilities, allowing you to enable, disable, and remove passes dynamically.
|
|
392
|
+
|
|
393
|
+
### renderer.enablePass(passName)
|
|
394
|
+
|
|
395
|
+
Enable a render pass for rendering.
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
renderer.enablePass('background-effect');
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### renderer.disablePass(passName)
|
|
402
|
+
|
|
403
|
+
Disable a render pass (it will be skipped during rendering).
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
renderer.disablePass('post-process');
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### renderer.isPassEnabled(passName)
|
|
410
|
+
|
|
411
|
+
Check if a pass is currently enabled.
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
if (renderer.isPassEnabled('main-effect')) {
|
|
415
|
+
console.log('Main effect is active');
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### renderer.removePass(passName)
|
|
420
|
+
|
|
421
|
+
Permanently remove a render pass from the pipeline.
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
const removed = renderer.removePass('debug-pass');
|
|
425
|
+
if (removed) {
|
|
426
|
+
console.log('Pass successfully removed');
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### renderer.getAllPasses()
|
|
431
|
+
|
|
432
|
+
Get all passes (both enabled and disabled).
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
const allPasses = renderer.getAllPasses();
|
|
436
|
+
allPasses.forEach(pass => {
|
|
437
|
+
console.log(`Pass: ${pass.name}, Enabled: ${pass.enabled}`);
|
|
438
|
+
});
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### renderer.getEnabledPasses()
|
|
442
|
+
|
|
443
|
+
Get only the enabled passes.
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
const activePasses = renderer.getEnabledPasses();
|
|
447
|
+
console.log(`Active passes: ${activePasses.length}`);
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Pass Management Use Cases
|
|
451
|
+
|
|
452
|
+
**Debugging and Development**
|
|
453
|
+
```typescript
|
|
454
|
+
// Isolate a specific pass for debugging
|
|
455
|
+
renderer.disablePass('post-process');
|
|
456
|
+
renderer.disablePass('effects');
|
|
457
|
+
// Only background will render
|
|
458
|
+
|
|
459
|
+
// Re-enable all passes
|
|
460
|
+
const allPasses = renderer.getAllPasses();
|
|
461
|
+
allPasses.forEach(pass => renderer.enablePass(pass.name));
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Performance Optimization**
|
|
465
|
+
```typescript
|
|
466
|
+
// Disable expensive effects on low-end devices
|
|
467
|
+
if (isLowEndDevice) {
|
|
468
|
+
renderer.disablePass('bloom');
|
|
469
|
+
renderer.disablePass('ssao');
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Dynamic Feature Toggling**
|
|
474
|
+
```typescript
|
|
475
|
+
// UI controls for enabling/disabling effects
|
|
476
|
+
document.getElementById('toggle-bloom').onclick = () => {
|
|
477
|
+
if (renderer.isPassEnabled('bloom')) {
|
|
478
|
+
renderer.disablePass('bloom');
|
|
479
|
+
} else {
|
|
480
|
+
renderer.enablePass('bloom');
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
```
|
|
297
484
|
|
|
298
485
|
### renderer.createSampler(options?)
|
|
299
486
|
|
package/README.zh-CN.md
CHANGED
|
@@ -201,7 +201,7 @@ const renderer = await createWGSLRenderer(canvas)
|
|
|
201
201
|
|
|
202
202
|
options:
|
|
203
203
|
|
|
204
|
-
```
|
|
204
|
+
```typescript
|
|
205
205
|
interface WGSLRendererOptions {
|
|
206
206
|
config?: GPUCanvasConfiguration;
|
|
207
207
|
}
|
|
@@ -211,7 +211,7 @@ interface WGSLRendererOptions {
|
|
|
211
211
|
|
|
212
212
|
添加渲染通道。
|
|
213
213
|
|
|
214
|
-
```
|
|
214
|
+
```typescript
|
|
215
215
|
interface RenderPassOptions {
|
|
216
216
|
name: string;
|
|
217
217
|
shaderCode: string;
|
|
@@ -221,7 +221,8 @@ interface RenderPassOptions {
|
|
|
221
221
|
};
|
|
222
222
|
clearColor?: { r: number; g: number; b: number; a: number };
|
|
223
223
|
blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
|
|
224
|
-
resources
|
|
224
|
+
resources?: GPUBindingResource[];
|
|
225
|
+
bindGroupSets?: { [setName: string]: GPUBindingResource[] }; // 可选的设置多个绑定组,用于动态切换
|
|
225
226
|
view?: GPUTextureView; // 可选的自定义View
|
|
226
227
|
format?: GPUTextureFormat; // 可选的自定义格式(使用自定义View时需要指定格式一致)
|
|
227
228
|
}
|
|
@@ -312,11 +313,214 @@ renderer.loopRender((time) => {
|
|
|
312
313
|
#### renderer.stopLoop()
|
|
313
314
|
停止循环渲染。
|
|
314
315
|
|
|
316
|
+
### 切换绑定组
|
|
317
|
+
|
|
318
|
+
渲染器支持在运行时在不同的绑定组之间切换,用于:
|
|
319
|
+
|
|
320
|
+
- 切换不同的纹理
|
|
321
|
+
- 动态修改着色器参数
|
|
322
|
+
- 实现多材质渲染
|
|
323
|
+
|
|
324
|
+
#### renderer.switchBindGroupSet(passName, setName)
|
|
325
|
+
|
|
326
|
+
给指定的通道切换绑定组
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
// 给渲染通道添加多个绑定组
|
|
330
|
+
renderer.addPass({
|
|
331
|
+
name: 'main',
|
|
332
|
+
shaderCode: myShader,
|
|
333
|
+
resources: [uniforms, sampler, texture1], // Default resources
|
|
334
|
+
bindGroupSets: {
|
|
335
|
+
'material1': [uniforms, sampler, texture1],
|
|
336
|
+
'material2': [uniforms, sampler, texture2],
|
|
337
|
+
'material3': [uniforms, sampler, texture3],
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Switch between materials
|
|
342
|
+
renderer.switchBindGroupSet('main', 'material1');
|
|
343
|
+
renderer.switchBindGroupSet('main', 'material2');
|
|
344
|
+
renderer.switchBindGroupSet('main', 'material3');
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**示例:动态切换纹理**
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
// 创建多个纹理
|
|
355
|
+
const textures = [
|
|
356
|
+
await renderer.loadImageTexture('texture1.png'),
|
|
357
|
+
await renderer.loadImageTexture('texture2.png'),
|
|
358
|
+
await renderer.loadImageTexture('texture3.png'),
|
|
359
|
+
];
|
|
360
|
+
|
|
361
|
+
// 给渲染通道设置多个绑定
|
|
362
|
+
renderer.addPass({
|
|
363
|
+
name: 'renderer',
|
|
364
|
+
shaderCode: textureShader,
|
|
365
|
+
resources: [uniforms, sampler, textures[0]], // Default
|
|
366
|
+
bindGroupSets: {
|
|
367
|
+
'texture0': [uniforms, sampler, textures[0]],
|
|
368
|
+
'texture1': [uniforms, sampler, textures[1]],
|
|
369
|
+
'texture2': [uniforms, sampler, textures[2]],
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// 用户控制
|
|
374
|
+
document.getElementById('btn1').onclick = () => {
|
|
375
|
+
renderer.switchBindGroupSet('renderer', 'texture0');
|
|
376
|
+
};
|
|
377
|
+
document.getElementById('btn2').onclick = () => {
|
|
378
|
+
renderer.switchBindGroupSet('renderer', 'texture1');
|
|
379
|
+
};
|
|
380
|
+
document.getElementById('btn3').onclick = () => {
|
|
381
|
+
renderer.switchBindGroupSet('renderer', 'texture2');
|
|
382
|
+
};
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
#### renderer.updateBindGroupSetResources(passName, setName, resources)
|
|
386
|
+
|
|
387
|
+
动态增、改设置的绑定组。这可以让你在运行时修改绑定组。
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
// 添加新纹理到已有绑定组(假设textureSet绑定组已经添加到了main通道)
|
|
391
|
+
const newTexture = renderer.createTexture({ /* options */ });
|
|
392
|
+
renderer.updateBindGroupSetResources('main', 'textureSet', [
|
|
393
|
+
uniforms,
|
|
394
|
+
sampler,
|
|
395
|
+
newTexture,
|
|
396
|
+
]);
|
|
397
|
+
|
|
398
|
+
// 即时创建一个新的绑定组
|
|
399
|
+
renderer.updateBindGroupSetResources('main', 'newSet', [
|
|
400
|
+
newUniforms,
|
|
401
|
+
newSampler,
|
|
402
|
+
anotherTexture,
|
|
403
|
+
]);
|
|
404
|
+
renderer.switchBindGroupSet('main', 'newSet');
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
可用于:
|
|
408
|
+
|
|
409
|
+
- 实时流式传输纹理
|
|
410
|
+
- 动态更新着色器参数
|
|
411
|
+
- 运行时创建编程式内容
|
|
412
|
+
- 高效内存资源管理
|
|
413
|
+
|
|
414
|
+
### 渲染通道管理
|
|
415
|
+
|
|
416
|
+
渲染器提供了灵活的渲染通道管理功能,你可以动态的开启、关闭、移除渲染通道。
|
|
417
|
+
|
|
418
|
+
#### renderer.enablePass(passName)
|
|
419
|
+
|
|
420
|
+
开启渲染通道。
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
renderer.enablePass('background-effect');
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
#### renderer.disablePass(passName)
|
|
427
|
+
|
|
428
|
+
禁用渲染通道(在渲染过程中将跳过该通道的渲染)。
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
renderer.disablePass('post-process');
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### renderer.isPassEnabled(passName)
|
|
435
|
+
|
|
436
|
+
检查通道当前是否开启。
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
if (renderer.isPassEnabled('main-effect')) {
|
|
440
|
+
console.log('Main effect is active');
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
#### renderer.removePass(passName)
|
|
447
|
+
|
|
448
|
+
从渲染管线中永久删除渲染通道。
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
const removed = renderer.removePass('debug-pass');
|
|
452
|
+
if (removed) {
|
|
453
|
+
console.log('Pass successfully removed');
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
#### renderer.getEnabledPasses()
|
|
458
|
+
|
|
459
|
+
获取所有开启的渲染通道。
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
const activePasses = renderer.getEnabledPasses();
|
|
463
|
+
console.log(`Active passes: ${activePasses.length}`);
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
#### renderer.getAllPasses()
|
|
469
|
+
|
|
470
|
+
获取所有渲染通道(包括开启和禁用的)。
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
const allPasses = renderer.getAllPasses();
|
|
474
|
+
allPasses.forEach(pass => {
|
|
475
|
+
console.log(`Pass: ${pass.name}, Enabled: ${pass.enabled}`);
|
|
476
|
+
});
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
#### 通道管理示例
|
|
480
|
+
|
|
481
|
+
**开发调试**
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
// Isolate a specific pass for debugging
|
|
485
|
+
renderer.disablePass('post-process');
|
|
486
|
+
renderer.disablePass('effects');
|
|
487
|
+
// Only background will render
|
|
488
|
+
|
|
489
|
+
// Re-enable all passes
|
|
490
|
+
const allPasses = renderer.getAllPasses();
|
|
491
|
+
allPasses.forEach(pass => renderer.enablePass(pass.name));
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**性能优化**
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
// Disable expensive effects on low-end devices
|
|
498
|
+
if (isLowEndDevice) {
|
|
499
|
+
renderer.disablePass('bloom');
|
|
500
|
+
renderer.disablePass('ssao');
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
**动态功能切换**
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
// UI controls for enabling/disabling effects
|
|
508
|
+
document.getElementById('toggle-bloom').onclick = () => {
|
|
509
|
+
if (renderer.isPassEnabled('bloom')) {
|
|
510
|
+
renderer.disablePass('bloom');
|
|
511
|
+
} else {
|
|
512
|
+
renderer.enablePass('bloom');
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
|
|
315
519
|
### renderer.createSampler(options?)
|
|
316
520
|
|
|
317
521
|
创建采样器,默认参数:
|
|
318
522
|
|
|
319
|
-
```
|
|
523
|
+
```typescript
|
|
320
524
|
const options = {
|
|
321
525
|
magFilter: 'linear',
|
|
322
526
|
minFilter: 'linear',
|
package/dist/cjs/index.js
CHANGED
|
@@ -10,9 +10,14 @@ var RenderPass = class {
|
|
|
10
10
|
view;
|
|
11
11
|
format;
|
|
12
12
|
passResources = [];
|
|
13
|
+
bindGroups = {};
|
|
14
|
+
activeBindGroupSet = "default";
|
|
13
15
|
device;
|
|
16
|
+
descriptor;
|
|
17
|
+
enabled = true;
|
|
14
18
|
constructor(descriptor, device, format, layout) {
|
|
15
19
|
this.device = device;
|
|
20
|
+
this.descriptor = descriptor;
|
|
16
21
|
this.name = descriptor.name;
|
|
17
22
|
this.clearColor = descriptor.clearColor || {
|
|
18
23
|
r: 0,
|
|
@@ -24,7 +29,10 @@ var RenderPass = class {
|
|
|
24
29
|
this.view = descriptor.view;
|
|
25
30
|
this.format = descriptor.format;
|
|
26
31
|
const actualFormat = descriptor.format || format;
|
|
27
|
-
const module$1 = this.device.createShaderModule({
|
|
32
|
+
const module$1 = this.device.createShaderModule({
|
|
33
|
+
code: descriptor.shaderCode,
|
|
34
|
+
label: `Shader for ${descriptor.name}`
|
|
35
|
+
});
|
|
28
36
|
this.vertexBuffer = this.device.createBuffer({
|
|
29
37
|
size: 36,
|
|
30
38
|
usage: GPUBufferUsage.VERTEX,
|
|
@@ -79,6 +87,59 @@ var RenderPass = class {
|
|
|
79
87
|
layout: bindGroupLayout,
|
|
80
88
|
entries: newEntries
|
|
81
89
|
});
|
|
90
|
+
this.bindGroups.default = this.bindGroup;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Update a specific bind group set with new entries
|
|
94
|
+
*/
|
|
95
|
+
updateBindGroupSet(setName, newEntries) {
|
|
96
|
+
if (!this.bindGroups[setName]) {
|
|
97
|
+
const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
|
|
98
|
+
this.bindGroups[setName] = this.device.createBindGroup({
|
|
99
|
+
layout: bindGroupLayout,
|
|
100
|
+
entries: newEntries
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Switch to a different bind group set
|
|
106
|
+
*/
|
|
107
|
+
switchBindGroupSet(setName) {
|
|
108
|
+
if (this.bindGroups[setName]) {
|
|
109
|
+
this.activeBindGroupSet = setName;
|
|
110
|
+
this.bindGroup = this.bindGroups[setName];
|
|
111
|
+
} else throw new Error(`Bind group set '${setName}' not found. Available sets: ${Object.keys(this.bindGroups).join(", ")}`);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get the current active bind group
|
|
115
|
+
*/
|
|
116
|
+
getActiveBindGroup() {
|
|
117
|
+
return this.bindGroups[this.activeBindGroupSet] || this.bindGroup;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get all available bind group set names
|
|
121
|
+
*/
|
|
122
|
+
getBindGroupSets() {
|
|
123
|
+
return Object.keys(this.bindGroups);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Update or add a bind group set with new resources
|
|
127
|
+
* This allows dynamic modification of bind groups at runtime
|
|
128
|
+
*/
|
|
129
|
+
updateBindGroupSetResources(setName, resources) {
|
|
130
|
+
const entries = [];
|
|
131
|
+
resources.forEach((resource, index) => {
|
|
132
|
+
if (resource) entries.push({
|
|
133
|
+
binding: index,
|
|
134
|
+
resource
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
|
|
138
|
+
this.bindGroups[setName] = this.device.createBindGroup({
|
|
139
|
+
layout: bindGroupLayout,
|
|
140
|
+
entries
|
|
141
|
+
});
|
|
142
|
+
if (this.activeBindGroupSet === setName) this.bindGroup = this.bindGroups[setName];
|
|
82
143
|
}
|
|
83
144
|
getBlendState() {
|
|
84
145
|
switch (this.blendMode) {
|
|
@@ -270,6 +331,74 @@ var WGSLRenderer = class {
|
|
|
270
331
|
return this.passes.find((pass) => pass.name === passName);
|
|
271
332
|
}
|
|
272
333
|
/**
|
|
334
|
+
* Disable a render pass (it will be skipped during rendering)
|
|
335
|
+
*/
|
|
336
|
+
disablePass(passName) {
|
|
337
|
+
const pass = this.getPassByName(passName);
|
|
338
|
+
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
339
|
+
pass.enabled = false;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Enable a render pass
|
|
343
|
+
*/
|
|
344
|
+
enablePass(passName) {
|
|
345
|
+
const pass = this.getPassByName(passName);
|
|
346
|
+
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
347
|
+
pass.enabled = true;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Check if a pass is enabled
|
|
351
|
+
*/
|
|
352
|
+
isPassEnabled(passName) {
|
|
353
|
+
const pass = this.getPassByName(passName);
|
|
354
|
+
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
355
|
+
return pass.enabled;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Remove a render pass permanently
|
|
359
|
+
*/
|
|
360
|
+
removePass(passName) {
|
|
361
|
+
const index = this.passes.findIndex((pass) => pass.name === passName);
|
|
362
|
+
if (index !== -1) {
|
|
363
|
+
this.passes.splice(index, 1);
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Get all passes (enabled and disabled)
|
|
370
|
+
*/
|
|
371
|
+
getAllPasses() {
|
|
372
|
+
return [...this.passes];
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get only enabled passes
|
|
376
|
+
*/
|
|
377
|
+
getEnabledPasses() {
|
|
378
|
+
return this.passes.filter((pass) => pass.enabled);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Switch bind group set for a specific pass
|
|
382
|
+
*/
|
|
383
|
+
switchBindGroupSet(passName, setName) {
|
|
384
|
+
const pass = this.getPassByName(passName);
|
|
385
|
+
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
386
|
+
pass.switchBindGroupSet(setName);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Update bind group set resources for a specific pass
|
|
390
|
+
* This allows dynamic modification of bind groups at runtime
|
|
391
|
+
*/
|
|
392
|
+
updateBindGroupSetResources(passName, setName, resources) {
|
|
393
|
+
const pass = this.getPassByName(passName);
|
|
394
|
+
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
395
|
+
const resolvedResources = resources.map((resource) => {
|
|
396
|
+
if (resource && isPassTextureRef(resource)) return this.resolveResource(resource);
|
|
397
|
+
return resource;
|
|
398
|
+
});
|
|
399
|
+
pass.updateBindGroupSetResources(setName, resolvedResources);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
273
402
|
* Add a render pass to the multi-pass pipeline
|
|
274
403
|
*/
|
|
275
404
|
addPass(descriptor) {
|
|
@@ -280,6 +409,11 @@ var WGSLRenderer = class {
|
|
|
280
409
|
resource
|
|
281
410
|
});
|
|
282
411
|
});
|
|
412
|
+
let bindGroupSetsCopy = void 0;
|
|
413
|
+
if (descriptor.bindGroupSets) {
|
|
414
|
+
bindGroupSetsCopy = {};
|
|
415
|
+
for (const [setName, resources] of Object.entries(descriptor.bindGroupSets)) bindGroupSetsCopy[setName] = [...resources];
|
|
416
|
+
}
|
|
283
417
|
const internalDescriptor = {
|
|
284
418
|
name: descriptor.name,
|
|
285
419
|
shaderCode: descriptor.shaderCode,
|
|
@@ -287,6 +421,7 @@ var WGSLRenderer = class {
|
|
|
287
421
|
clearColor: descriptor.clearColor,
|
|
288
422
|
blendMode: descriptor.blendMode,
|
|
289
423
|
bindGroupEntries: finalBindGroupEntries,
|
|
424
|
+
bindGroupSets: bindGroupSetsCopy,
|
|
290
425
|
view: descriptor.view,
|
|
291
426
|
format: descriptor.format
|
|
292
427
|
};
|
|
@@ -317,6 +452,19 @@ var WGSLRenderer = class {
|
|
|
317
452
|
});
|
|
318
453
|
});
|
|
319
454
|
pass.updateBindGroup(finalBindGroupEntries);
|
|
455
|
+
if (pass.descriptor && pass.descriptor.bindGroupSets) for (const [setName, resources] of Object.entries(pass.descriptor.bindGroupSets)) {
|
|
456
|
+
const entries = [];
|
|
457
|
+
resources.forEach((resource, index) => {
|
|
458
|
+
if (resource) {
|
|
459
|
+
const resolved = this.resolveResource(resource);
|
|
460
|
+
entries.push({
|
|
461
|
+
binding: index,
|
|
462
|
+
resource: resolved
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
pass.updateBindGroupSet(setName, entries);
|
|
467
|
+
}
|
|
320
468
|
});
|
|
321
469
|
}
|
|
322
470
|
/**
|
|
@@ -349,13 +497,22 @@ var WGSLRenderer = class {
|
|
|
349
497
|
addressModeV: "clamp-to-edge"
|
|
350
498
|
}, options));
|
|
351
499
|
}
|
|
352
|
-
async loadImageTexture(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
500
|
+
async loadImageTexture(image, format) {
|
|
501
|
+
if (typeof image === "string") if (image.startsWith("data:")) {
|
|
502
|
+
const base64Data = image.split(",")[1];
|
|
503
|
+
const binaryString = atob(base64Data);
|
|
504
|
+
const len = binaryString.length;
|
|
505
|
+
const bytes = new Uint8Array(len);
|
|
506
|
+
for (let i = 0; i < len; i++) bytes[i] = binaryString.charCodeAt(i);
|
|
507
|
+
image = new Blob([bytes], { type: "application/octet-stream" });
|
|
508
|
+
} else {
|
|
509
|
+
const resp = fetch(image);
|
|
510
|
+
resp.catch((err) => {
|
|
511
|
+
console.error("Failed to load texture:", err);
|
|
512
|
+
});
|
|
513
|
+
image = await (await resp).blob();
|
|
514
|
+
}
|
|
515
|
+
const future = createImageBitmap(image);
|
|
359
516
|
future.catch((err) => {
|
|
360
517
|
console.error("Failed to load texture:", err);
|
|
361
518
|
});
|
|
@@ -366,24 +523,27 @@ var WGSLRenderer = class {
|
|
|
366
523
|
imgBitmap.height,
|
|
367
524
|
1
|
|
368
525
|
],
|
|
369
|
-
format: "rgba8unorm",
|
|
526
|
+
format: format || "rgba8unorm",
|
|
370
527
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
371
528
|
});
|
|
372
529
|
this.device.queue.copyExternalImageToTexture({ source: imgBitmap }, { texture }, [imgBitmap.width, imgBitmap.height]);
|
|
373
530
|
return {
|
|
374
531
|
texture,
|
|
532
|
+
bitMap: imgBitmap,
|
|
375
533
|
width: imgBitmap.width,
|
|
376
534
|
height: imgBitmap.height
|
|
377
535
|
};
|
|
378
536
|
}
|
|
379
537
|
renderFrame() {
|
|
380
538
|
if (this.passes.length === 0) return;
|
|
539
|
+
const enabledPasses = this.getEnabledPasses();
|
|
540
|
+
if (enabledPasses.length === 0) return;
|
|
381
541
|
this.updateBindGroups();
|
|
382
542
|
const commandEncoder = this.device.createCommandEncoder();
|
|
383
|
-
for (let i = 0; i <
|
|
384
|
-
const pass =
|
|
543
|
+
for (let i = 0; i < enabledPasses.length; i++) {
|
|
544
|
+
const pass = enabledPasses[i];
|
|
385
545
|
let loadOp = "load";
|
|
386
|
-
const isLast = i ===
|
|
546
|
+
const isLast = i === enabledPasses.length - 1;
|
|
387
547
|
if (isLast) loadOp = "clear";
|
|
388
548
|
let renderTarget;
|
|
389
549
|
if (pass.view) renderTarget = pass.view;
|
|
@@ -391,7 +551,7 @@ var WGSLRenderer = class {
|
|
|
391
551
|
else {
|
|
392
552
|
const textureName = `pass_${i}_output`;
|
|
393
553
|
let texture = this.textureManager.getTexture(textureName);
|
|
394
|
-
if (!texture) texture = this.textureManager.createTexture(textureName,
|
|
554
|
+
if (!texture) texture = this.textureManager.createTexture(textureName, pass.format || this.format);
|
|
395
555
|
renderTarget = texture.createView();
|
|
396
556
|
}
|
|
397
557
|
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
@@ -401,7 +561,8 @@ var WGSLRenderer = class {
|
|
|
401
561
|
clearValue: pass.clearColor
|
|
402
562
|
}] });
|
|
403
563
|
renderPass.setPipeline(pass.pipeline);
|
|
404
|
-
|
|
564
|
+
const activeBindGroup = pass.getActiveBindGroup();
|
|
565
|
+
if (activeBindGroup) renderPass.setBindGroup(0, activeBindGroup);
|
|
405
566
|
renderPass.setVertexBuffer(0, pass.vertexBuffer);
|
|
406
567
|
renderPass.draw(3, 1, 0, 0);
|
|
407
568
|
renderPass.end();
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -30,6 +30,9 @@ interface RenderPassOptions {
|
|
|
30
30
|
};
|
|
31
31
|
blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
|
|
32
32
|
resources?: BandingResource[];
|
|
33
|
+
bindGroupSets?: {
|
|
34
|
+
[setName: string]: BandingResource[];
|
|
35
|
+
};
|
|
33
36
|
view?: GPUTextureView;
|
|
34
37
|
format?: GPUTextureFormat;
|
|
35
38
|
}
|
|
@@ -48,6 +51,9 @@ interface InternalRenderPassDescriptor {
|
|
|
48
51
|
};
|
|
49
52
|
blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
|
|
50
53
|
bindGroupEntries: BindingEntry[];
|
|
54
|
+
bindGroupSets?: {
|
|
55
|
+
[setName: string]: BandingResource[];
|
|
56
|
+
};
|
|
51
57
|
view?: GPUTextureView;
|
|
52
58
|
format?: GPUTextureFormat;
|
|
53
59
|
}
|
|
@@ -66,12 +72,45 @@ declare class RenderPass {
|
|
|
66
72
|
view?: GPUTextureView;
|
|
67
73
|
format?: GPUTextureFormat;
|
|
68
74
|
passResources: BandingResource[];
|
|
75
|
+
bindGroups: {
|
|
76
|
+
[setName: string]: GPUBindGroup;
|
|
77
|
+
};
|
|
78
|
+
activeBindGroupSet: string;
|
|
69
79
|
private device;
|
|
80
|
+
descriptor: InternalRenderPassDescriptor;
|
|
81
|
+
enabled: boolean;
|
|
70
82
|
constructor(descriptor: InternalRenderPassDescriptor, device: GPUDevice, format: GPUTextureFormat, layout: GPUPipelineLayout | 'auto');
|
|
71
83
|
/**
|
|
72
84
|
* Update bind group with new entries (e.g., after texture resize)
|
|
73
85
|
*/
|
|
74
|
-
updateBindGroup(newEntries:
|
|
86
|
+
updateBindGroup(newEntries: {
|
|
87
|
+
binding: number;
|
|
88
|
+
resource: GPUBindingResource;
|
|
89
|
+
}[]): void;
|
|
90
|
+
/**
|
|
91
|
+
* Update a specific bind group set with new entries
|
|
92
|
+
*/
|
|
93
|
+
updateBindGroupSet(setName: string, newEntries: {
|
|
94
|
+
binding: number;
|
|
95
|
+
resource: GPUBindingResource;
|
|
96
|
+
}[]): void;
|
|
97
|
+
/**
|
|
98
|
+
* Switch to a different bind group set
|
|
99
|
+
*/
|
|
100
|
+
switchBindGroupSet(setName: string): void;
|
|
101
|
+
/**
|
|
102
|
+
* Get the current active bind group
|
|
103
|
+
*/
|
|
104
|
+
getActiveBindGroup(): GPUBindGroup | null;
|
|
105
|
+
/**
|
|
106
|
+
* Get all available bind group set names
|
|
107
|
+
*/
|
|
108
|
+
getBindGroupSets(): string[];
|
|
109
|
+
/**
|
|
110
|
+
* Update or add a bind group set with new resources
|
|
111
|
+
* This allows dynamic modification of bind groups at runtime
|
|
112
|
+
*/
|
|
113
|
+
updateBindGroupSetResources(setName: string, resources: BandingResource[]): void;
|
|
75
114
|
private getBlendState;
|
|
76
115
|
}
|
|
77
116
|
//#endregion
|
|
@@ -110,6 +149,39 @@ declare class WGSLRenderer {
|
|
|
110
149
|
* Get pass by name
|
|
111
150
|
*/
|
|
112
151
|
getPassByName(passName: string): RenderPass | undefined;
|
|
152
|
+
/**
|
|
153
|
+
* Disable a render pass (it will be skipped during rendering)
|
|
154
|
+
*/
|
|
155
|
+
disablePass(passName: string): void;
|
|
156
|
+
/**
|
|
157
|
+
* Enable a render pass
|
|
158
|
+
*/
|
|
159
|
+
enablePass(passName: string): void;
|
|
160
|
+
/**
|
|
161
|
+
* Check if a pass is enabled
|
|
162
|
+
*/
|
|
163
|
+
isPassEnabled(passName: string): boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Remove a render pass permanently
|
|
166
|
+
*/
|
|
167
|
+
removePass(passName: string): boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Get all passes (enabled and disabled)
|
|
170
|
+
*/
|
|
171
|
+
getAllPasses(): RenderPass[];
|
|
172
|
+
/**
|
|
173
|
+
* Get only enabled passes
|
|
174
|
+
*/
|
|
175
|
+
getEnabledPasses(): RenderPass[];
|
|
176
|
+
/**
|
|
177
|
+
* Switch bind group set for a specific pass
|
|
178
|
+
*/
|
|
179
|
+
switchBindGroupSet(passName: string, setName: string): void;
|
|
180
|
+
/**
|
|
181
|
+
* Update bind group set resources for a specific pass
|
|
182
|
+
* This allows dynamic modification of bind groups at runtime
|
|
183
|
+
*/
|
|
184
|
+
updateBindGroupSetResources(passName: string, setName: string, resources: BandingResource[]): void;
|
|
113
185
|
/**
|
|
114
186
|
* Add a render pass to the multi-pass pipeline
|
|
115
187
|
*/
|
|
@@ -138,8 +210,9 @@ declare class WGSLRenderer {
|
|
|
138
210
|
* Create a sampler
|
|
139
211
|
*/
|
|
140
212
|
createSampler(options?: GPUSamplerDescriptor): GPUSampler;
|
|
141
|
-
loadImageTexture(
|
|
213
|
+
loadImageTexture(image: Blob | string, format?: GPUTextureFormat): Promise<{
|
|
142
214
|
texture: GPUTexture;
|
|
215
|
+
bitMap: ImageBitmap;
|
|
143
216
|
width: number;
|
|
144
217
|
height: number;
|
|
145
218
|
}>;
|
|
@@ -151,4 +224,4 @@ declare class WGSLRenderer {
|
|
|
151
224
|
}
|
|
152
225
|
declare function createWGSLRenderer(cvs: HTMLCanvasElement, options?: WGSLRendererOptions): Promise<WGSLRenderer>;
|
|
153
226
|
//#endregion
|
|
154
|
-
export { MultiPassDescriptor, createWGSLRenderer };
|
|
227
|
+
export { MultiPassDescriptor, type WGSLRenderer, createWGSLRenderer };
|
package/dist/esm/index.js
CHANGED
|
@@ -9,9 +9,14 @@ var RenderPass = class {
|
|
|
9
9
|
view;
|
|
10
10
|
format;
|
|
11
11
|
passResources = [];
|
|
12
|
+
bindGroups = {};
|
|
13
|
+
activeBindGroupSet = "default";
|
|
12
14
|
device;
|
|
15
|
+
descriptor;
|
|
16
|
+
enabled = true;
|
|
13
17
|
constructor(descriptor, device, format, layout) {
|
|
14
18
|
this.device = device;
|
|
19
|
+
this.descriptor = descriptor;
|
|
15
20
|
this.name = descriptor.name;
|
|
16
21
|
this.clearColor = descriptor.clearColor || {
|
|
17
22
|
r: 0,
|
|
@@ -23,7 +28,10 @@ var RenderPass = class {
|
|
|
23
28
|
this.view = descriptor.view;
|
|
24
29
|
this.format = descriptor.format;
|
|
25
30
|
const actualFormat = descriptor.format || format;
|
|
26
|
-
const module = this.device.createShaderModule({
|
|
31
|
+
const module = this.device.createShaderModule({
|
|
32
|
+
code: descriptor.shaderCode,
|
|
33
|
+
label: `Shader for ${descriptor.name}`
|
|
34
|
+
});
|
|
27
35
|
this.vertexBuffer = this.device.createBuffer({
|
|
28
36
|
size: 36,
|
|
29
37
|
usage: GPUBufferUsage.VERTEX,
|
|
@@ -78,6 +86,59 @@ var RenderPass = class {
|
|
|
78
86
|
layout: bindGroupLayout,
|
|
79
87
|
entries: newEntries
|
|
80
88
|
});
|
|
89
|
+
this.bindGroups.default = this.bindGroup;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Update a specific bind group set with new entries
|
|
93
|
+
*/
|
|
94
|
+
updateBindGroupSet(setName, newEntries) {
|
|
95
|
+
if (!this.bindGroups[setName]) {
|
|
96
|
+
const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
|
|
97
|
+
this.bindGroups[setName] = this.device.createBindGroup({
|
|
98
|
+
layout: bindGroupLayout,
|
|
99
|
+
entries: newEntries
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Switch to a different bind group set
|
|
105
|
+
*/
|
|
106
|
+
switchBindGroupSet(setName) {
|
|
107
|
+
if (this.bindGroups[setName]) {
|
|
108
|
+
this.activeBindGroupSet = setName;
|
|
109
|
+
this.bindGroup = this.bindGroups[setName];
|
|
110
|
+
} else throw new Error(`Bind group set '${setName}' not found. Available sets: ${Object.keys(this.bindGroups).join(", ")}`);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get the current active bind group
|
|
114
|
+
*/
|
|
115
|
+
getActiveBindGroup() {
|
|
116
|
+
return this.bindGroups[this.activeBindGroupSet] || this.bindGroup;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get all available bind group set names
|
|
120
|
+
*/
|
|
121
|
+
getBindGroupSets() {
|
|
122
|
+
return Object.keys(this.bindGroups);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Update or add a bind group set with new resources
|
|
126
|
+
* This allows dynamic modification of bind groups at runtime
|
|
127
|
+
*/
|
|
128
|
+
updateBindGroupSetResources(setName, resources) {
|
|
129
|
+
const entries = [];
|
|
130
|
+
resources.forEach((resource, index) => {
|
|
131
|
+
if (resource) entries.push({
|
|
132
|
+
binding: index,
|
|
133
|
+
resource
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
|
|
137
|
+
this.bindGroups[setName] = this.device.createBindGroup({
|
|
138
|
+
layout: bindGroupLayout,
|
|
139
|
+
entries
|
|
140
|
+
});
|
|
141
|
+
if (this.activeBindGroupSet === setName) this.bindGroup = this.bindGroups[setName];
|
|
81
142
|
}
|
|
82
143
|
getBlendState() {
|
|
83
144
|
switch (this.blendMode) {
|
|
@@ -269,6 +330,74 @@ var WGSLRenderer = class {
|
|
|
269
330
|
return this.passes.find((pass) => pass.name === passName);
|
|
270
331
|
}
|
|
271
332
|
/**
|
|
333
|
+
* Disable a render pass (it will be skipped during rendering)
|
|
334
|
+
*/
|
|
335
|
+
disablePass(passName) {
|
|
336
|
+
const pass = this.getPassByName(passName);
|
|
337
|
+
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
338
|
+
pass.enabled = false;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Enable a render pass
|
|
342
|
+
*/
|
|
343
|
+
enablePass(passName) {
|
|
344
|
+
const pass = this.getPassByName(passName);
|
|
345
|
+
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
346
|
+
pass.enabled = true;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Check if a pass is enabled
|
|
350
|
+
*/
|
|
351
|
+
isPassEnabled(passName) {
|
|
352
|
+
const pass = this.getPassByName(passName);
|
|
353
|
+
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
354
|
+
return pass.enabled;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Remove a render pass permanently
|
|
358
|
+
*/
|
|
359
|
+
removePass(passName) {
|
|
360
|
+
const index = this.passes.findIndex((pass) => pass.name === passName);
|
|
361
|
+
if (index !== -1) {
|
|
362
|
+
this.passes.splice(index, 1);
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Get all passes (enabled and disabled)
|
|
369
|
+
*/
|
|
370
|
+
getAllPasses() {
|
|
371
|
+
return [...this.passes];
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get only enabled passes
|
|
375
|
+
*/
|
|
376
|
+
getEnabledPasses() {
|
|
377
|
+
return this.passes.filter((pass) => pass.enabled);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Switch bind group set for a specific pass
|
|
381
|
+
*/
|
|
382
|
+
switchBindGroupSet(passName, setName) {
|
|
383
|
+
const pass = this.getPassByName(passName);
|
|
384
|
+
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
385
|
+
pass.switchBindGroupSet(setName);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Update bind group set resources for a specific pass
|
|
389
|
+
* This allows dynamic modification of bind groups at runtime
|
|
390
|
+
*/
|
|
391
|
+
updateBindGroupSetResources(passName, setName, resources) {
|
|
392
|
+
const pass = this.getPassByName(passName);
|
|
393
|
+
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
394
|
+
const resolvedResources = resources.map((resource) => {
|
|
395
|
+
if (resource && isPassTextureRef(resource)) return this.resolveResource(resource);
|
|
396
|
+
return resource;
|
|
397
|
+
});
|
|
398
|
+
pass.updateBindGroupSetResources(setName, resolvedResources);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
272
401
|
* Add a render pass to the multi-pass pipeline
|
|
273
402
|
*/
|
|
274
403
|
addPass(descriptor) {
|
|
@@ -279,6 +408,11 @@ var WGSLRenderer = class {
|
|
|
279
408
|
resource
|
|
280
409
|
});
|
|
281
410
|
});
|
|
411
|
+
let bindGroupSetsCopy = void 0;
|
|
412
|
+
if (descriptor.bindGroupSets) {
|
|
413
|
+
bindGroupSetsCopy = {};
|
|
414
|
+
for (const [setName, resources] of Object.entries(descriptor.bindGroupSets)) bindGroupSetsCopy[setName] = [...resources];
|
|
415
|
+
}
|
|
282
416
|
const internalDescriptor = {
|
|
283
417
|
name: descriptor.name,
|
|
284
418
|
shaderCode: descriptor.shaderCode,
|
|
@@ -286,6 +420,7 @@ var WGSLRenderer = class {
|
|
|
286
420
|
clearColor: descriptor.clearColor,
|
|
287
421
|
blendMode: descriptor.blendMode,
|
|
288
422
|
bindGroupEntries: finalBindGroupEntries,
|
|
423
|
+
bindGroupSets: bindGroupSetsCopy,
|
|
289
424
|
view: descriptor.view,
|
|
290
425
|
format: descriptor.format
|
|
291
426
|
};
|
|
@@ -316,6 +451,19 @@ var WGSLRenderer = class {
|
|
|
316
451
|
});
|
|
317
452
|
});
|
|
318
453
|
pass.updateBindGroup(finalBindGroupEntries);
|
|
454
|
+
if (pass.descriptor && pass.descriptor.bindGroupSets) for (const [setName, resources] of Object.entries(pass.descriptor.bindGroupSets)) {
|
|
455
|
+
const entries = [];
|
|
456
|
+
resources.forEach((resource, index) => {
|
|
457
|
+
if (resource) {
|
|
458
|
+
const resolved = this.resolveResource(resource);
|
|
459
|
+
entries.push({
|
|
460
|
+
binding: index,
|
|
461
|
+
resource: resolved
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
pass.updateBindGroupSet(setName, entries);
|
|
466
|
+
}
|
|
319
467
|
});
|
|
320
468
|
}
|
|
321
469
|
/**
|
|
@@ -348,13 +496,22 @@ var WGSLRenderer = class {
|
|
|
348
496
|
addressModeV: "clamp-to-edge"
|
|
349
497
|
}, options));
|
|
350
498
|
}
|
|
351
|
-
async loadImageTexture(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
499
|
+
async loadImageTexture(image, format) {
|
|
500
|
+
if (typeof image === "string") if (image.startsWith("data:")) {
|
|
501
|
+
const base64Data = image.split(",")[1];
|
|
502
|
+
const binaryString = atob(base64Data);
|
|
503
|
+
const len = binaryString.length;
|
|
504
|
+
const bytes = new Uint8Array(len);
|
|
505
|
+
for (let i = 0; i < len; i++) bytes[i] = binaryString.charCodeAt(i);
|
|
506
|
+
image = new Blob([bytes], { type: "application/octet-stream" });
|
|
507
|
+
} else {
|
|
508
|
+
const resp = fetch(image);
|
|
509
|
+
resp.catch((err) => {
|
|
510
|
+
console.error("Failed to load texture:", err);
|
|
511
|
+
});
|
|
512
|
+
image = await (await resp).blob();
|
|
513
|
+
}
|
|
514
|
+
const future = createImageBitmap(image);
|
|
358
515
|
future.catch((err) => {
|
|
359
516
|
console.error("Failed to load texture:", err);
|
|
360
517
|
});
|
|
@@ -365,24 +522,27 @@ var WGSLRenderer = class {
|
|
|
365
522
|
imgBitmap.height,
|
|
366
523
|
1
|
|
367
524
|
],
|
|
368
|
-
format: "rgba8unorm",
|
|
525
|
+
format: format || "rgba8unorm",
|
|
369
526
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
370
527
|
});
|
|
371
528
|
this.device.queue.copyExternalImageToTexture({ source: imgBitmap }, { texture }, [imgBitmap.width, imgBitmap.height]);
|
|
372
529
|
return {
|
|
373
530
|
texture,
|
|
531
|
+
bitMap: imgBitmap,
|
|
374
532
|
width: imgBitmap.width,
|
|
375
533
|
height: imgBitmap.height
|
|
376
534
|
};
|
|
377
535
|
}
|
|
378
536
|
renderFrame() {
|
|
379
537
|
if (this.passes.length === 0) return;
|
|
538
|
+
const enabledPasses = this.getEnabledPasses();
|
|
539
|
+
if (enabledPasses.length === 0) return;
|
|
380
540
|
this.updateBindGroups();
|
|
381
541
|
const commandEncoder = this.device.createCommandEncoder();
|
|
382
|
-
for (let i = 0; i <
|
|
383
|
-
const pass =
|
|
542
|
+
for (let i = 0; i < enabledPasses.length; i++) {
|
|
543
|
+
const pass = enabledPasses[i];
|
|
384
544
|
let loadOp = "load";
|
|
385
|
-
const isLast = i ===
|
|
545
|
+
const isLast = i === enabledPasses.length - 1;
|
|
386
546
|
if (isLast) loadOp = "clear";
|
|
387
547
|
let renderTarget;
|
|
388
548
|
if (pass.view) renderTarget = pass.view;
|
|
@@ -390,7 +550,7 @@ var WGSLRenderer = class {
|
|
|
390
550
|
else {
|
|
391
551
|
const textureName = `pass_${i}_output`;
|
|
392
552
|
let texture = this.textureManager.getTexture(textureName);
|
|
393
|
-
if (!texture) texture = this.textureManager.createTexture(textureName,
|
|
553
|
+
if (!texture) texture = this.textureManager.createTexture(textureName, pass.format || this.format);
|
|
394
554
|
renderTarget = texture.createView();
|
|
395
555
|
}
|
|
396
556
|
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
@@ -400,7 +560,8 @@ var WGSLRenderer = class {
|
|
|
400
560
|
clearValue: pass.clearColor
|
|
401
561
|
}] });
|
|
402
562
|
renderPass.setPipeline(pass.pipeline);
|
|
403
|
-
|
|
563
|
+
const activeBindGroup = pass.getActiveBindGroup();
|
|
564
|
+
if (activeBindGroup) renderPass.setBindGroup(0, activeBindGroup);
|
|
404
565
|
renderPass.setVertexBuffer(0, pass.vertexBuffer);
|
|
405
566
|
renderPass.draw(3, 1, 0, 0);
|
|
406
567
|
renderPass.end();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wgsl-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "一个基于WebGPU和WGSL的多通道渲染器",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -33,12 +33,11 @@
|
|
|
33
33
|
"files": [
|
|
34
34
|
"dist"
|
|
35
35
|
],
|
|
36
|
-
"dependencies": {
|
|
37
|
-
"@webgpu/types": "^0.1.66"
|
|
38
|
-
},
|
|
36
|
+
"dependencies": {},
|
|
39
37
|
"devDependencies": {
|
|
40
38
|
"@taiyuuki/eslint-config": "^1.4.14",
|
|
41
39
|
"@types/node": "^20.14.2",
|
|
40
|
+
"@webgpu/types": "^0.1.66",
|
|
42
41
|
"eslint": "^9.5.0",
|
|
43
42
|
"rimraf": "^6.1.2",
|
|
44
43
|
"rolldown": "1.0.0-beta.52",
|