wgsl-renderer 0.0.2 → 0.0.4
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 +111 -114
- package/dist/esm/index.d.ts +154 -0
- package/dist/esm/index.js +111 -114
- package/package.json +6 -4
- package/dist/types/index.d.ts +0 -85
- package/dist/types/index.js +0 -434
package/dist/types/index.js
DELETED
|
@@ -1,434 +0,0 @@
|
|
|
1
|
-
//#region src/RenderPass.ts
|
|
2
|
-
var RenderPass = class {
|
|
3
|
-
name;
|
|
4
|
-
pipeline;
|
|
5
|
-
bindGroup;
|
|
6
|
-
vertexBuffer;
|
|
7
|
-
clearColor;
|
|
8
|
-
blendMode;
|
|
9
|
-
hasOutputTexture = false;
|
|
10
|
-
passResources = [];
|
|
11
|
-
device;
|
|
12
|
-
originalBindGroupEntries;
|
|
13
|
-
constructor(descriptor, device, format, layout) {
|
|
14
|
-
this.device = device;
|
|
15
|
-
this.originalBindGroupEntries = [...descriptor.bindGroupEntries || []];
|
|
16
|
-
this.name = descriptor.name;
|
|
17
|
-
this.clearColor = descriptor.clearColor || {
|
|
18
|
-
r: 0,
|
|
19
|
-
g: 0,
|
|
20
|
-
b: 0,
|
|
21
|
-
a: 1
|
|
22
|
-
};
|
|
23
|
-
this.blendMode = descriptor.blendMode || "alpha";
|
|
24
|
-
const module = this.device.createShaderModule({ code: descriptor.shaderCode });
|
|
25
|
-
this.vertexBuffer = this.device.createBuffer({
|
|
26
|
-
size: 36,
|
|
27
|
-
usage: GPUBufferUsage.VERTEX,
|
|
28
|
-
mappedAtCreation: true
|
|
29
|
-
});
|
|
30
|
-
new Float32Array(this.vertexBuffer.getMappedRange()).set([
|
|
31
|
-
-1,
|
|
32
|
-
-1,
|
|
33
|
-
0,
|
|
34
|
-
3,
|
|
35
|
-
-1,
|
|
36
|
-
0,
|
|
37
|
-
-1,
|
|
38
|
-
3,
|
|
39
|
-
0
|
|
40
|
-
]);
|
|
41
|
-
this.vertexBuffer.unmap();
|
|
42
|
-
this.pipeline = this.device.createRenderPipeline({
|
|
43
|
-
layout,
|
|
44
|
-
vertex: {
|
|
45
|
-
module,
|
|
46
|
-
entryPoint: "vs_main",
|
|
47
|
-
buffers: [{
|
|
48
|
-
arrayStride: 12,
|
|
49
|
-
attributes: [{
|
|
50
|
-
shaderLocation: 0,
|
|
51
|
-
offset: 0,
|
|
52
|
-
format: "float32x3"
|
|
53
|
-
}]
|
|
54
|
-
}]
|
|
55
|
-
},
|
|
56
|
-
fragment: {
|
|
57
|
-
module,
|
|
58
|
-
entryPoint: "fs_main",
|
|
59
|
-
targets: [{
|
|
60
|
-
format,
|
|
61
|
-
blend: this.getBlendState()
|
|
62
|
-
}]
|
|
63
|
-
},
|
|
64
|
-
primitive: { topology: "triangle-list" }
|
|
65
|
-
});
|
|
66
|
-
const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
|
|
67
|
-
this.bindGroup = this.device.createBindGroup({
|
|
68
|
-
layout: bindGroupLayout,
|
|
69
|
-
entries: this.originalBindGroupEntries
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Update bind group with new entries (e.g., after texture resize)
|
|
74
|
-
*/
|
|
75
|
-
updateBindGroup(newEntries) {
|
|
76
|
-
const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
|
|
77
|
-
this.originalBindGroupEntries = [...newEntries];
|
|
78
|
-
this.bindGroup = this.device.createBindGroup({
|
|
79
|
-
layout: bindGroupLayout,
|
|
80
|
-
entries: newEntries
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
getBlendState() {
|
|
84
|
-
switch (this.blendMode) {
|
|
85
|
-
case "none": return;
|
|
86
|
-
case "alpha": return {
|
|
87
|
-
color: {
|
|
88
|
-
srcFactor: "src-alpha",
|
|
89
|
-
dstFactor: "one-minus-src-alpha",
|
|
90
|
-
operation: "add"
|
|
91
|
-
},
|
|
92
|
-
alpha: {
|
|
93
|
-
srcFactor: "one",
|
|
94
|
-
dstFactor: "one-minus-src-alpha",
|
|
95
|
-
operation: "add"
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
case "additive": return {
|
|
99
|
-
color: {
|
|
100
|
-
srcFactor: "src-alpha",
|
|
101
|
-
dstFactor: "one",
|
|
102
|
-
operation: "add"
|
|
103
|
-
},
|
|
104
|
-
alpha: {
|
|
105
|
-
srcFactor: "one",
|
|
106
|
-
dstFactor: "one",
|
|
107
|
-
operation: "add"
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
case "multiply": return {
|
|
111
|
-
color: {
|
|
112
|
-
srcFactor: "src",
|
|
113
|
-
dstFactor: "dst",
|
|
114
|
-
operation: "add"
|
|
115
|
-
},
|
|
116
|
-
alpha: {
|
|
117
|
-
srcFactor: "one",
|
|
118
|
-
dstFactor: "one-minus-src-alpha",
|
|
119
|
-
operation: "add"
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
default: return;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
//#endregion
|
|
128
|
-
//#region src/TextureManager.ts
|
|
129
|
-
var TextureManager = class {
|
|
130
|
-
textures = /* @__PURE__ */ new Map();
|
|
131
|
-
device;
|
|
132
|
-
width;
|
|
133
|
-
height;
|
|
134
|
-
oldTextures = [];
|
|
135
|
-
constructor(device, width, height) {
|
|
136
|
-
this.device = device;
|
|
137
|
-
this.width = width;
|
|
138
|
-
this.height = height;
|
|
139
|
-
}
|
|
140
|
-
createTexture(name, format) {
|
|
141
|
-
if (this.textures.has(name)) {
|
|
142
|
-
const oldTexture = this.textures.get(name);
|
|
143
|
-
this.oldTextures.push(oldTexture);
|
|
144
|
-
}
|
|
145
|
-
const texture = this.device.createTexture({
|
|
146
|
-
size: [this.width, this.height],
|
|
147
|
-
format: format || "bgra8unorm",
|
|
148
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
|
|
149
|
-
});
|
|
150
|
-
this.textures.set(name, texture);
|
|
151
|
-
return texture;
|
|
152
|
-
}
|
|
153
|
-
getTexture(name) {
|
|
154
|
-
return this.textures.get(name);
|
|
155
|
-
}
|
|
156
|
-
resize(width, height) {
|
|
157
|
-
if (width === this.width && height === this.height) return;
|
|
158
|
-
this.width = width;
|
|
159
|
-
this.height = height;
|
|
160
|
-
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
|
-
texture.destroy();
|
|
184
|
-
});
|
|
185
|
-
this.oldTextures.length = 0;
|
|
186
|
-
}
|
|
187
|
-
destroy() {
|
|
188
|
-
this.textures.forEach((texture) => texture.destroy());
|
|
189
|
-
this.textures.clear();
|
|
190
|
-
this.oldTextures.forEach((texture) => texture.destroy());
|
|
191
|
-
this.oldTextures.length = 0;
|
|
192
|
-
}
|
|
193
|
-
getPixelSize() {
|
|
194
|
-
return {
|
|
195
|
-
width: this.width,
|
|
196
|
-
height: this.height
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
//#endregion
|
|
202
|
-
//#region src/index.ts
|
|
203
|
-
var WGSLRenderer = class {
|
|
204
|
-
ctx;
|
|
205
|
-
device;
|
|
206
|
-
format;
|
|
207
|
-
passes = [];
|
|
208
|
-
textureManager;
|
|
209
|
-
animationFrameId = null;
|
|
210
|
-
isResizing = false;
|
|
211
|
-
constructor(canvas, options) {
|
|
212
|
-
this.canvas = canvas;
|
|
213
|
-
this.options = options;
|
|
214
|
-
if (!navigator.gpu) throw new Error("WebGPU is not supported in this browser.");
|
|
215
|
-
this.ctx = canvas.getContext("webgpu");
|
|
216
|
-
}
|
|
217
|
-
async init() {
|
|
218
|
-
this.device = await (await navigator.gpu.requestAdapter()).requestDevice();
|
|
219
|
-
this.format = navigator.gpu.getPreferredCanvasFormat();
|
|
220
|
-
const config = Object.assign({
|
|
221
|
-
device: this.device,
|
|
222
|
-
format: this.format,
|
|
223
|
-
alphaMode: "opaque"
|
|
224
|
-
}, this.options?.config);
|
|
225
|
-
this.ctx.configure(config);
|
|
226
|
-
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
227
|
-
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
228
|
-
this.textureManager = new TextureManager(this.device, canvasWidth, canvasHeight);
|
|
229
|
-
}
|
|
230
|
-
async resize(width, height) {
|
|
231
|
-
if (this.isResizing) return;
|
|
232
|
-
const currentSize = this.textureManager.getPixelSize();
|
|
233
|
-
if (currentSize.width === width && currentSize.height === height) return;
|
|
234
|
-
this.isResizing = true;
|
|
235
|
-
this.canvas.width = width;
|
|
236
|
-
this.canvas.height = height;
|
|
237
|
-
const future = this.device.queue.onSubmittedWorkDone();
|
|
238
|
-
future.catch(() => {
|
|
239
|
-
this.isResizing = false;
|
|
240
|
-
});
|
|
241
|
-
await future;
|
|
242
|
-
this.textureManager.cleanupOldTextures();
|
|
243
|
-
this.textureManager.resize(width, height);
|
|
244
|
-
this.updateAllBindGroups();
|
|
245
|
-
this.isResizing = false;
|
|
246
|
-
}
|
|
247
|
-
getContext() {
|
|
248
|
-
return this.ctx;
|
|
249
|
-
}
|
|
250
|
-
getDevice() {
|
|
251
|
-
return this.device;
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Update bind groups for all passes after texture resize
|
|
255
|
-
*/
|
|
256
|
-
updateAllBindGroups() {
|
|
257
|
-
this.recreatePassTextures();
|
|
258
|
-
this.passes.forEach((pass, index) => {
|
|
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
|
-
});
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Recreate all pass output textures after resize
|
|
280
|
-
*/
|
|
281
|
-
recreatePassTextures() {
|
|
282
|
-
this.passes.forEach((pass, index) => {
|
|
283
|
-
if (index < this.passes.length - 1 || pass.hasOutputTexture) {
|
|
284
|
-
const textureName = `pass_${index}_output`;
|
|
285
|
-
this.textureManager.recreateTexture(textureName, this.format);
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Add a render pass to the multi-pass pipeline
|
|
291
|
-
*/
|
|
292
|
-
addPass(descriptor) {
|
|
293
|
-
const finalBindGroupEntries = [];
|
|
294
|
-
const firstPass = this.passes.length === 0;
|
|
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) => {
|
|
303
|
-
finalBindGroupEntries.push({
|
|
304
|
-
binding: index + (firstPass ? 0 : 1),
|
|
305
|
-
resource
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
const pass = new RenderPass({
|
|
309
|
-
name: descriptor.name,
|
|
310
|
-
shaderCode: descriptor.shaderCode,
|
|
311
|
-
clearColor: descriptor.clearColor,
|
|
312
|
-
blendMode: descriptor.blendMode,
|
|
313
|
-
bindGroupEntries: finalBindGroupEntries
|
|
314
|
-
}, this.device, this.format, "auto");
|
|
315
|
-
pass.passResources = [...descriptor.resources];
|
|
316
|
-
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
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Get the output texture of a specific pass
|
|
324
|
-
*/
|
|
325
|
-
getPassOutput(passIndex) {
|
|
326
|
-
if (passIndex < 0 || passIndex >= this.passes.length) return;
|
|
327
|
-
if (passIndex === this.passes.length - 1) {
|
|
328
|
-
if (!this.passes[passIndex]?.hasOutputTexture) return;
|
|
329
|
-
}
|
|
330
|
-
const textureName = `pass_${passIndex}_output`;
|
|
331
|
-
return this.textureManager.getTexture(textureName);
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Create a uniforms
|
|
335
|
-
* @param length The length of the uniform buffer in number of floats
|
|
336
|
-
* @return The uniform object containing the buffer and data array
|
|
337
|
-
*/
|
|
338
|
-
createUniforms(length) {
|
|
339
|
-
const values = new Float32Array(Math.ceil(length));
|
|
340
|
-
const buffer = this.device.createBuffer({
|
|
341
|
-
size: values.byteLength,
|
|
342
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
343
|
-
});
|
|
344
|
-
return {
|
|
345
|
-
values,
|
|
346
|
-
apply: () => {
|
|
347
|
-
this.device.queue.writeBuffer(buffer, 0, values.buffer, values.byteOffset, values.byteLength);
|
|
348
|
-
},
|
|
349
|
-
getBuffer: () => buffer
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Create a sampler
|
|
354
|
-
*/
|
|
355
|
-
createSampler(options) {
|
|
356
|
-
return this.device.createSampler(Object.assign({
|
|
357
|
-
magFilter: "linear",
|
|
358
|
-
minFilter: "linear",
|
|
359
|
-
addressModeU: "clamp-to-edge",
|
|
360
|
-
addressModeV: "clamp-to-edge"
|
|
361
|
-
}, options));
|
|
362
|
-
}
|
|
363
|
-
async loadImageTexture(url) {
|
|
364
|
-
const resp = fetch(url);
|
|
365
|
-
resp.catch((err) => {
|
|
366
|
-
console.error("Failed to load texture:", err);
|
|
367
|
-
});
|
|
368
|
-
const res = await resp;
|
|
369
|
-
const imgBitmap = await createImageBitmap(await res.blob());
|
|
370
|
-
const texture = this.device.createTexture({
|
|
371
|
-
size: [
|
|
372
|
-
imgBitmap.width,
|
|
373
|
-
imgBitmap.height,
|
|
374
|
-
1
|
|
375
|
-
],
|
|
376
|
-
format: "rgba8unorm",
|
|
377
|
-
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
378
|
-
});
|
|
379
|
-
this.device.queue.copyExternalImageToTexture({ source: imgBitmap }, { texture }, [imgBitmap.width, imgBitmap.height]);
|
|
380
|
-
return {
|
|
381
|
-
texture,
|
|
382
|
-
width: imgBitmap.width,
|
|
383
|
-
height: imgBitmap.height
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
renderFrame() {
|
|
387
|
-
if (this.passes.length === 0) return;
|
|
388
|
-
const commandEncoder = this.device.createCommandEncoder();
|
|
389
|
-
for (let i = 0; i < this.passes.length; i++) {
|
|
390
|
-
const pass = this.passes[i];
|
|
391
|
-
let renderTarget;
|
|
392
|
-
let loadOp = "clear";
|
|
393
|
-
if (i === this.passes.length - 1) renderTarget = this.ctx.getCurrentTexture().createView();
|
|
394
|
-
else {
|
|
395
|
-
const textureName = `pass_${i}_output`;
|
|
396
|
-
const texture = this.textureManager.getTexture(textureName);
|
|
397
|
-
if (!texture) continue;
|
|
398
|
-
renderTarget = texture.createView();
|
|
399
|
-
loadOp = "load";
|
|
400
|
-
}
|
|
401
|
-
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
402
|
-
view: renderTarget,
|
|
403
|
-
loadOp: i === 0 ? "clear" : loadOp,
|
|
404
|
-
storeOp: "store",
|
|
405
|
-
clearValue: pass.clearColor
|
|
406
|
-
}] });
|
|
407
|
-
renderPass.setPipeline(pass.pipeline);
|
|
408
|
-
if (pass.bindGroup) renderPass.setBindGroup(0, pass.bindGroup);
|
|
409
|
-
renderPass.setVertexBuffer(0, pass.vertexBuffer);
|
|
410
|
-
renderPass.draw(3, 1, 0, 0);
|
|
411
|
-
renderPass.end();
|
|
412
|
-
}
|
|
413
|
-
this.device.queue.submit([commandEncoder.finish()]);
|
|
414
|
-
}
|
|
415
|
-
loopRender(cb) {
|
|
416
|
-
cb?.();
|
|
417
|
-
this.renderFrame();
|
|
418
|
-
this.animationFrameId = requestAnimationFrame(() => this.loopRender(cb));
|
|
419
|
-
}
|
|
420
|
-
stopLoop() {
|
|
421
|
-
if (this.animationFrameId !== null) {
|
|
422
|
-
cancelAnimationFrame(this.animationFrameId);
|
|
423
|
-
this.animationFrameId = null;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
async function createWGSLRenderer(cvs, options) {
|
|
428
|
-
const renderer = new WGSLRenderer(cvs, options);
|
|
429
|
-
await renderer.init();
|
|
430
|
-
return renderer;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
//#endregion
|
|
434
|
-
export { createWGSLRenderer };
|