wgsl-renderer 0.3.3 → 0.4.0
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/dist/cjs/index.js +145 -17
- package/dist/esm/index.d.ts +24 -1
- package/dist/esm/index.js +145 -18
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -314,6 +314,8 @@ var TextureManager = class {
|
|
|
314
314
|
device;
|
|
315
315
|
width;
|
|
316
316
|
height;
|
|
317
|
+
renderTarget = null;
|
|
318
|
+
outputTexture = null;
|
|
317
319
|
constructor(device, width, height) {
|
|
318
320
|
this.device = device;
|
|
319
321
|
this.width = width;
|
|
@@ -348,9 +350,64 @@ var TextureManager = class {
|
|
|
348
350
|
setTexture(name, texture) {
|
|
349
351
|
this.textures.set(name, texture);
|
|
350
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* 获取或创建渲染目标纹理
|
|
355
|
+
* 所有渲染到这里的内容会再复制到 canvas 和输出纹理
|
|
356
|
+
* 必须支持 COPY_SRC 以便复制到 canvas 和输出纹理
|
|
357
|
+
*/
|
|
358
|
+
getOrCreateRenderTarget(width, height, format) {
|
|
359
|
+
if (!this.renderTarget || this.renderTarget.width !== width || this.renderTarget.height !== height) {
|
|
360
|
+
this.renderTarget?.destroy();
|
|
361
|
+
this.renderTarget = this.device.createTexture({
|
|
362
|
+
size: [
|
|
363
|
+
width,
|
|
364
|
+
height,
|
|
365
|
+
1
|
|
366
|
+
],
|
|
367
|
+
format,
|
|
368
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.TEXTURE_BINDING
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return this.renderTarget;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* 获取当前渲染目标纹理
|
|
375
|
+
*/
|
|
376
|
+
getRenderTarget() {
|
|
377
|
+
return this.renderTarget;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* 获取或创建输出纹理
|
|
381
|
+
* 用于视频导出,始终包含最新的渲染结果
|
|
382
|
+
*/
|
|
383
|
+
getOrCreateOutputTexture(width, height, format) {
|
|
384
|
+
if (!this.outputTexture || this.outputTexture.width !== width || this.outputTexture.height !== height) {
|
|
385
|
+
this.outputTexture?.destroy();
|
|
386
|
+
this.outputTexture = this.device.createTexture({
|
|
387
|
+
size: [
|
|
388
|
+
width,
|
|
389
|
+
height,
|
|
390
|
+
1
|
|
391
|
+
],
|
|
392
|
+
format,
|
|
393
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.TEXTURE_BINDING
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return this.outputTexture;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* 获取当前输出纹理
|
|
400
|
+
*/
|
|
401
|
+
getOutputTexture() {
|
|
402
|
+
return this.outputTexture;
|
|
403
|
+
}
|
|
351
404
|
destroy() {
|
|
352
405
|
this.textures.forEach((texture) => texture.destroy());
|
|
353
406
|
this.textures.clear();
|
|
407
|
+
this.renderTarget?.destroy();
|
|
408
|
+
this.renderTarget = null;
|
|
409
|
+
this.outputTexture?.destroy();
|
|
410
|
+
this.outputTexture = null;
|
|
354
411
|
}
|
|
355
412
|
getPixelSize() {
|
|
356
413
|
return {
|
|
@@ -398,6 +455,11 @@ function createSamplingView(texture, ref) {
|
|
|
398
455
|
|
|
399
456
|
//#endregion
|
|
400
457
|
//#region src/index.ts
|
|
458
|
+
var RenderMode = /* @__PURE__ */ function(RenderMode$1) {
|
|
459
|
+
RenderMode$1["NORMAL"] = "normal";
|
|
460
|
+
RenderMode$1["EXPORT"] = "export";
|
|
461
|
+
return RenderMode$1;
|
|
462
|
+
}(RenderMode || {});
|
|
401
463
|
var WGSLRenderer = class {
|
|
402
464
|
ctx;
|
|
403
465
|
device;
|
|
@@ -406,6 +468,9 @@ var WGSLRenderer = class {
|
|
|
406
468
|
textureManager;
|
|
407
469
|
animationFrameId = null;
|
|
408
470
|
isResizing = false;
|
|
471
|
+
renderMode = RenderMode.NORMAL;
|
|
472
|
+
readBuffer = null;
|
|
473
|
+
hasClearedCanvasThisFrame = false;
|
|
409
474
|
constructor(canvas, options) {
|
|
410
475
|
this.canvas = canvas;
|
|
411
476
|
this.options = options;
|
|
@@ -442,6 +507,19 @@ var WGSLRenderer = class {
|
|
|
442
507
|
this.textureManager.resize(width, height);
|
|
443
508
|
this.isResizing = false;
|
|
444
509
|
}
|
|
510
|
+
/**
|
|
511
|
+
* 设置渲染模式
|
|
512
|
+
* @param mode NORMAL模式渲染到canvas和outputTexture,EXPORT模式只渲染到outputTexture
|
|
513
|
+
*/
|
|
514
|
+
setRenderMode(mode) {
|
|
515
|
+
this.renderMode = mode;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* 获取当前渲染模式
|
|
519
|
+
*/
|
|
520
|
+
getRenderMode() {
|
|
521
|
+
return this.renderMode;
|
|
522
|
+
}
|
|
445
523
|
getContext() {
|
|
446
524
|
return this.ctx;
|
|
447
525
|
}
|
|
@@ -456,10 +534,6 @@ var WGSLRenderer = class {
|
|
|
456
534
|
* @param options Optional texture creation options for when the texture needs to be created
|
|
457
535
|
*/
|
|
458
536
|
getPassTexture(passName, options) {
|
|
459
|
-
const pass = this.passes.find((pass$1) => pass$1.name === passName);
|
|
460
|
-
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
461
|
-
const f = options?.format ?? "rgba8unorm";
|
|
462
|
-
if (pass.format && f !== pass.format) throw new Error(`Format must be set to ${pass.format}, pass name: '${passName}'`);
|
|
463
537
|
return PassTextureRef.create(passName, options);
|
|
464
538
|
}
|
|
465
539
|
/**
|
|
@@ -714,30 +788,45 @@ var WGSLRenderer = class {
|
|
|
714
788
|
if (this.passes.length === 0) return;
|
|
715
789
|
const enabledPasses = this.getEnabledPasses();
|
|
716
790
|
if (enabledPasses.length === 0) return;
|
|
791
|
+
this.hasClearedCanvasThisFrame = false;
|
|
717
792
|
this.updateBindGroups();
|
|
718
793
|
const commandEncoder = this.device.createCommandEncoder();
|
|
794
|
+
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
795
|
+
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
719
796
|
for (let i = 0; i < enabledPasses.length; i++) {
|
|
720
797
|
const pass = enabledPasses[i];
|
|
721
798
|
let loadOp = "load";
|
|
722
|
-
|
|
723
|
-
let renderTarget;
|
|
724
|
-
let resolveTarget;
|
|
725
|
-
const canvasTexture = this.ctx.getCurrentTexture();
|
|
799
|
+
const isFirst = i === 0;
|
|
726
800
|
const isLastPass = i === enabledPasses.length - 1;
|
|
727
|
-
if (
|
|
728
|
-
|
|
729
|
-
|
|
801
|
+
if (isFirst) loadOp = "clear";
|
|
802
|
+
let renderTargetView;
|
|
803
|
+
let isRenderingToCanvas = false;
|
|
804
|
+
if (pass.view) renderTargetView = pass.view;
|
|
805
|
+
else if (this.renderMode === RenderMode.EXPORT && (pass.renderToCanvas || isLastPass)) {
|
|
806
|
+
renderTargetView = this.textureManager.getOrCreateOutputTexture(canvasWidth, canvasHeight, this.format).createView();
|
|
807
|
+
if (this.hasClearedCanvasThisFrame) {
|
|
808
|
+
if (loadOp === "clear") loadOp = "load";
|
|
809
|
+
} else {
|
|
810
|
+
if (loadOp === "load") loadOp = "clear";
|
|
811
|
+
this.hasClearedCanvasThisFrame = true;
|
|
812
|
+
}
|
|
813
|
+
} else if (pass.renderToCanvas || isLastPass) {
|
|
814
|
+
renderTargetView = this.ctx.getCurrentTexture().createView();
|
|
815
|
+
isRenderingToCanvas = true;
|
|
816
|
+
} else {
|
|
730
817
|
const textureName = `pass_${i}_output`;
|
|
731
818
|
let texture = this.textureManager.getTexture(textureName);
|
|
732
819
|
if (!texture) texture = this.textureManager.createTexture(textureName, pass.format || this.format);
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
820
|
+
renderTargetView = texture.createView();
|
|
821
|
+
}
|
|
822
|
+
if (isRenderingToCanvas) if (this.hasClearedCanvasThisFrame) {
|
|
823
|
+
if (loadOp === "clear") loadOp = "load";
|
|
824
|
+
} else {
|
|
825
|
+
if (loadOp === "load") loadOp = "clear";
|
|
826
|
+
this.hasClearedCanvasThisFrame = true;
|
|
737
827
|
}
|
|
738
828
|
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
739
|
-
view:
|
|
740
|
-
resolveTarget,
|
|
829
|
+
view: renderTargetView,
|
|
741
830
|
loadOp,
|
|
742
831
|
storeOp: "store",
|
|
743
832
|
clearValue: pass.clearColor
|
|
@@ -768,6 +857,44 @@ var WGSLRenderer = class {
|
|
|
768
857
|
this.stopLoop();
|
|
769
858
|
this.passes = [];
|
|
770
859
|
this.textureManager.destroy();
|
|
860
|
+
this.readBuffer?.destroy();
|
|
861
|
+
this.readBuffer = null;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* 快速捕获当前帧的像素数据
|
|
865
|
+
* 从outputTexture读取,不经过canvas,用于视频导出
|
|
866
|
+
* @returns Uint8Array格式的RGBA像素数据
|
|
867
|
+
*/
|
|
868
|
+
async captureFrameFast() {
|
|
869
|
+
const outputTexture = this.textureManager.getOutputTexture();
|
|
870
|
+
if (!outputTexture) throw new Error("Output texture not available. Please render a frame first.");
|
|
871
|
+
const width = outputTexture.width;
|
|
872
|
+
const height = outputTexture.height;
|
|
873
|
+
const bytesPerRow = Math.ceil(width * 4 / 256) * 256;
|
|
874
|
+
const bufferSize = bytesPerRow * height;
|
|
875
|
+
if (!this.readBuffer || this.readBuffer.size !== bufferSize) {
|
|
876
|
+
this.readBuffer?.destroy();
|
|
877
|
+
this.readBuffer = this.device.createBuffer({
|
|
878
|
+
size: bufferSize,
|
|
879
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
const commandEncoder = this.device.createCommandEncoder();
|
|
883
|
+
commandEncoder.copyTextureToBuffer({ texture: outputTexture }, {
|
|
884
|
+
buffer: this.readBuffer,
|
|
885
|
+
bytesPerRow
|
|
886
|
+
}, [width, height]);
|
|
887
|
+
this.device.queue.submit([commandEncoder.finish()]);
|
|
888
|
+
await this.readBuffer.mapAsync(GPUMapMode.READ);
|
|
889
|
+
const mappedBuffer = new Uint8Array(this.readBuffer.getMappedRange().slice(0));
|
|
890
|
+
this.readBuffer.unmap();
|
|
891
|
+
const pixelData = new Uint8Array(width * height * 4);
|
|
892
|
+
for (let row = 0; row < height; row++) {
|
|
893
|
+
const srcOffset = row * bytesPerRow;
|
|
894
|
+
const dstOffset = row * width * 4;
|
|
895
|
+
pixelData.set(mappedBuffer.subarray(srcOffset, srcOffset + width * 4), dstOffset);
|
|
896
|
+
}
|
|
897
|
+
return pixelData;
|
|
771
898
|
}
|
|
772
899
|
};
|
|
773
900
|
async function createWGSLRenderer(cvs, options) {
|
|
@@ -778,6 +905,7 @@ async function createWGSLRenderer(cvs, options) {
|
|
|
778
905
|
|
|
779
906
|
//#endregion
|
|
780
907
|
exports.PassTextureRef = PassTextureRef;
|
|
908
|
+
exports.RenderMode = RenderMode;
|
|
781
909
|
exports.WGSLRenderer = WGSLRenderer;
|
|
782
910
|
exports.createSamplingView = createSamplingView;
|
|
783
911
|
exports.createWGSLRenderer = createWGSLRenderer;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -140,6 +140,11 @@ declare class RenderPass {
|
|
|
140
140
|
}
|
|
141
141
|
//#endregion
|
|
142
142
|
//#region src/index.d.ts
|
|
143
|
+
declare enum RenderMode {
|
|
144
|
+
NORMAL = "normal",
|
|
145
|
+
// 正常模式:渲染到canvas和outputTexture
|
|
146
|
+
EXPORT = "export",
|
|
147
|
+
}
|
|
143
148
|
interface WGSLRendererOptions {
|
|
144
149
|
config?: Partial<GPUCanvasConfiguration>;
|
|
145
150
|
}
|
|
@@ -153,9 +158,21 @@ declare class WGSLRenderer {
|
|
|
153
158
|
private textureManager;
|
|
154
159
|
private animationFrameId;
|
|
155
160
|
private isResizing;
|
|
161
|
+
private renderMode;
|
|
162
|
+
private readBuffer;
|
|
163
|
+
private hasClearedCanvasThisFrame;
|
|
156
164
|
constructor(canvas: HTMLCanvasElement, options?: WGSLRendererOptions | undefined);
|
|
157
165
|
init(): Promise<void>;
|
|
158
166
|
resize(width: number, height: number): Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* 设置渲染模式
|
|
169
|
+
* @param mode NORMAL模式渲染到canvas和outputTexture,EXPORT模式只渲染到outputTexture
|
|
170
|
+
*/
|
|
171
|
+
setRenderMode(mode: RenderMode): void;
|
|
172
|
+
/**
|
|
173
|
+
* 获取当前渲染模式
|
|
174
|
+
*/
|
|
175
|
+
getRenderMode(): RenderMode;
|
|
159
176
|
getContext(): GPUCanvasContext;
|
|
160
177
|
getDevice(): GPUDevice;
|
|
161
178
|
/**
|
|
@@ -264,7 +281,13 @@ declare class WGSLRenderer {
|
|
|
264
281
|
}): void;
|
|
265
282
|
stopLoop(): void;
|
|
266
283
|
reset(): void;
|
|
284
|
+
/**
|
|
285
|
+
* 快速捕获当前帧的像素数据
|
|
286
|
+
* 从outputTexture读取,不经过canvas,用于视频导出
|
|
287
|
+
* @returns Uint8Array格式的RGBA像素数据
|
|
288
|
+
*/
|
|
289
|
+
captureFrameFast(): Promise<Uint8Array>;
|
|
267
290
|
}
|
|
268
291
|
declare function createWGSLRenderer(cvs: HTMLCanvasElement, options?: WGSLRendererOptions): Promise<WGSLRenderer>;
|
|
269
292
|
//#endregion
|
|
270
|
-
export { BindingResource, PassTextureRef, RenderPassOptions, WGSLRenderer, createSamplingView, createWGSLRenderer };
|
|
293
|
+
export { BindingResource, PassTextureRef, RenderMode, RenderPassOptions, WGSLRenderer, createSamplingView, createWGSLRenderer };
|
package/dist/esm/index.js
CHANGED
|
@@ -313,6 +313,8 @@ var TextureManager = class {
|
|
|
313
313
|
device;
|
|
314
314
|
width;
|
|
315
315
|
height;
|
|
316
|
+
renderTarget = null;
|
|
317
|
+
outputTexture = null;
|
|
316
318
|
constructor(device, width, height) {
|
|
317
319
|
this.device = device;
|
|
318
320
|
this.width = width;
|
|
@@ -347,9 +349,64 @@ var TextureManager = class {
|
|
|
347
349
|
setTexture(name, texture) {
|
|
348
350
|
this.textures.set(name, texture);
|
|
349
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* 获取或创建渲染目标纹理
|
|
354
|
+
* 所有渲染到这里的内容会再复制到 canvas 和输出纹理
|
|
355
|
+
* 必须支持 COPY_SRC 以便复制到 canvas 和输出纹理
|
|
356
|
+
*/
|
|
357
|
+
getOrCreateRenderTarget(width, height, format) {
|
|
358
|
+
if (!this.renderTarget || this.renderTarget.width !== width || this.renderTarget.height !== height) {
|
|
359
|
+
this.renderTarget?.destroy();
|
|
360
|
+
this.renderTarget = this.device.createTexture({
|
|
361
|
+
size: [
|
|
362
|
+
width,
|
|
363
|
+
height,
|
|
364
|
+
1
|
|
365
|
+
],
|
|
366
|
+
format,
|
|
367
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.TEXTURE_BINDING
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return this.renderTarget;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* 获取当前渲染目标纹理
|
|
374
|
+
*/
|
|
375
|
+
getRenderTarget() {
|
|
376
|
+
return this.renderTarget;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* 获取或创建输出纹理
|
|
380
|
+
* 用于视频导出,始终包含最新的渲染结果
|
|
381
|
+
*/
|
|
382
|
+
getOrCreateOutputTexture(width, height, format) {
|
|
383
|
+
if (!this.outputTexture || this.outputTexture.width !== width || this.outputTexture.height !== height) {
|
|
384
|
+
this.outputTexture?.destroy();
|
|
385
|
+
this.outputTexture = this.device.createTexture({
|
|
386
|
+
size: [
|
|
387
|
+
width,
|
|
388
|
+
height,
|
|
389
|
+
1
|
|
390
|
+
],
|
|
391
|
+
format,
|
|
392
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.TEXTURE_BINDING
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
return this.outputTexture;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* 获取当前输出纹理
|
|
399
|
+
*/
|
|
400
|
+
getOutputTexture() {
|
|
401
|
+
return this.outputTexture;
|
|
402
|
+
}
|
|
350
403
|
destroy() {
|
|
351
404
|
this.textures.forEach((texture) => texture.destroy());
|
|
352
405
|
this.textures.clear();
|
|
406
|
+
this.renderTarget?.destroy();
|
|
407
|
+
this.renderTarget = null;
|
|
408
|
+
this.outputTexture?.destroy();
|
|
409
|
+
this.outputTexture = null;
|
|
353
410
|
}
|
|
354
411
|
getPixelSize() {
|
|
355
412
|
return {
|
|
@@ -397,6 +454,11 @@ function createSamplingView(texture, ref) {
|
|
|
397
454
|
|
|
398
455
|
//#endregion
|
|
399
456
|
//#region src/index.ts
|
|
457
|
+
var RenderMode = /* @__PURE__ */ function(RenderMode$1) {
|
|
458
|
+
RenderMode$1["NORMAL"] = "normal";
|
|
459
|
+
RenderMode$1["EXPORT"] = "export";
|
|
460
|
+
return RenderMode$1;
|
|
461
|
+
}(RenderMode || {});
|
|
400
462
|
var WGSLRenderer = class {
|
|
401
463
|
ctx;
|
|
402
464
|
device;
|
|
@@ -405,6 +467,9 @@ var WGSLRenderer = class {
|
|
|
405
467
|
textureManager;
|
|
406
468
|
animationFrameId = null;
|
|
407
469
|
isResizing = false;
|
|
470
|
+
renderMode = RenderMode.NORMAL;
|
|
471
|
+
readBuffer = null;
|
|
472
|
+
hasClearedCanvasThisFrame = false;
|
|
408
473
|
constructor(canvas, options) {
|
|
409
474
|
this.canvas = canvas;
|
|
410
475
|
this.options = options;
|
|
@@ -441,6 +506,19 @@ var WGSLRenderer = class {
|
|
|
441
506
|
this.textureManager.resize(width, height);
|
|
442
507
|
this.isResizing = false;
|
|
443
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* 设置渲染模式
|
|
511
|
+
* @param mode NORMAL模式渲染到canvas和outputTexture,EXPORT模式只渲染到outputTexture
|
|
512
|
+
*/
|
|
513
|
+
setRenderMode(mode) {
|
|
514
|
+
this.renderMode = mode;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* 获取当前渲染模式
|
|
518
|
+
*/
|
|
519
|
+
getRenderMode() {
|
|
520
|
+
return this.renderMode;
|
|
521
|
+
}
|
|
444
522
|
getContext() {
|
|
445
523
|
return this.ctx;
|
|
446
524
|
}
|
|
@@ -455,10 +533,6 @@ var WGSLRenderer = class {
|
|
|
455
533
|
* @param options Optional texture creation options for when the texture needs to be created
|
|
456
534
|
*/
|
|
457
535
|
getPassTexture(passName, options) {
|
|
458
|
-
const pass = this.passes.find((pass$1) => pass$1.name === passName);
|
|
459
|
-
if (!pass) throw new Error(`Cannot find pass named '${passName}'. Available passes: [${this.passes.map((p) => p.name).join(", ")}]`);
|
|
460
|
-
const f = options?.format ?? "rgba8unorm";
|
|
461
|
-
if (pass.format && f !== pass.format) throw new Error(`Format must be set to ${pass.format}, pass name: '${passName}'`);
|
|
462
536
|
return PassTextureRef.create(passName, options);
|
|
463
537
|
}
|
|
464
538
|
/**
|
|
@@ -713,30 +787,45 @@ var WGSLRenderer = class {
|
|
|
713
787
|
if (this.passes.length === 0) return;
|
|
714
788
|
const enabledPasses = this.getEnabledPasses();
|
|
715
789
|
if (enabledPasses.length === 0) return;
|
|
790
|
+
this.hasClearedCanvasThisFrame = false;
|
|
716
791
|
this.updateBindGroups();
|
|
717
792
|
const commandEncoder = this.device.createCommandEncoder();
|
|
793
|
+
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
794
|
+
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
718
795
|
for (let i = 0; i < enabledPasses.length; i++) {
|
|
719
796
|
const pass = enabledPasses[i];
|
|
720
797
|
let loadOp = "load";
|
|
721
|
-
|
|
722
|
-
let renderTarget;
|
|
723
|
-
let resolveTarget;
|
|
724
|
-
const canvasTexture = this.ctx.getCurrentTexture();
|
|
798
|
+
const isFirst = i === 0;
|
|
725
799
|
const isLastPass = i === enabledPasses.length - 1;
|
|
726
|
-
if (
|
|
727
|
-
|
|
728
|
-
|
|
800
|
+
if (isFirst) loadOp = "clear";
|
|
801
|
+
let renderTargetView;
|
|
802
|
+
let isRenderingToCanvas = false;
|
|
803
|
+
if (pass.view) renderTargetView = pass.view;
|
|
804
|
+
else if (this.renderMode === RenderMode.EXPORT && (pass.renderToCanvas || isLastPass)) {
|
|
805
|
+
renderTargetView = this.textureManager.getOrCreateOutputTexture(canvasWidth, canvasHeight, this.format).createView();
|
|
806
|
+
if (this.hasClearedCanvasThisFrame) {
|
|
807
|
+
if (loadOp === "clear") loadOp = "load";
|
|
808
|
+
} else {
|
|
809
|
+
if (loadOp === "load") loadOp = "clear";
|
|
810
|
+
this.hasClearedCanvasThisFrame = true;
|
|
811
|
+
}
|
|
812
|
+
} else if (pass.renderToCanvas || isLastPass) {
|
|
813
|
+
renderTargetView = this.ctx.getCurrentTexture().createView();
|
|
814
|
+
isRenderingToCanvas = true;
|
|
815
|
+
} else {
|
|
729
816
|
const textureName = `pass_${i}_output`;
|
|
730
817
|
let texture = this.textureManager.getTexture(textureName);
|
|
731
818
|
if (!texture) texture = this.textureManager.createTexture(textureName, pass.format || this.format);
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
819
|
+
renderTargetView = texture.createView();
|
|
820
|
+
}
|
|
821
|
+
if (isRenderingToCanvas) if (this.hasClearedCanvasThisFrame) {
|
|
822
|
+
if (loadOp === "clear") loadOp = "load";
|
|
823
|
+
} else {
|
|
824
|
+
if (loadOp === "load") loadOp = "clear";
|
|
825
|
+
this.hasClearedCanvasThisFrame = true;
|
|
736
826
|
}
|
|
737
827
|
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
738
|
-
view:
|
|
739
|
-
resolveTarget,
|
|
828
|
+
view: renderTargetView,
|
|
740
829
|
loadOp,
|
|
741
830
|
storeOp: "store",
|
|
742
831
|
clearValue: pass.clearColor
|
|
@@ -767,6 +856,44 @@ var WGSLRenderer = class {
|
|
|
767
856
|
this.stopLoop();
|
|
768
857
|
this.passes = [];
|
|
769
858
|
this.textureManager.destroy();
|
|
859
|
+
this.readBuffer?.destroy();
|
|
860
|
+
this.readBuffer = null;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* 快速捕获当前帧的像素数据
|
|
864
|
+
* 从outputTexture读取,不经过canvas,用于视频导出
|
|
865
|
+
* @returns Uint8Array格式的RGBA像素数据
|
|
866
|
+
*/
|
|
867
|
+
async captureFrameFast() {
|
|
868
|
+
const outputTexture = this.textureManager.getOutputTexture();
|
|
869
|
+
if (!outputTexture) throw new Error("Output texture not available. Please render a frame first.");
|
|
870
|
+
const width = outputTexture.width;
|
|
871
|
+
const height = outputTexture.height;
|
|
872
|
+
const bytesPerRow = Math.ceil(width * 4 / 256) * 256;
|
|
873
|
+
const bufferSize = bytesPerRow * height;
|
|
874
|
+
if (!this.readBuffer || this.readBuffer.size !== bufferSize) {
|
|
875
|
+
this.readBuffer?.destroy();
|
|
876
|
+
this.readBuffer = this.device.createBuffer({
|
|
877
|
+
size: bufferSize,
|
|
878
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
const commandEncoder = this.device.createCommandEncoder();
|
|
882
|
+
commandEncoder.copyTextureToBuffer({ texture: outputTexture }, {
|
|
883
|
+
buffer: this.readBuffer,
|
|
884
|
+
bytesPerRow
|
|
885
|
+
}, [width, height]);
|
|
886
|
+
this.device.queue.submit([commandEncoder.finish()]);
|
|
887
|
+
await this.readBuffer.mapAsync(GPUMapMode.READ);
|
|
888
|
+
const mappedBuffer = new Uint8Array(this.readBuffer.getMappedRange().slice(0));
|
|
889
|
+
this.readBuffer.unmap();
|
|
890
|
+
const pixelData = new Uint8Array(width * height * 4);
|
|
891
|
+
for (let row = 0; row < height; row++) {
|
|
892
|
+
const srcOffset = row * bytesPerRow;
|
|
893
|
+
const dstOffset = row * width * 4;
|
|
894
|
+
pixelData.set(mappedBuffer.subarray(srcOffset, srcOffset + width * 4), dstOffset);
|
|
895
|
+
}
|
|
896
|
+
return pixelData;
|
|
770
897
|
}
|
|
771
898
|
};
|
|
772
899
|
async function createWGSLRenderer(cvs, options) {
|
|
@@ -776,4 +903,4 @@ async function createWGSLRenderer(cvs, options) {
|
|
|
776
903
|
}
|
|
777
904
|
|
|
778
905
|
//#endregion
|
|
779
|
-
export { PassTextureRef, WGSLRenderer, createSamplingView, createWGSLRenderer };
|
|
906
|
+
export { PassTextureRef, RenderMode, WGSLRenderer, createSamplingView, createWGSLRenderer };
|