wgsl-renderer 0.3.4 → 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 -13
- package/dist/esm/index.d.ts +24 -1
- package/dist/esm/index.js +145 -14
- 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
|
}
|
|
@@ -710,30 +788,45 @@ var WGSLRenderer = class {
|
|
|
710
788
|
if (this.passes.length === 0) return;
|
|
711
789
|
const enabledPasses = this.getEnabledPasses();
|
|
712
790
|
if (enabledPasses.length === 0) return;
|
|
791
|
+
this.hasClearedCanvasThisFrame = false;
|
|
713
792
|
this.updateBindGroups();
|
|
714
793
|
const commandEncoder = this.device.createCommandEncoder();
|
|
794
|
+
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
795
|
+
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
715
796
|
for (let i = 0; i < enabledPasses.length; i++) {
|
|
716
797
|
const pass = enabledPasses[i];
|
|
717
798
|
let loadOp = "load";
|
|
718
|
-
|
|
719
|
-
let renderTarget;
|
|
720
|
-
let resolveTarget;
|
|
721
|
-
const canvasTexture = this.ctx.getCurrentTexture();
|
|
799
|
+
const isFirst = i === 0;
|
|
722
800
|
const isLastPass = i === enabledPasses.length - 1;
|
|
723
|
-
if (
|
|
724
|
-
|
|
725
|
-
|
|
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 {
|
|
726
817
|
const textureName = `pass_${i}_output`;
|
|
727
818
|
let texture = this.textureManager.getTexture(textureName);
|
|
728
819
|
if (!texture) texture = this.textureManager.createTexture(textureName, pass.format || this.format);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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;
|
|
733
827
|
}
|
|
734
828
|
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
735
|
-
view:
|
|
736
|
-
resolveTarget,
|
|
829
|
+
view: renderTargetView,
|
|
737
830
|
loadOp,
|
|
738
831
|
storeOp: "store",
|
|
739
832
|
clearValue: pass.clearColor
|
|
@@ -764,6 +857,44 @@ var WGSLRenderer = class {
|
|
|
764
857
|
this.stopLoop();
|
|
765
858
|
this.passes = [];
|
|
766
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;
|
|
767
898
|
}
|
|
768
899
|
};
|
|
769
900
|
async function createWGSLRenderer(cvs, options) {
|
|
@@ -774,6 +905,7 @@ async function createWGSLRenderer(cvs, options) {
|
|
|
774
905
|
|
|
775
906
|
//#endregion
|
|
776
907
|
exports.PassTextureRef = PassTextureRef;
|
|
908
|
+
exports.RenderMode = RenderMode;
|
|
777
909
|
exports.WGSLRenderer = WGSLRenderer;
|
|
778
910
|
exports.createSamplingView = createSamplingView;
|
|
779
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
|
}
|
|
@@ -709,30 +787,45 @@ var WGSLRenderer = class {
|
|
|
709
787
|
if (this.passes.length === 0) return;
|
|
710
788
|
const enabledPasses = this.getEnabledPasses();
|
|
711
789
|
if (enabledPasses.length === 0) return;
|
|
790
|
+
this.hasClearedCanvasThisFrame = false;
|
|
712
791
|
this.updateBindGroups();
|
|
713
792
|
const commandEncoder = this.device.createCommandEncoder();
|
|
793
|
+
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
794
|
+
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
714
795
|
for (let i = 0; i < enabledPasses.length; i++) {
|
|
715
796
|
const pass = enabledPasses[i];
|
|
716
797
|
let loadOp = "load";
|
|
717
|
-
|
|
718
|
-
let renderTarget;
|
|
719
|
-
let resolveTarget;
|
|
720
|
-
const canvasTexture = this.ctx.getCurrentTexture();
|
|
798
|
+
const isFirst = i === 0;
|
|
721
799
|
const isLastPass = i === enabledPasses.length - 1;
|
|
722
|
-
if (
|
|
723
|
-
|
|
724
|
-
|
|
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 {
|
|
725
816
|
const textureName = `pass_${i}_output`;
|
|
726
817
|
let texture = this.textureManager.getTexture(textureName);
|
|
727
818
|
if (!texture) texture = this.textureManager.createTexture(textureName, pass.format || this.format);
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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;
|
|
732
826
|
}
|
|
733
827
|
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
734
|
-
view:
|
|
735
|
-
resolveTarget,
|
|
828
|
+
view: renderTargetView,
|
|
736
829
|
loadOp,
|
|
737
830
|
storeOp: "store",
|
|
738
831
|
clearValue: pass.clearColor
|
|
@@ -763,6 +856,44 @@ var WGSLRenderer = class {
|
|
|
763
856
|
this.stopLoop();
|
|
764
857
|
this.passes = [];
|
|
765
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;
|
|
766
897
|
}
|
|
767
898
|
};
|
|
768
899
|
async function createWGSLRenderer(cvs, options) {
|
|
@@ -772,4 +903,4 @@ async function createWGSLRenderer(cvs, options) {
|
|
|
772
903
|
}
|
|
773
904
|
|
|
774
905
|
//#endregion
|
|
775
|
-
export { PassTextureRef, WGSLRenderer, createSamplingView, createWGSLRenderer };
|
|
906
|
+
export { PassTextureRef, RenderMode, WGSLRenderer, createSamplingView, createWGSLRenderer };
|