wgsl-renderer 0.0.3 → 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.zh-CN.md +1 -1
- package/dist/cjs/index.js +6 -2
- package/dist/{types → esm}/index.d.ts +20 -15
- package/dist/esm/index.js +6 -2
- package/package.json +3 -3
- package/dist/types/index.js +0 -427
package/README.zh-CN.md
CHANGED
package/dist/cjs/index.js
CHANGED
|
@@ -311,7 +311,7 @@ var WGSLRenderer = class {
|
|
|
311
311
|
this.passes.forEach((pass) => {
|
|
312
312
|
const finalBindGroupEntries = [];
|
|
313
313
|
pass.passResources.forEach((resource, index) => {
|
|
314
|
-
finalBindGroupEntries.push({
|
|
314
|
+
if (resource) finalBindGroupEntries.push({
|
|
315
315
|
binding: index,
|
|
316
316
|
resource: this.resolveResource(resource)
|
|
317
317
|
});
|
|
@@ -355,7 +355,11 @@ var WGSLRenderer = class {
|
|
|
355
355
|
console.error("Failed to load texture:", err);
|
|
356
356
|
});
|
|
357
357
|
const res = await resp;
|
|
358
|
-
const
|
|
358
|
+
const future = createImageBitmap(await res.blob());
|
|
359
|
+
future.catch((err) => {
|
|
360
|
+
console.error("Failed to load texture:", err);
|
|
361
|
+
});
|
|
362
|
+
const imgBitmap = await future;
|
|
359
363
|
const texture = this.device.createTexture({
|
|
360
364
|
size: [
|
|
361
365
|
imgBitmap.width,
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
+
//#region src/PassTextureRef.d.ts
|
|
2
|
+
declare const PASS_TEXTURE_REF_SYMBOL: unique symbol;
|
|
3
|
+
declare class PassTextureRef {
|
|
4
|
+
readonly [PASS_TEXTURE_REF_SYMBOL] = true;
|
|
5
|
+
readonly passName: string;
|
|
6
|
+
constructor(passName: string);
|
|
7
|
+
static is(obj: any): obj is PassTextureRef;
|
|
8
|
+
static fromGPUBindingResource(resource: GPUBindingResource): PassTextureRef | null;
|
|
9
|
+
static create(passName: string): PassTextureRef;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
1
12
|
//#region src/RenderPass.d.ts
|
|
13
|
+
type BandingResource = GPUBindingResource | PassTextureRef;
|
|
14
|
+
type BindingEntry = {
|
|
15
|
+
binding: number;
|
|
16
|
+
resource: BandingResource;
|
|
17
|
+
};
|
|
2
18
|
interface RenderPassOptions {
|
|
3
19
|
name: string;
|
|
4
20
|
shaderCode: string;
|
|
@@ -13,7 +29,7 @@ interface RenderPassOptions {
|
|
|
13
29
|
a: number;
|
|
14
30
|
};
|
|
15
31
|
blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
|
|
16
|
-
resources
|
|
32
|
+
resources?: BandingResource[];
|
|
17
33
|
view?: GPUTextureView;
|
|
18
34
|
format?: GPUTextureFormat;
|
|
19
35
|
}
|
|
@@ -31,7 +47,7 @@ interface InternalRenderPassDescriptor {
|
|
|
31
47
|
a: number;
|
|
32
48
|
};
|
|
33
49
|
blendMode?: 'additive' | 'alpha' | 'multiply' | 'none';
|
|
34
|
-
bindGroupEntries:
|
|
50
|
+
bindGroupEntries: BindingEntry[];
|
|
35
51
|
view?: GPUTextureView;
|
|
36
52
|
format?: GPUTextureFormat;
|
|
37
53
|
}
|
|
@@ -49,27 +65,16 @@ declare class RenderPass {
|
|
|
49
65
|
blendMode: 'additive' | 'alpha' | 'multiply' | 'none';
|
|
50
66
|
view?: GPUTextureView;
|
|
51
67
|
format?: GPUTextureFormat;
|
|
52
|
-
passResources:
|
|
68
|
+
passResources: BandingResource[];
|
|
53
69
|
private device;
|
|
54
70
|
constructor(descriptor: InternalRenderPassDescriptor, device: GPUDevice, format: GPUTextureFormat, layout: GPUPipelineLayout | 'auto');
|
|
55
71
|
/**
|
|
56
72
|
* Update bind group with new entries (e.g., after texture resize)
|
|
57
73
|
*/
|
|
58
|
-
updateBindGroup(newEntries:
|
|
74
|
+
updateBindGroup(newEntries: BindingEntry[]): void;
|
|
59
75
|
private getBlendState;
|
|
60
76
|
}
|
|
61
77
|
//#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
|
|
73
78
|
//#region src/index.d.ts
|
|
74
79
|
interface MultiPassDescriptor {
|
|
75
80
|
passes: RenderPassOptions[];
|
package/dist/esm/index.js
CHANGED
|
@@ -310,7 +310,7 @@ var WGSLRenderer = class {
|
|
|
310
310
|
this.passes.forEach((pass) => {
|
|
311
311
|
const finalBindGroupEntries = [];
|
|
312
312
|
pass.passResources.forEach((resource, index) => {
|
|
313
|
-
finalBindGroupEntries.push({
|
|
313
|
+
if (resource) finalBindGroupEntries.push({
|
|
314
314
|
binding: index,
|
|
315
315
|
resource: this.resolveResource(resource)
|
|
316
316
|
});
|
|
@@ -354,7 +354,11 @@ var WGSLRenderer = class {
|
|
|
354
354
|
console.error("Failed to load texture:", err);
|
|
355
355
|
});
|
|
356
356
|
const res = await resp;
|
|
357
|
-
const
|
|
357
|
+
const future = createImageBitmap(await res.blob());
|
|
358
|
+
future.catch((err) => {
|
|
359
|
+
console.error("Failed to load texture:", err);
|
|
360
|
+
});
|
|
361
|
+
const imgBitmap = await future;
|
|
358
362
|
const texture = this.device.createTexture({
|
|
359
363
|
size: [
|
|
360
364
|
imgBitmap.width,
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wgsl-renderer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "一个基于WebGPU和WGSL的多通道渲染器",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
7
7
|
"module": "./dist/esm/index.js",
|
|
8
|
-
"
|
|
8
|
+
"typings": "./dist/esm/index.d.ts",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "https://github.com/taiyuuki/wgsl-renderer"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"sideEffects": false,
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
26
|
-
"types": "./dist/index.d.ts",
|
|
26
|
+
"types": "./dist/esm/index.d.ts",
|
|
27
27
|
"import": "./dist/esm/index.js",
|
|
28
28
|
"require": "./dist/cjs/index.js"
|
|
29
29
|
}
|
package/dist/types/index.js
DELETED
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
//#region src/RenderPass.ts
|
|
2
|
-
var RenderPass = class {
|
|
3
|
-
name;
|
|
4
|
-
pipeline;
|
|
5
|
-
bindGroup;
|
|
6
|
-
vertexBuffer;
|
|
7
|
-
clearColor;
|
|
8
|
-
blendMode;
|
|
9
|
-
view;
|
|
10
|
-
format;
|
|
11
|
-
passResources = [];
|
|
12
|
-
device;
|
|
13
|
-
constructor(descriptor, device, format, layout) {
|
|
14
|
-
this.device = device;
|
|
15
|
-
this.name = descriptor.name;
|
|
16
|
-
this.clearColor = descriptor.clearColor || {
|
|
17
|
-
r: 0,
|
|
18
|
-
g: 0,
|
|
19
|
-
b: 0,
|
|
20
|
-
a: 1
|
|
21
|
-
};
|
|
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({
|
|
28
|
-
size: 36,
|
|
29
|
-
usage: GPUBufferUsage.VERTEX,
|
|
30
|
-
mappedAtCreation: true
|
|
31
|
-
});
|
|
32
|
-
new Float32Array(this.vertexBuffer.getMappedRange()).set([
|
|
33
|
-
-1,
|
|
34
|
-
-1,
|
|
35
|
-
0,
|
|
36
|
-
3,
|
|
37
|
-
-1,
|
|
38
|
-
0,
|
|
39
|
-
-1,
|
|
40
|
-
3,
|
|
41
|
-
0
|
|
42
|
-
]);
|
|
43
|
-
this.vertexBuffer.unmap();
|
|
44
|
-
const vertexEntryPoint = descriptor.entryPoints?.vertex || "vs_main";
|
|
45
|
-
const fragmentEntryPoint = descriptor.entryPoints?.fragment || "fs_main";
|
|
46
|
-
this.pipeline = this.device.createRenderPipeline({
|
|
47
|
-
layout,
|
|
48
|
-
vertex: {
|
|
49
|
-
module,
|
|
50
|
-
entryPoint: vertexEntryPoint,
|
|
51
|
-
buffers: [{
|
|
52
|
-
arrayStride: 12,
|
|
53
|
-
attributes: [{
|
|
54
|
-
shaderLocation: 0,
|
|
55
|
-
offset: 0,
|
|
56
|
-
format: "float32x3"
|
|
57
|
-
}]
|
|
58
|
-
}]
|
|
59
|
-
},
|
|
60
|
-
fragment: {
|
|
61
|
-
module,
|
|
62
|
-
entryPoint: fragmentEntryPoint,
|
|
63
|
-
targets: [{
|
|
64
|
-
format: actualFormat,
|
|
65
|
-
blend: this.getBlendState()
|
|
66
|
-
}]
|
|
67
|
-
},
|
|
68
|
-
primitive: { topology: "triangle-list" }
|
|
69
|
-
});
|
|
70
|
-
this.bindGroup = null;
|
|
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.bindGroup = this.device.createBindGroup({
|
|
78
|
-
layout: bindGroupLayout,
|
|
79
|
-
entries: newEntries
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
getBlendState() {
|
|
83
|
-
switch (this.blendMode) {
|
|
84
|
-
case "none": return;
|
|
85
|
-
case "alpha": return {
|
|
86
|
-
color: {
|
|
87
|
-
srcFactor: "src-alpha",
|
|
88
|
-
dstFactor: "one-minus-src-alpha",
|
|
89
|
-
operation: "add"
|
|
90
|
-
},
|
|
91
|
-
alpha: {
|
|
92
|
-
srcFactor: "one",
|
|
93
|
-
dstFactor: "one-minus-src-alpha",
|
|
94
|
-
operation: "add"
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
case "additive": return {
|
|
98
|
-
color: {
|
|
99
|
-
srcFactor: "src-alpha",
|
|
100
|
-
dstFactor: "one",
|
|
101
|
-
operation: "add"
|
|
102
|
-
},
|
|
103
|
-
alpha: {
|
|
104
|
-
srcFactor: "one",
|
|
105
|
-
dstFactor: "one",
|
|
106
|
-
operation: "add"
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
case "multiply": return {
|
|
110
|
-
color: {
|
|
111
|
-
srcFactor: "src",
|
|
112
|
-
dstFactor: "dst",
|
|
113
|
-
operation: "add"
|
|
114
|
-
},
|
|
115
|
-
alpha: {
|
|
116
|
-
srcFactor: "one",
|
|
117
|
-
dstFactor: "one-minus-src-alpha",
|
|
118
|
-
operation: "add"
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
default: return;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
//#endregion
|
|
127
|
-
//#region src/TextureManager.ts
|
|
128
|
-
var TextureManager = class {
|
|
129
|
-
textures = /* @__PURE__ */ new Map();
|
|
130
|
-
device;
|
|
131
|
-
width;
|
|
132
|
-
height;
|
|
133
|
-
constructor(device, width, height) {
|
|
134
|
-
this.device = device;
|
|
135
|
-
this.width = width;
|
|
136
|
-
this.height = height;
|
|
137
|
-
}
|
|
138
|
-
createTexture(name, format) {
|
|
139
|
-
if (this.textures.has(name)) this.textures.get(name).destroy();
|
|
140
|
-
const texture = this.device.createTexture({
|
|
141
|
-
size: [this.width, this.height],
|
|
142
|
-
format: format || "bgra8unorm",
|
|
143
|
-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
|
|
144
|
-
});
|
|
145
|
-
this.textures.set(name, texture);
|
|
146
|
-
return texture;
|
|
147
|
-
}
|
|
148
|
-
getTexture(name) {
|
|
149
|
-
return this.textures.get(name);
|
|
150
|
-
}
|
|
151
|
-
resize(width, height) {
|
|
152
|
-
if (width === this.width && height === this.height) return;
|
|
153
|
-
this.width = width;
|
|
154
|
-
this.height = height;
|
|
155
|
-
this.textures.forEach((texture) => {
|
|
156
|
-
texture.destroy();
|
|
157
|
-
});
|
|
158
|
-
this.textures.clear();
|
|
159
|
-
}
|
|
160
|
-
destroy() {
|
|
161
|
-
this.textures.forEach((texture) => texture.destroy());
|
|
162
|
-
this.textures.clear();
|
|
163
|
-
}
|
|
164
|
-
getPixelSize() {
|
|
165
|
-
return {
|
|
166
|
-
width: this.width,
|
|
167
|
-
height: this.height
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
};
|
|
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
|
-
|
|
196
|
-
//#endregion
|
|
197
|
-
//#region src/index.ts
|
|
198
|
-
var WGSLRenderer = class {
|
|
199
|
-
ctx;
|
|
200
|
-
device;
|
|
201
|
-
format;
|
|
202
|
-
passes = [];
|
|
203
|
-
textureManager;
|
|
204
|
-
animationFrameId = null;
|
|
205
|
-
isResizing = false;
|
|
206
|
-
constructor(canvas, options) {
|
|
207
|
-
this.canvas = canvas;
|
|
208
|
-
this.options = options;
|
|
209
|
-
if (!navigator.gpu) throw new Error("WebGPU is not supported in this browser.");
|
|
210
|
-
this.ctx = canvas.getContext("webgpu");
|
|
211
|
-
}
|
|
212
|
-
async init() {
|
|
213
|
-
this.device = await (await navigator.gpu.requestAdapter()).requestDevice();
|
|
214
|
-
this.format = navigator.gpu.getPreferredCanvasFormat();
|
|
215
|
-
const config = Object.assign({
|
|
216
|
-
device: this.device,
|
|
217
|
-
format: this.format,
|
|
218
|
-
alphaMode: "opaque"
|
|
219
|
-
}, this.options?.config);
|
|
220
|
-
this.ctx.configure(config);
|
|
221
|
-
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
222
|
-
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
223
|
-
this.textureManager = new TextureManager(this.device, canvasWidth, canvasHeight);
|
|
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;
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Get texture reference by pass name
|
|
248
|
-
* Returns a PassTextureRef that will resolve to the actual texture at render time
|
|
249
|
-
*/
|
|
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);
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Add a render pass to the multi-pass pipeline
|
|
273
|
-
*/
|
|
274
|
-
addPass(descriptor) {
|
|
275
|
-
const finalBindGroupEntries = [];
|
|
276
|
-
descriptor.resources?.forEach((resource, index) => {
|
|
277
|
-
finalBindGroupEntries.push({
|
|
278
|
-
binding: index,
|
|
279
|
-
resource
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
const internalDescriptor = {
|
|
283
|
-
name: descriptor.name,
|
|
284
|
-
shaderCode: descriptor.shaderCode,
|
|
285
|
-
entryPoints: descriptor.entryPoints,
|
|
286
|
-
clearColor: descriptor.clearColor,
|
|
287
|
-
blendMode: descriptor.blendMode,
|
|
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 ?? [];
|
|
295
|
-
this.passes.push(pass);
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Resolve resource to actual GPU binding resource
|
|
299
|
-
* Handles PassTextureRef by getting the current texture view with validation
|
|
300
|
-
*/
|
|
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
|
-
});
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Create a uniforms
|
|
323
|
-
* @param length The length of the uniform buffer in number of floats
|
|
324
|
-
* @return The uniform object containing the buffer and data array
|
|
325
|
-
*/
|
|
326
|
-
createUniforms(length) {
|
|
327
|
-
const values = new Float32Array(Math.ceil(length));
|
|
328
|
-
const buffer = this.device.createBuffer({
|
|
329
|
-
size: values.byteLength,
|
|
330
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
331
|
-
});
|
|
332
|
-
return {
|
|
333
|
-
values,
|
|
334
|
-
apply: () => {
|
|
335
|
-
this.device.queue.writeBuffer(buffer, 0, values.buffer, values.byteOffset, values.byteLength);
|
|
336
|
-
},
|
|
337
|
-
getBuffer: () => buffer
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Create a sampler
|
|
342
|
-
*/
|
|
343
|
-
createSampler(options) {
|
|
344
|
-
return this.device.createSampler(Object.assign({
|
|
345
|
-
magFilter: "linear",
|
|
346
|
-
minFilter: "linear",
|
|
347
|
-
addressModeU: "clamp-to-edge",
|
|
348
|
-
addressModeV: "clamp-to-edge"
|
|
349
|
-
}, options));
|
|
350
|
-
}
|
|
351
|
-
async loadImageTexture(url) {
|
|
352
|
-
const resp = fetch(url);
|
|
353
|
-
resp.catch((err) => {
|
|
354
|
-
console.error("Failed to load texture:", err);
|
|
355
|
-
});
|
|
356
|
-
const res = await resp;
|
|
357
|
-
const imgBitmap = await createImageBitmap(await res.blob());
|
|
358
|
-
const texture = this.device.createTexture({
|
|
359
|
-
size: [
|
|
360
|
-
imgBitmap.width,
|
|
361
|
-
imgBitmap.height,
|
|
362
|
-
1
|
|
363
|
-
],
|
|
364
|
-
format: "rgba8unorm",
|
|
365
|
-
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
366
|
-
});
|
|
367
|
-
this.device.queue.copyExternalImageToTexture({ source: imgBitmap }, { texture }, [imgBitmap.width, imgBitmap.height]);
|
|
368
|
-
return {
|
|
369
|
-
texture,
|
|
370
|
-
width: imgBitmap.width,
|
|
371
|
-
height: imgBitmap.height
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
renderFrame() {
|
|
375
|
-
if (this.passes.length === 0) return;
|
|
376
|
-
this.updateBindGroups();
|
|
377
|
-
const commandEncoder = this.device.createCommandEncoder();
|
|
378
|
-
for (let i = 0; i < this.passes.length; i++) {
|
|
379
|
-
const pass = this.passes[i];
|
|
380
|
-
let loadOp = "load";
|
|
381
|
-
const isLast = i === this.passes.length - 1;
|
|
382
|
-
if (isLast) loadOp = "clear";
|
|
383
|
-
let renderTarget;
|
|
384
|
-
if (pass.view) renderTarget = pass.view;
|
|
385
|
-
else if (isLast) renderTarget = this.ctx.getCurrentTexture().createView();
|
|
386
|
-
else {
|
|
387
|
-
const textureName = `pass_${i}_output`;
|
|
388
|
-
let texture = this.textureManager.getTexture(textureName);
|
|
389
|
-
if (!texture) texture = this.textureManager.createTexture(textureName, "rgba16float");
|
|
390
|
-
renderTarget = texture.createView();
|
|
391
|
-
}
|
|
392
|
-
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
393
|
-
view: renderTarget,
|
|
394
|
-
loadOp,
|
|
395
|
-
storeOp: "store",
|
|
396
|
-
clearValue: pass.clearColor
|
|
397
|
-
}] });
|
|
398
|
-
renderPass.setPipeline(pass.pipeline);
|
|
399
|
-
if (pass.bindGroup) renderPass.setBindGroup(0, pass.bindGroup);
|
|
400
|
-
renderPass.setVertexBuffer(0, pass.vertexBuffer);
|
|
401
|
-
renderPass.draw(3, 1, 0, 0);
|
|
402
|
-
renderPass.end();
|
|
403
|
-
}
|
|
404
|
-
this.device.queue.submit([commandEncoder.finish()]);
|
|
405
|
-
}
|
|
406
|
-
loopRender(cb) {
|
|
407
|
-
this.animationFrameId = requestAnimationFrame((t) => {
|
|
408
|
-
cb?.(t);
|
|
409
|
-
this.renderFrame();
|
|
410
|
-
this.loopRender(cb);
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
stopLoop() {
|
|
414
|
-
if (this.animationFrameId !== null) {
|
|
415
|
-
cancelAnimationFrame(this.animationFrameId);
|
|
416
|
-
this.animationFrameId = null;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
async function createWGSLRenderer(cvs, options) {
|
|
421
|
-
const renderer = new WGSLRenderer(cvs, options);
|
|
422
|
-
await renderer.init();
|
|
423
|
-
return renderer;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
//#endregion
|
|
427
|
-
export { createWGSLRenderer };
|