wgsl-renderer 0.0.2 → 0.0.3
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 +236 -228
- package/README.zh-CN.md +417 -0
- package/dist/cjs/index.js +106 -113
- package/dist/esm/index.js +106 -113
- package/dist/types/index.d.ts +79 -15
- package/dist/types/index.js +106 -113
- package/package.json +4 -2
package/dist/types/index.js
CHANGED
|
@@ -6,13 +6,12 @@ var RenderPass = class {
|
|
|
6
6
|
vertexBuffer;
|
|
7
7
|
clearColor;
|
|
8
8
|
blendMode;
|
|
9
|
-
|
|
9
|
+
view;
|
|
10
|
+
format;
|
|
10
11
|
passResources = [];
|
|
11
12
|
device;
|
|
12
|
-
originalBindGroupEntries;
|
|
13
13
|
constructor(descriptor, device, format, layout) {
|
|
14
14
|
this.device = device;
|
|
15
|
-
this.originalBindGroupEntries = [...descriptor.bindGroupEntries || []];
|
|
16
15
|
this.name = descriptor.name;
|
|
17
16
|
this.clearColor = descriptor.clearColor || {
|
|
18
17
|
r: 0,
|
|
@@ -20,7 +19,10 @@ var RenderPass = class {
|
|
|
20
19
|
b: 0,
|
|
21
20
|
a: 1
|
|
22
21
|
};
|
|
23
|
-
this.blendMode = descriptor.blendMode || "
|
|
22
|
+
this.blendMode = descriptor.blendMode || "none";
|
|
23
|
+
this.view = descriptor.view;
|
|
24
|
+
this.format = descriptor.format;
|
|
25
|
+
const actualFormat = descriptor.format || format;
|
|
24
26
|
const module = this.device.createShaderModule({ code: descriptor.shaderCode });
|
|
25
27
|
this.vertexBuffer = this.device.createBuffer({
|
|
26
28
|
size: 36,
|
|
@@ -39,11 +41,13 @@ var RenderPass = class {
|
|
|
39
41
|
0
|
|
40
42
|
]);
|
|
41
43
|
this.vertexBuffer.unmap();
|
|
44
|
+
const vertexEntryPoint = descriptor.entryPoints?.vertex || "vs_main";
|
|
45
|
+
const fragmentEntryPoint = descriptor.entryPoints?.fragment || "fs_main";
|
|
42
46
|
this.pipeline = this.device.createRenderPipeline({
|
|
43
47
|
layout,
|
|
44
48
|
vertex: {
|
|
45
49
|
module,
|
|
46
|
-
entryPoint:
|
|
50
|
+
entryPoint: vertexEntryPoint,
|
|
47
51
|
buffers: [{
|
|
48
52
|
arrayStride: 12,
|
|
49
53
|
attributes: [{
|
|
@@ -55,26 +59,21 @@ var RenderPass = class {
|
|
|
55
59
|
},
|
|
56
60
|
fragment: {
|
|
57
61
|
module,
|
|
58
|
-
entryPoint:
|
|
62
|
+
entryPoint: fragmentEntryPoint,
|
|
59
63
|
targets: [{
|
|
60
|
-
format,
|
|
64
|
+
format: actualFormat,
|
|
61
65
|
blend: this.getBlendState()
|
|
62
66
|
}]
|
|
63
67
|
},
|
|
64
68
|
primitive: { topology: "triangle-list" }
|
|
65
69
|
});
|
|
66
|
-
|
|
67
|
-
this.bindGroup = this.device.createBindGroup({
|
|
68
|
-
layout: bindGroupLayout,
|
|
69
|
-
entries: this.originalBindGroupEntries
|
|
70
|
-
});
|
|
70
|
+
this.bindGroup = null;
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
73
|
* Update bind group with new entries (e.g., after texture resize)
|
|
74
74
|
*/
|
|
75
75
|
updateBindGroup(newEntries) {
|
|
76
76
|
const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
|
|
77
|
-
this.originalBindGroupEntries = [...newEntries];
|
|
78
77
|
this.bindGroup = this.device.createBindGroup({
|
|
79
78
|
layout: bindGroupLayout,
|
|
80
79
|
entries: newEntries
|
|
@@ -131,21 +130,17 @@ var TextureManager = class {
|
|
|
131
130
|
device;
|
|
132
131
|
width;
|
|
133
132
|
height;
|
|
134
|
-
oldTextures = [];
|
|
135
133
|
constructor(device, width, height) {
|
|
136
134
|
this.device = device;
|
|
137
135
|
this.width = width;
|
|
138
136
|
this.height = height;
|
|
139
137
|
}
|
|
140
138
|
createTexture(name, format) {
|
|
141
|
-
if (this.textures.has(name))
|
|
142
|
-
const oldTexture = this.textures.get(name);
|
|
143
|
-
this.oldTextures.push(oldTexture);
|
|
144
|
-
}
|
|
139
|
+
if (this.textures.has(name)) this.textures.get(name).destroy();
|
|
145
140
|
const texture = this.device.createTexture({
|
|
146
141
|
size: [this.width, this.height],
|
|
147
142
|
format: format || "bgra8unorm",
|
|
148
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
|
|
143
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
|
|
149
144
|
});
|
|
150
145
|
this.textures.set(name, texture);
|
|
151
146
|
return texture;
|
|
@@ -158,37 +153,13 @@ var TextureManager = class {
|
|
|
158
153
|
this.width = width;
|
|
159
154
|
this.height = height;
|
|
160
155
|
this.textures.forEach((texture) => {
|
|
161
|
-
this.oldTextures.push(texture);
|
|
162
|
-
});
|
|
163
|
-
this.textures.clear();
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Recreate a specific texture with current dimensions
|
|
167
|
-
*/
|
|
168
|
-
recreateTexture(name, format) {
|
|
169
|
-
const texture = this.device.createTexture({
|
|
170
|
-
size: [this.width, this.height],
|
|
171
|
-
format: format || "bgra8unorm",
|
|
172
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
|
|
173
|
-
});
|
|
174
|
-
this.textures.set(name, texture);
|
|
175
|
-
return texture;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Clean up old textures that are no longer needed
|
|
179
|
-
* Call this after ensuring GPU work is complete
|
|
180
|
-
*/
|
|
181
|
-
cleanupOldTextures() {
|
|
182
|
-
this.oldTextures.forEach((texture) => {
|
|
183
156
|
texture.destroy();
|
|
184
157
|
});
|
|
185
|
-
this.
|
|
158
|
+
this.textures.clear();
|
|
186
159
|
}
|
|
187
160
|
destroy() {
|
|
188
161
|
this.textures.forEach((texture) => texture.destroy());
|
|
189
162
|
this.textures.clear();
|
|
190
|
-
this.oldTextures.forEach((texture) => texture.destroy());
|
|
191
|
-
this.oldTextures.length = 0;
|
|
192
163
|
}
|
|
193
164
|
getPixelSize() {
|
|
194
165
|
return {
|
|
@@ -198,6 +169,30 @@ var TextureManager = class {
|
|
|
198
169
|
}
|
|
199
170
|
};
|
|
200
171
|
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/PassTextureRef.ts
|
|
174
|
+
const PASS_TEXTURE_REF_SYMBOL = Symbol("PassTextureRef");
|
|
175
|
+
var PassTextureRef = class PassTextureRef {
|
|
176
|
+
[PASS_TEXTURE_REF_SYMBOL] = true;
|
|
177
|
+
passName;
|
|
178
|
+
constructor(passName) {
|
|
179
|
+
this.passName = passName;
|
|
180
|
+
}
|
|
181
|
+
static is(obj) {
|
|
182
|
+
return obj && typeof obj === "object" && PASS_TEXTURE_REF_SYMBOL in obj;
|
|
183
|
+
}
|
|
184
|
+
static fromGPUBindingResource(resource) {
|
|
185
|
+
if (this.is(resource)) return resource;
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
static create(passName) {
|
|
189
|
+
return new PassTextureRef(passName);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
function isPassTextureRef(obj) {
|
|
193
|
+
return PassTextureRef.is(obj);
|
|
194
|
+
}
|
|
195
|
+
|
|
201
196
|
//#endregion
|
|
202
197
|
//#region src/index.ts
|
|
203
198
|
var WGSLRenderer = class {
|
|
@@ -229,19 +224,17 @@ var WGSLRenderer = class {
|
|
|
229
224
|
}
|
|
230
225
|
async resize(width, height) {
|
|
231
226
|
if (this.isResizing) return;
|
|
232
|
-
|
|
233
|
-
if (currentSize.width === width && currentSize.height === height) return;
|
|
227
|
+
if (this.canvas.width === width && this.canvas.height === height) return;
|
|
234
228
|
this.isResizing = true;
|
|
235
229
|
this.canvas.width = width;
|
|
236
230
|
this.canvas.height = height;
|
|
237
231
|
const future = this.device.queue.onSubmittedWorkDone();
|
|
238
232
|
future.catch(() => {
|
|
233
|
+
console.warn("GPU work submission failed during resize.");
|
|
239
234
|
this.isResizing = false;
|
|
240
235
|
});
|
|
241
236
|
await future;
|
|
242
|
-
this.textureManager.cleanupOldTextures();
|
|
243
237
|
this.textureManager.resize(width, height);
|
|
244
|
-
this.updateAllBindGroups();
|
|
245
238
|
this.isResizing = false;
|
|
246
239
|
}
|
|
247
240
|
getContext() {
|
|
@@ -251,84 +244,79 @@ var WGSLRenderer = class {
|
|
|
251
244
|
return this.device;
|
|
252
245
|
}
|
|
253
246
|
/**
|
|
254
|
-
*
|
|
247
|
+
* Get texture reference by pass name
|
|
248
|
+
* Returns a PassTextureRef that will resolve to the actual texture at render time
|
|
255
249
|
*/
|
|
256
|
-
|
|
257
|
-
this.
|
|
258
|
-
|
|
259
|
-
const finalBindGroupEntries = [];
|
|
260
|
-
if (index >= 1) {
|
|
261
|
-
const previousOutputTextureName = `pass_${index - 1}_output`;
|
|
262
|
-
let previousOutput = this.textureManager.getTexture(previousOutputTextureName);
|
|
263
|
-
if (!previousOutput) previousOutput = this.textureManager.recreateTexture(previousOutputTextureName, this.format);
|
|
264
|
-
finalBindGroupEntries.push({
|
|
265
|
-
binding: 0,
|
|
266
|
-
resource: previousOutput.createView()
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
if (pass.passResources) pass.passResources.forEach((resource, resourceIndex) => {
|
|
270
|
-
finalBindGroupEntries.push({
|
|
271
|
-
binding: resourceIndex + 1,
|
|
272
|
-
resource
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
pass.updateBindGroup(finalBindGroupEntries);
|
|
276
|
-
});
|
|
250
|
+
getPassTexture(passName) {
|
|
251
|
+
if (!this.passes.find((pass) => pass.name === passName)) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
252
|
+
return PassTextureRef.create(passName);
|
|
277
253
|
}
|
|
278
254
|
/**
|
|
279
|
-
*
|
|
255
|
+
* Resolve a PassTextureRef to actual GPUTextureView with validation
|
|
280
256
|
*/
|
|
281
|
-
|
|
282
|
-
this.passes.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
257
|
+
resolveTextureRef(ref) {
|
|
258
|
+
const targetPassIndex = this.passes.findIndex((pass) => pass.name === ref.passName);
|
|
259
|
+
if (targetPassIndex === -1) throw new Error(`Cannot find pass named '${ref.passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
260
|
+
const textureName = `pass_${targetPassIndex}_output`;
|
|
261
|
+
let texture = this.textureManager.getTexture(textureName);
|
|
262
|
+
if (!texture) texture = this.textureManager.createTexture(textureName, this.format);
|
|
263
|
+
return texture.createView();
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get pass by name
|
|
267
|
+
*/
|
|
268
|
+
getPassByName(passName) {
|
|
269
|
+
return this.passes.find((pass) => pass.name === passName);
|
|
288
270
|
}
|
|
289
271
|
/**
|
|
290
272
|
* Add a render pass to the multi-pass pipeline
|
|
291
273
|
*/
|
|
292
274
|
addPass(descriptor) {
|
|
293
275
|
const finalBindGroupEntries = [];
|
|
294
|
-
|
|
295
|
-
if (!firstPass) {
|
|
296
|
-
const previousOutput = this.getPassOutput(this.passes.length - 1);
|
|
297
|
-
if (previousOutput) finalBindGroupEntries.push({
|
|
298
|
-
binding: 0,
|
|
299
|
-
resource: previousOutput.createView()
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
descriptor.resources.forEach((resource, index) => {
|
|
276
|
+
descriptor.resources?.forEach((resource, index) => {
|
|
303
277
|
finalBindGroupEntries.push({
|
|
304
|
-
binding: index
|
|
278
|
+
binding: index,
|
|
305
279
|
resource
|
|
306
280
|
});
|
|
307
281
|
});
|
|
308
|
-
const
|
|
282
|
+
const internalDescriptor = {
|
|
309
283
|
name: descriptor.name,
|
|
310
284
|
shaderCode: descriptor.shaderCode,
|
|
285
|
+
entryPoints: descriptor.entryPoints,
|
|
311
286
|
clearColor: descriptor.clearColor,
|
|
312
287
|
blendMode: descriptor.blendMode,
|
|
313
|
-
bindGroupEntries: finalBindGroupEntries
|
|
314
|
-
|
|
315
|
-
|
|
288
|
+
bindGroupEntries: finalBindGroupEntries,
|
|
289
|
+
view: descriptor.view,
|
|
290
|
+
format: descriptor.format
|
|
291
|
+
};
|
|
292
|
+
const pipelineFormat = descriptor.format || this.format;
|
|
293
|
+
const pass = new RenderPass(internalDescriptor, this.device, pipelineFormat, "auto");
|
|
294
|
+
pass.passResources = descriptor.resources ?? [];
|
|
316
295
|
this.passes.push(pass);
|
|
317
|
-
const currentPassIndex = this.passes.length - 1;
|
|
318
|
-
const textureName = `pass_${currentPassIndex}_output`;
|
|
319
|
-
this.textureManager.createTexture(textureName, this.format);
|
|
320
|
-
this.passes[currentPassIndex].hasOutputTexture = true;
|
|
321
296
|
}
|
|
322
297
|
/**
|
|
323
|
-
*
|
|
298
|
+
* Resolve resource to actual GPU binding resource
|
|
299
|
+
* Handles PassTextureRef by getting the current texture view with validation
|
|
324
300
|
*/
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
301
|
+
resolveResource(resource) {
|
|
302
|
+
if (isPassTextureRef(resource)) return this.resolveTextureRef(resource);
|
|
303
|
+
return resource;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Update bind groups to resolve current texture references
|
|
307
|
+
* Call this before rendering to ensure all PassTextureRef are resolved
|
|
308
|
+
*/
|
|
309
|
+
updateBindGroups() {
|
|
310
|
+
this.passes.forEach((pass) => {
|
|
311
|
+
const finalBindGroupEntries = [];
|
|
312
|
+
pass.passResources.forEach((resource, index) => {
|
|
313
|
+
finalBindGroupEntries.push({
|
|
314
|
+
binding: index,
|
|
315
|
+
resource: this.resolveResource(resource)
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
pass.updateBindGroup(finalBindGroupEntries);
|
|
319
|
+
});
|
|
332
320
|
}
|
|
333
321
|
/**
|
|
334
322
|
* Create a uniforms
|
|
@@ -385,22 +373,25 @@ var WGSLRenderer = class {
|
|
|
385
373
|
}
|
|
386
374
|
renderFrame() {
|
|
387
375
|
if (this.passes.length === 0) return;
|
|
376
|
+
this.updateBindGroups();
|
|
388
377
|
const commandEncoder = this.device.createCommandEncoder();
|
|
389
378
|
for (let i = 0; i < this.passes.length; i++) {
|
|
390
379
|
const pass = this.passes[i];
|
|
380
|
+
let loadOp = "load";
|
|
381
|
+
const isLast = i === this.passes.length - 1;
|
|
382
|
+
if (isLast) loadOp = "clear";
|
|
391
383
|
let renderTarget;
|
|
392
|
-
|
|
393
|
-
if (
|
|
384
|
+
if (pass.view) renderTarget = pass.view;
|
|
385
|
+
else if (isLast) renderTarget = this.ctx.getCurrentTexture().createView();
|
|
394
386
|
else {
|
|
395
387
|
const textureName = `pass_${i}_output`;
|
|
396
|
-
|
|
397
|
-
if (!texture)
|
|
388
|
+
let texture = this.textureManager.getTexture(textureName);
|
|
389
|
+
if (!texture) texture = this.textureManager.createTexture(textureName, "rgba16float");
|
|
398
390
|
renderTarget = texture.createView();
|
|
399
|
-
loadOp = "load";
|
|
400
391
|
}
|
|
401
392
|
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
402
393
|
view: renderTarget,
|
|
403
|
-
loadOp
|
|
394
|
+
loadOp,
|
|
404
395
|
storeOp: "store",
|
|
405
396
|
clearValue: pass.clearColor
|
|
406
397
|
}] });
|
|
@@ -413,9 +404,11 @@ var WGSLRenderer = class {
|
|
|
413
404
|
this.device.queue.submit([commandEncoder.finish()]);
|
|
414
405
|
}
|
|
415
406
|
loopRender(cb) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
407
|
+
this.animationFrameId = requestAnimationFrame((t) => {
|
|
408
|
+
cb?.(t);
|
|
409
|
+
this.renderFrame();
|
|
410
|
+
this.loopRender(cb);
|
|
411
|
+
});
|
|
419
412
|
}
|
|
420
413
|
stopLoop() {
|
|
421
414
|
if (this.animationFrameId !== null) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wgsl-renderer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "一个基于WebGPU和WGSL的多通道渲染器",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -33,10 +33,12 @@
|
|
|
33
33
|
"files": [
|
|
34
34
|
"dist"
|
|
35
35
|
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@webgpu/types": "^0.1.66"
|
|
38
|
+
},
|
|
36
39
|
"devDependencies": {
|
|
37
40
|
"@taiyuuki/eslint-config": "^1.4.14",
|
|
38
41
|
"@types/node": "^20.14.2",
|
|
39
|
-
"@webgpu/types": "^0.1.66",
|
|
40
42
|
"eslint": "^9.5.0",
|
|
41
43
|
"rimraf": "^6.1.2",
|
|
42
44
|
"rolldown": "1.0.0-beta.52",
|