wgsl-renderer 0.0.1 → 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/LICENSE.md +8 -8
- package/README.md +294 -200
- package/README.zh-CN.md +417 -0
- package/dist/cjs/index.js +147 -157
- package/dist/esm/index.js +147 -157
- package/dist/types/index.d.ts +64 -55
- package/dist/types/index.js +147 -157
- package/package.json +4 -2
package/dist/esm/index.js
CHANGED
|
@@ -6,8 +6,12 @@ var RenderPass = class {
|
|
|
6
6
|
vertexBuffer;
|
|
7
7
|
clearColor;
|
|
8
8
|
blendMode;
|
|
9
|
-
|
|
9
|
+
view;
|
|
10
|
+
format;
|
|
11
|
+
passResources = [];
|
|
12
|
+
device;
|
|
10
13
|
constructor(descriptor, device, format, layout) {
|
|
14
|
+
this.device = device;
|
|
11
15
|
this.name = descriptor.name;
|
|
12
16
|
this.clearColor = descriptor.clearColor || {
|
|
13
17
|
r: 0,
|
|
@@ -15,9 +19,12 @@ var RenderPass = class {
|
|
|
15
19
|
b: 0,
|
|
16
20
|
a: 1
|
|
17
21
|
};
|
|
18
|
-
this.blendMode = descriptor.blendMode || "
|
|
19
|
-
|
|
20
|
-
this.
|
|
22
|
+
this.blendMode = descriptor.blendMode || "none";
|
|
23
|
+
this.view = descriptor.view;
|
|
24
|
+
this.format = descriptor.format;
|
|
25
|
+
const actualFormat = descriptor.format || format;
|
|
26
|
+
const module = this.device.createShaderModule({ code: descriptor.shaderCode });
|
|
27
|
+
this.vertexBuffer = this.device.createBuffer({
|
|
21
28
|
size: 36,
|
|
22
29
|
usage: GPUBufferUsage.VERTEX,
|
|
23
30
|
mappedAtCreation: true
|
|
@@ -34,11 +41,13 @@ var RenderPass = class {
|
|
|
34
41
|
0
|
|
35
42
|
]);
|
|
36
43
|
this.vertexBuffer.unmap();
|
|
37
|
-
|
|
44
|
+
const vertexEntryPoint = descriptor.entryPoints?.vertex || "vs_main";
|
|
45
|
+
const fragmentEntryPoint = descriptor.entryPoints?.fragment || "fs_main";
|
|
46
|
+
this.pipeline = this.device.createRenderPipeline({
|
|
38
47
|
layout,
|
|
39
48
|
vertex: {
|
|
40
49
|
module,
|
|
41
|
-
entryPoint:
|
|
50
|
+
entryPoint: vertexEntryPoint,
|
|
42
51
|
buffers: [{
|
|
43
52
|
arrayStride: 12,
|
|
44
53
|
attributes: [{
|
|
@@ -50,18 +59,24 @@ var RenderPass = class {
|
|
|
50
59
|
},
|
|
51
60
|
fragment: {
|
|
52
61
|
module,
|
|
53
|
-
entryPoint:
|
|
62
|
+
entryPoint: fragmentEntryPoint,
|
|
54
63
|
targets: [{
|
|
55
|
-
format,
|
|
64
|
+
format: actualFormat,
|
|
56
65
|
blend: this.getBlendState()
|
|
57
66
|
}]
|
|
58
67
|
},
|
|
59
68
|
primitive: { topology: "triangle-list" }
|
|
60
69
|
});
|
|
70
|
+
this.bindGroup = null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Update bind group with new entries (e.g., after texture resize)
|
|
74
|
+
*/
|
|
75
|
+
updateBindGroup(newEntries) {
|
|
61
76
|
const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
|
|
62
|
-
this.bindGroup = device.createBindGroup({
|
|
77
|
+
this.bindGroup = this.device.createBindGroup({
|
|
63
78
|
layout: bindGroupLayout,
|
|
64
|
-
entries:
|
|
79
|
+
entries: newEntries
|
|
65
80
|
});
|
|
66
81
|
}
|
|
67
82
|
getBlendState() {
|
|
@@ -125,7 +140,7 @@ var TextureManager = class {
|
|
|
125
140
|
const texture = this.device.createTexture({
|
|
126
141
|
size: [this.width, this.height],
|
|
127
142
|
format: format || "bgra8unorm",
|
|
128
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
|
|
143
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
|
|
129
144
|
});
|
|
130
145
|
this.textures.set(name, texture);
|
|
131
146
|
return texture;
|
|
@@ -135,10 +150,12 @@ var TextureManager = class {
|
|
|
135
150
|
}
|
|
136
151
|
resize(width, height) {
|
|
137
152
|
if (width === this.width && height === this.height) return;
|
|
138
|
-
this.textures.forEach((texture) => texture.destroy());
|
|
139
|
-
this.textures.clear();
|
|
140
153
|
this.width = width;
|
|
141
154
|
this.height = height;
|
|
155
|
+
this.textures.forEach((texture) => {
|
|
156
|
+
texture.destroy();
|
|
157
|
+
});
|
|
158
|
+
this.textures.clear();
|
|
142
159
|
}
|
|
143
160
|
destroy() {
|
|
144
161
|
this.textures.forEach((texture) => texture.destroy());
|
|
@@ -152,6 +169,30 @@ var TextureManager = class {
|
|
|
152
169
|
}
|
|
153
170
|
};
|
|
154
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
|
+
|
|
155
196
|
//#endregion
|
|
156
197
|
//#region src/index.ts
|
|
157
198
|
var WGSLRenderer = class {
|
|
@@ -160,145 +201,122 @@ var WGSLRenderer = class {
|
|
|
160
201
|
format;
|
|
161
202
|
passes = [];
|
|
162
203
|
textureManager;
|
|
163
|
-
backgroundPassAdded = false;
|
|
164
|
-
backgroundColor = {
|
|
165
|
-
r: .1,
|
|
166
|
-
g: .1,
|
|
167
|
-
b: .1,
|
|
168
|
-
a: 1
|
|
169
|
-
};
|
|
170
|
-
uniforms = /* @__PURE__ */ new Map();
|
|
171
204
|
animationFrameId = null;
|
|
205
|
+
isResizing = false;
|
|
172
206
|
constructor(canvas, options) {
|
|
173
207
|
this.canvas = canvas;
|
|
208
|
+
this.options = options;
|
|
174
209
|
if (!navigator.gpu) throw new Error("WebGPU is not supported in this browser.");
|
|
175
210
|
this.ctx = canvas.getContext("webgpu");
|
|
176
|
-
switch (typeof options?.backgroundColor) {
|
|
177
|
-
case "number":
|
|
178
|
-
const hex = options.backgroundColor;
|
|
179
|
-
this.backgroundColor = {
|
|
180
|
-
r: (hex >> 16 & 255) / 255,
|
|
181
|
-
g: (hex >> 8 & 255) / 255,
|
|
182
|
-
b: (hex & 255) / 255,
|
|
183
|
-
a: 1
|
|
184
|
-
};
|
|
185
|
-
break;
|
|
186
|
-
case "string":
|
|
187
|
-
const m = options.backgroundColor.match(/^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i);
|
|
188
|
-
if (m) this.backgroundColor = {
|
|
189
|
-
r: Number.parseInt(m[1], 16) / 255,
|
|
190
|
-
g: Number.parseInt(m[2], 16) / 255,
|
|
191
|
-
b: Number.parseInt(m[3], 16) / 255,
|
|
192
|
-
a: 1
|
|
193
|
-
};
|
|
194
|
-
break;
|
|
195
|
-
case "object":
|
|
196
|
-
Object.assign(this.backgroundColor, options.backgroundColor);
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
211
|
}
|
|
200
212
|
async init() {
|
|
201
213
|
this.device = await (await navigator.gpu.requestAdapter()).requestDevice();
|
|
202
214
|
this.format = navigator.gpu.getPreferredCanvasFormat();
|
|
203
|
-
|
|
215
|
+
const config = Object.assign({
|
|
204
216
|
device: this.device,
|
|
205
217
|
format: this.format,
|
|
206
218
|
alphaMode: "opaque"
|
|
207
|
-
});
|
|
219
|
+
}, this.options?.config);
|
|
220
|
+
this.ctx.configure(config);
|
|
208
221
|
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
209
222
|
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
210
223
|
this.textureManager = new TextureManager(this.device, canvasWidth, canvasHeight);
|
|
211
|
-
|
|
224
|
+
}
|
|
225
|
+
async resize(width, height) {
|
|
226
|
+
if (this.isResizing) return;
|
|
227
|
+
if (this.canvas.width === width && this.canvas.height === height) return;
|
|
228
|
+
this.isResizing = true;
|
|
229
|
+
this.canvas.width = width;
|
|
230
|
+
this.canvas.height = height;
|
|
231
|
+
const future = this.device.queue.onSubmittedWorkDone();
|
|
232
|
+
future.catch(() => {
|
|
233
|
+
console.warn("GPU work submission failed during resize.");
|
|
234
|
+
this.isResizing = false;
|
|
235
|
+
});
|
|
236
|
+
await future;
|
|
237
|
+
this.textureManager.resize(width, height);
|
|
238
|
+
this.isResizing = false;
|
|
239
|
+
}
|
|
240
|
+
getContext() {
|
|
241
|
+
return this.ctx;
|
|
242
|
+
}
|
|
243
|
+
getDevice() {
|
|
244
|
+
return this.device;
|
|
212
245
|
}
|
|
213
246
|
/**
|
|
214
|
-
*
|
|
247
|
+
* Get texture reference by pass name
|
|
248
|
+
* Returns a PassTextureRef that will resolve to the actual texture at render time
|
|
215
249
|
*/
|
|
216
|
-
|
|
217
|
-
if (!this.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
this.backgroundPassAdded = true;
|
|
237
|
-
this.textureManager.createTexture("pass_0_output", this.format);
|
|
238
|
-
}
|
|
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);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Resolve a PassTextureRef to actual GPUTextureView with validation
|
|
256
|
+
*/
|
|
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);
|
|
239
270
|
}
|
|
240
271
|
/**
|
|
241
272
|
* Add a render pass to the multi-pass pipeline
|
|
242
273
|
*/
|
|
243
274
|
addPass(descriptor) {
|
|
244
275
|
const finalBindGroupEntries = [];
|
|
245
|
-
|
|
246
|
-
const previousOutput = this.getPassOutput(this.passes.length - 1);
|
|
247
|
-
if (previousOutput) finalBindGroupEntries.push({
|
|
248
|
-
binding: 0,
|
|
249
|
-
resource: previousOutput.createView()
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
descriptor.resources.forEach((resource, index) => {
|
|
276
|
+
descriptor.resources?.forEach((resource, index) => {
|
|
253
277
|
finalBindGroupEntries.push({
|
|
254
|
-
binding: index
|
|
278
|
+
binding: index,
|
|
255
279
|
resource
|
|
256
280
|
});
|
|
257
281
|
});
|
|
258
|
-
const
|
|
282
|
+
const internalDescriptor = {
|
|
259
283
|
name: descriptor.name,
|
|
260
284
|
shaderCode: descriptor.shaderCode,
|
|
285
|
+
entryPoints: descriptor.entryPoints,
|
|
261
286
|
clearColor: descriptor.clearColor,
|
|
262
287
|
blendMode: descriptor.blendMode,
|
|
263
|
-
bindGroupEntries: finalBindGroupEntries
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const currentPassIndex = this.passes.length - 1;
|
|
267
|
-
const textureName = `pass_${currentPassIndex}_output`;
|
|
268
|
-
this.textureManager.createTexture(textureName, this.format);
|
|
269
|
-
this.passes[currentPassIndex].hasOutputTexture = true;
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Set background color
|
|
273
|
-
*/
|
|
274
|
-
setBackgroundColor(r, g, b, a = 1) {
|
|
275
|
-
this.backgroundColor = {
|
|
276
|
-
r,
|
|
277
|
-
g,
|
|
278
|
-
b,
|
|
279
|
-
a
|
|
288
|
+
bindGroupEntries: finalBindGroupEntries,
|
|
289
|
+
view: descriptor.view,
|
|
290
|
+
format: descriptor.format
|
|
280
291
|
};
|
|
292
|
+
const pipelineFormat = descriptor.format || this.format;
|
|
293
|
+
const pass = new RenderPass(internalDescriptor, this.device, pipelineFormat, "auto");
|
|
294
|
+
pass.passResources = descriptor.resources ?? [];
|
|
295
|
+
this.passes.push(pass);
|
|
281
296
|
}
|
|
282
297
|
/**
|
|
283
|
-
*
|
|
284
|
-
*
|
|
298
|
+
* Resolve resource to actual GPU binding resource
|
|
299
|
+
* Handles PassTextureRef by getting the current texture view with validation
|
|
285
300
|
*/
|
|
286
|
-
|
|
287
|
-
if (
|
|
288
|
-
|
|
289
|
-
const textureName = `pass_${passIndex}_output`;
|
|
290
|
-
return this.textureManager.createTexture(textureName, this.format);
|
|
301
|
+
resolveResource(resource) {
|
|
302
|
+
if (isPassTextureRef(resource)) return this.resolveTextureRef(resource);
|
|
303
|
+
return resource;
|
|
291
304
|
}
|
|
292
305
|
/**
|
|
293
|
-
*
|
|
306
|
+
* Update bind groups to resolve current texture references
|
|
307
|
+
* Call this before rendering to ensure all PassTextureRef are resolved
|
|
294
308
|
*/
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
+
});
|
|
302
320
|
}
|
|
303
321
|
/**
|
|
304
322
|
* Create a uniforms
|
|
@@ -311,20 +329,13 @@ var WGSLRenderer = class {
|
|
|
311
329
|
size: values.byteLength,
|
|
312
330
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
313
331
|
});
|
|
314
|
-
|
|
315
|
-
const uniforms = {
|
|
316
|
-
id: uniformID,
|
|
332
|
+
return {
|
|
317
333
|
values,
|
|
318
334
|
apply: () => {
|
|
319
335
|
this.device.queue.writeBuffer(buffer, 0, values.buffer, values.byteOffset, values.byteLength);
|
|
320
336
|
},
|
|
321
337
|
getBuffer: () => buffer
|
|
322
338
|
};
|
|
323
|
-
this.uniforms.set(uniformID, uniforms);
|
|
324
|
-
return uniforms;
|
|
325
|
-
}
|
|
326
|
-
getUniformsByID(id) {
|
|
327
|
-
return this.uniforms.get(id);
|
|
328
339
|
}
|
|
329
340
|
/**
|
|
330
341
|
* Create a sampler
|
|
@@ -337,25 +348,7 @@ var WGSLRenderer = class {
|
|
|
337
348
|
addressModeV: "clamp-to-edge"
|
|
338
349
|
}, options));
|
|
339
350
|
}
|
|
340
|
-
|
|
341
|
-
* Create a bind group entry for texture
|
|
342
|
-
*/
|
|
343
|
-
createTextureBinding(texture) {
|
|
344
|
-
return texture.createView();
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Configure multi-pass rendering
|
|
348
|
-
*/
|
|
349
|
-
setupMultiPass(descriptor) {
|
|
350
|
-
this.passes = [];
|
|
351
|
-
this.textureManager.destroy();
|
|
352
|
-
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
353
|
-
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
354
|
-
this.textureManager = new TextureManager(this.device, canvasWidth, canvasHeight);
|
|
355
|
-
descriptor.passes.forEach((passDesc) => this.addPass(passDesc));
|
|
356
|
-
if (descriptor.output?.texture && !descriptor.output.writeToCanvas) this.textureManager.createTexture("final_output");
|
|
357
|
-
}
|
|
358
|
-
async loadTexture(url) {
|
|
351
|
+
async loadImageTexture(url) {
|
|
359
352
|
const resp = fetch(url);
|
|
360
353
|
resp.catch((err) => {
|
|
361
354
|
console.error("Failed to load texture:", err);
|
|
@@ -380,25 +373,25 @@ var WGSLRenderer = class {
|
|
|
380
373
|
}
|
|
381
374
|
renderFrame() {
|
|
382
375
|
if (this.passes.length === 0) return;
|
|
376
|
+
this.updateBindGroups();
|
|
383
377
|
const commandEncoder = this.device.createCommandEncoder();
|
|
384
|
-
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
385
|
-
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
386
|
-
this.textureManager.resize(canvasWidth, canvasHeight);
|
|
387
378
|
for (let i = 0; i < this.passes.length; i++) {
|
|
388
379
|
const pass = this.passes[i];
|
|
380
|
+
let loadOp = "load";
|
|
381
|
+
const isLast = i === this.passes.length - 1;
|
|
382
|
+
if (isLast) loadOp = "clear";
|
|
389
383
|
let renderTarget;
|
|
390
|
-
|
|
391
|
-
if (
|
|
384
|
+
if (pass.view) renderTarget = pass.view;
|
|
385
|
+
else if (isLast) renderTarget = this.ctx.getCurrentTexture().createView();
|
|
392
386
|
else {
|
|
393
387
|
const textureName = `pass_${i}_output`;
|
|
394
|
-
|
|
395
|
-
if (!texture)
|
|
388
|
+
let texture = this.textureManager.getTexture(textureName);
|
|
389
|
+
if (!texture) texture = this.textureManager.createTexture(textureName, "rgba16float");
|
|
396
390
|
renderTarget = texture.createView();
|
|
397
|
-
loadOp = "load";
|
|
398
391
|
}
|
|
399
392
|
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
400
393
|
view: renderTarget,
|
|
401
|
-
loadOp
|
|
394
|
+
loadOp,
|
|
402
395
|
storeOp: "store",
|
|
403
396
|
clearValue: pass.clearColor
|
|
404
397
|
}] });
|
|
@@ -411,9 +404,11 @@ var WGSLRenderer = class {
|
|
|
411
404
|
this.device.queue.submit([commandEncoder.finish()]);
|
|
412
405
|
}
|
|
413
406
|
loopRender(cb) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
407
|
+
this.animationFrameId = requestAnimationFrame((t) => {
|
|
408
|
+
cb?.(t);
|
|
409
|
+
this.renderFrame();
|
|
410
|
+
this.loopRender(cb);
|
|
411
|
+
});
|
|
417
412
|
}
|
|
418
413
|
stopLoop() {
|
|
419
414
|
if (this.animationFrameId !== null) {
|
|
@@ -421,11 +416,6 @@ var WGSLRenderer = class {
|
|
|
421
416
|
this.animationFrameId = null;
|
|
422
417
|
}
|
|
423
418
|
}
|
|
424
|
-
resize(width, height) {
|
|
425
|
-
this.canvas.width = width;
|
|
426
|
-
this.canvas.height = height;
|
|
427
|
-
this.textureManager.resize(width, height);
|
|
428
|
-
}
|
|
429
419
|
};
|
|
430
420
|
async function createWGSLRenderer(cvs, options) {
|
|
431
421
|
const renderer = new WGSLRenderer(cvs, options);
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
//#region src/RenderPass.d.ts
|
|
2
|
-
interface
|
|
2
|
+
interface RenderPassOptions {
|
|
3
3
|
name: string;
|
|
4
4
|
shaderCode: string;
|
|
5
|
+
entryPoints?: {
|
|
6
|
+
vertex?: string;
|
|
7
|
+
fragment?: string;
|
|
8
|
+
};
|
|
5
9
|
clearColor?: {
|
|
6
10
|
r: number;
|
|
7
11
|
g: number;
|
|
@@ -10,10 +14,16 @@ interface RenderPassDescriptor {
|
|
|
10
14
|
};
|
|
11
15
|
blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
|
|
12
16
|
resources: GPUBindingResource[];
|
|
17
|
+
view?: GPUTextureView;
|
|
18
|
+
format?: GPUTextureFormat;
|
|
13
19
|
}
|
|
14
20
|
interface InternalRenderPassDescriptor {
|
|
15
21
|
name: string;
|
|
16
22
|
shaderCode: string;
|
|
23
|
+
entryPoints?: {
|
|
24
|
+
vertex?: string;
|
|
25
|
+
fragment?: string;
|
|
26
|
+
};
|
|
17
27
|
clearColor?: {
|
|
18
28
|
r: number;
|
|
19
29
|
g: number;
|
|
@@ -22,15 +32,13 @@ interface InternalRenderPassDescriptor {
|
|
|
22
32
|
};
|
|
23
33
|
blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
|
|
24
34
|
bindGroupEntries: GPUBindGroupEntry[];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
texture?: GPUTexture;
|
|
28
|
-
writeToCanvas?: boolean;
|
|
35
|
+
view?: GPUTextureView;
|
|
36
|
+
format?: GPUTextureFormat;
|
|
29
37
|
}
|
|
30
38
|
declare class RenderPass {
|
|
31
39
|
name: string;
|
|
32
40
|
pipeline: GPURenderPipeline;
|
|
33
|
-
bindGroup: GPUBindGroup;
|
|
41
|
+
bindGroup: GPUBindGroup | null;
|
|
34
42
|
vertexBuffer: GPUBuffer;
|
|
35
43
|
clearColor: {
|
|
36
44
|
r: number;
|
|
@@ -39,101 +47,102 @@ declare class RenderPass {
|
|
|
39
47
|
a: number;
|
|
40
48
|
};
|
|
41
49
|
blendMode: 'additive' | 'alpha' | 'multiply' | 'none';
|
|
42
|
-
|
|
50
|
+
view?: GPUTextureView;
|
|
51
|
+
format?: GPUTextureFormat;
|
|
52
|
+
passResources: GPUBindingResource[];
|
|
53
|
+
private device;
|
|
43
54
|
constructor(descriptor: InternalRenderPassDescriptor, device: GPUDevice, format: GPUTextureFormat, layout: GPUPipelineLayout | 'auto');
|
|
55
|
+
/**
|
|
56
|
+
* Update bind group with new entries (e.g., after texture resize)
|
|
57
|
+
*/
|
|
58
|
+
updateBindGroup(newEntries: GPUBindGroupEntry[]): void;
|
|
44
59
|
private getBlendState;
|
|
45
60
|
}
|
|
46
61
|
//#endregion
|
|
62
|
+
//#region src/PassTextureRef.d.ts
|
|
63
|
+
declare const PASS_TEXTURE_REF_SYMBOL: unique symbol;
|
|
64
|
+
declare class PassTextureRef {
|
|
65
|
+
readonly [PASS_TEXTURE_REF_SYMBOL] = true;
|
|
66
|
+
readonly passName: string;
|
|
67
|
+
constructor(passName: string);
|
|
68
|
+
static is(obj: any): obj is PassTextureRef;
|
|
69
|
+
static fromGPUBindingResource(resource: GPUBindingResource): PassTextureRef | null;
|
|
70
|
+
static create(passName: string): PassTextureRef;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
47
73
|
//#region src/index.d.ts
|
|
48
74
|
interface MultiPassDescriptor {
|
|
49
|
-
passes:
|
|
50
|
-
output?: RenderPassOutput;
|
|
75
|
+
passes: RenderPassOptions[];
|
|
51
76
|
}
|
|
52
77
|
interface WGSLRendererOptions {
|
|
53
|
-
|
|
54
|
-
r: number;
|
|
55
|
-
g: number;
|
|
56
|
-
b: number;
|
|
57
|
-
};
|
|
78
|
+
config?: GPUCanvasConfiguration;
|
|
58
79
|
}
|
|
59
80
|
declare class WGSLRenderer {
|
|
60
81
|
canvas: HTMLCanvasElement;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
options?: WGSLRendererOptions | undefined;
|
|
83
|
+
private ctx;
|
|
84
|
+
private device;
|
|
85
|
+
private format;
|
|
86
|
+
private passes;
|
|
65
87
|
private textureManager;
|
|
66
|
-
private backgroundPassAdded;
|
|
67
|
-
private backgroundColor;
|
|
68
|
-
private uniforms;
|
|
69
88
|
private animationFrameId;
|
|
70
|
-
|
|
89
|
+
private isResizing;
|
|
90
|
+
constructor(canvas: HTMLCanvasElement, options?: WGSLRendererOptions | undefined);
|
|
71
91
|
init(): Promise<void>;
|
|
92
|
+
resize(width: number, height: number): Promise<void>;
|
|
93
|
+
getContext(): GPUCanvasContext;
|
|
94
|
+
getDevice(): GPUDevice;
|
|
72
95
|
/**
|
|
73
|
-
*
|
|
96
|
+
* Get texture reference by pass name
|
|
97
|
+
* Returns a PassTextureRef that will resolve to the actual texture at render time
|
|
74
98
|
*/
|
|
75
|
-
|
|
99
|
+
getPassTexture(passName: string): PassTextureRef;
|
|
76
100
|
/**
|
|
77
|
-
*
|
|
101
|
+
* Resolve a PassTextureRef to actual GPUTextureView with validation
|
|
78
102
|
*/
|
|
79
|
-
|
|
103
|
+
private resolveTextureRef;
|
|
80
104
|
/**
|
|
81
|
-
*
|
|
105
|
+
* Get pass by name
|
|
106
|
+
*/
|
|
107
|
+
getPassByName(passName: string): RenderPass | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Add a render pass to the multi-pass pipeline
|
|
82
110
|
*/
|
|
83
|
-
|
|
111
|
+
addPass(descriptor: RenderPassOptions): void;
|
|
84
112
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
113
|
+
* Resolve resource to actual GPU binding resource
|
|
114
|
+
* Handles PassTextureRef by getting the current texture view with validation
|
|
87
115
|
*/
|
|
88
|
-
|
|
116
|
+
private resolveResource;
|
|
89
117
|
/**
|
|
90
|
-
*
|
|
118
|
+
* Update bind groups to resolve current texture references
|
|
119
|
+
* Call this before rendering to ensure all PassTextureRef are resolved
|
|
91
120
|
*/
|
|
92
|
-
|
|
121
|
+
private updateBindGroups;
|
|
93
122
|
/**
|
|
94
123
|
* Create a uniforms
|
|
95
124
|
* @param length The length of the uniform buffer in number of floats
|
|
96
125
|
* @return The uniform object containing the buffer and data array
|
|
97
126
|
*/
|
|
98
127
|
createUniforms(length: number): {
|
|
99
|
-
id: symbol;
|
|
100
128
|
values: Float32Array<ArrayBuffer>;
|
|
101
129
|
apply: () => void;
|
|
102
130
|
getBuffer: () => GPUBuffer;
|
|
103
131
|
};
|
|
104
|
-
getUniformsByID(id: symbol): {
|
|
105
|
-
id: symbol;
|
|
106
|
-
values: Float32Array;
|
|
107
|
-
apply: {
|
|
108
|
-
(): void;
|
|
109
|
-
};
|
|
110
|
-
getBuffer: {
|
|
111
|
-
(): GPUBuffer;
|
|
112
|
-
};
|
|
113
|
-
} | undefined;
|
|
114
132
|
/**
|
|
115
133
|
* Create a sampler
|
|
116
134
|
*/
|
|
117
135
|
createSampler(options?: GPUSamplerDescriptor): GPUSampler;
|
|
118
|
-
|
|
119
|
-
* Create a bind group entry for texture
|
|
120
|
-
*/
|
|
121
|
-
createTextureBinding(texture: GPUTexture): GPUTextureView;
|
|
122
|
-
/**
|
|
123
|
-
* Configure multi-pass rendering
|
|
124
|
-
*/
|
|
125
|
-
setupMultiPass(descriptor: MultiPassDescriptor): void;
|
|
126
|
-
loadTexture(url: string): Promise<{
|
|
136
|
+
loadImageTexture(url: string): Promise<{
|
|
127
137
|
texture: GPUTexture;
|
|
128
138
|
width: number;
|
|
129
139
|
height: number;
|
|
130
140
|
}>;
|
|
131
141
|
renderFrame(): void;
|
|
132
142
|
loopRender(cb?: {
|
|
133
|
-
(): void;
|
|
143
|
+
(t?: number): void;
|
|
134
144
|
}): void;
|
|
135
145
|
stopLoop(): void;
|
|
136
|
-
resize(width: number, height: number): void;
|
|
137
146
|
}
|
|
138
147
|
declare function createWGSLRenderer(cvs: HTMLCanvasElement, options?: WGSLRendererOptions): Promise<WGSLRenderer>;
|
|
139
148
|
//#endregion
|