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