Skip to content

Commit 606b135

Browse files
committed
Only use a main canvas framebuffer when necessary
1 parent 4cba57e commit 606b135

2 files changed

Lines changed: 128 additions & 32 deletions

File tree

src/webgl/p5.Framebuffer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class Framebuffer {
6262
this.renderer.framebuffers.add(this);
6363

6464
this._isClipApplied = false;
65+
this._useCanvasFormat = settings._useCanvasFormat || false;
6566

6667
this.dirty = { colorTexture: false, depthTexture: false };
6768

src/webgpu/p5.RendererWebGPU.js

Lines changed: 127 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ function rendererWebGPU(p5, fn) {
6262
this._pixelReadCanvas = null;
6363
this._pixelReadCtx = null;
6464
this.mainFramebuffer = null;
65+
this._framePromotedToFramebuffer = false;
6566

6667
this.finalCamera = new Camera(this);
6768
this.finalCamera._computeCameraDefaultSettings();
@@ -107,7 +108,7 @@ function rendererWebGPU(p5, fn) {
107108

108109
// TODO disablable stencil
109110
this.depthFormat = 'depth24plus-stencil8';
110-
this.mainFramebuffer = this.createFramebuffer();
111+
this.mainFramebuffer = this.createFramebuffer({ _useCanvasFormat: true });
111112
this._updateSize();
112113
this._update();
113114
}
@@ -801,7 +802,8 @@ function rendererWebGPU(p5, fn) {
801802
}
802803

803804
_resetBuffersBeforeDraw() {
804-
if (!this.activeFramebuffer()) {
805+
// Only use mainFramebuffer if promotion has occurred
806+
if (!this.activeFramebuffer() && this._framePromotedToFramebuffer) {
805807
this.mainFramebuffer.begin();
806808
}
807809
const commandEncoder = this.device.createCommandEncoder();
@@ -830,6 +832,64 @@ function rendererWebGPU(p5, fn) {
830832
this._hasPendingDraws = true;
831833
}
832834

835+
/**
836+
* Promotes the current frame to use mainFramebuffer.
837+
* Copies current canvas content to mainFramebuffer, then switches to rendering there.
838+
* @private
839+
*/
840+
_promoteToFramebuffer() {
841+
// Already promoted this frame
842+
if (this._framePromotedToFramebuffer) {
843+
return;
844+
}
845+
846+
// Already drawing to a custom framebuffer, no promotion needed
847+
if (this.activeFramebuffer()) {
848+
return;
849+
}
850+
851+
// Flush any pending draws to canvas first
852+
this.flushDraw();
853+
854+
// Mark as promoted
855+
this._framePromotedToFramebuffer = true;
856+
857+
// Get current canvas texture
858+
const canvasTexture = this.drawingContext.getCurrentTexture();
859+
860+
// Ensure mainFramebuffer matches canvas size
861+
if (this.mainFramebuffer.width !== this.width ||
862+
this.mainFramebuffer.height !== this.height) {
863+
this.mainFramebuffer.resize(this.width, this.height);
864+
}
865+
866+
// Copy canvas texture to mainFramebuffer
867+
const commandEncoder = this.device.createCommandEncoder();
868+
commandEncoder.copyTextureToTexture(
869+
{
870+
texture: canvasTexture,
871+
origin: { x: 0, y: 0, z: 0 },
872+
mipLevel: 0,
873+
},
874+
{
875+
texture: this.mainFramebuffer.colorTexture,
876+
origin: { x: 0, y: 0, z: 0 },
877+
mipLevel: 0,
878+
},
879+
{
880+
width: Math.ceil(this.width * this._pixelDensity),
881+
height: Math.ceil(this.height * this._pixelDensity),
882+
depthOrArrayLayers: 1,
883+
}
884+
);
885+
886+
this._pendingCommandEncoders.push(commandEncoder.finish());
887+
this._hasPendingDraws = true;
888+
889+
// Activate mainFramebuffer for subsequent draws
890+
this.mainFramebuffer.begin();
891+
}
892+
833893
//////////////////////////////////////////////
834894
// Geometry buffer pool management
835895
//////////////////////////////////////////////
@@ -1025,24 +1085,29 @@ function rendererWebGPU(p5, fn) {
10251085

10261086
async finishDraw() {
10271087
this.flushDraw();
1088+
10281089
const states = [];
1029-
while (this.activeFramebuffers.length > 0) {
1030-
const fbo = this.activeFramebuffers.pop();
1031-
states.unshift({ fbo, diff: { ...this.states } });
1032-
}
1033-
this.flushDraw();
10341090

1035-
// this._pInst.background('red');
1036-
this._pInst.push();
1037-
this.states.setValue('enableLighting', false);
1038-
this.states.setValue('activeImageLight', null);
1039-
this._pInst.setCamera(this.finalCamera);
1040-
this._pInst.shader(this._getBlitShader());
1041-
this._pInst.resetMatrix();
1042-
this._pInst.imageMode(this._pInst.CENTER);
1043-
this._pInst.image(this.mainFramebuffer, 0, 0);
1044-
this._pInst.pop();
1045-
this.flushDraw();
1091+
// Only blit if we promoted to framebuffer this frame
1092+
if (this._framePromotedToFramebuffer) {
1093+
while (this.activeFramebuffers.length > 0) {
1094+
const fbo = this.activeFramebuffers.pop();
1095+
states.unshift({ fbo, diff: { ...this.states } });
1096+
}
1097+
this.flushDraw();
1098+
1099+
// this._pInst.background('red');
1100+
this._pInst.push();
1101+
this.states.setValue('enableLighting', false);
1102+
this.states.setValue('activeImageLight', null);
1103+
this._pInst.setCamera(this.finalCamera);
1104+
this._pInst.shader(this._getBlitShader());
1105+
this._pInst.resetMatrix();
1106+
this._pInst.imageMode(this._pInst.CENTER);
1107+
this._pInst.image(this.mainFramebuffer, 0, 0);
1108+
this._pInst.pop();
1109+
this.flushDraw();
1110+
}
10461111

10471112
// Return all uniform buffers to their pools
10481113
this._returnUniformBuffersToPool();
@@ -1063,12 +1128,19 @@ function rendererWebGPU(p5, fn) {
10631128
}
10641129
this._retiredBuffers = [];
10651130

1066-
for (const { fbo, diff } of states) {
1067-
fbo.begin();
1068-
for (const key in diff) {
1069-
this.states.setValue(key, diff[key]);
1131+
if (this._framePromotedToFramebuffer) {
1132+
for (const { fbo, diff } of states) {
1133+
if (fbo !== this.mainFramebuffer || !this._framePromotedToFramebuffer) {
1134+
fbo.begin();
1135+
}
1136+
for (const key in diff) {
1137+
this.states.setValue(key, diff[key]);
1138+
}
10701139
}
10711140
}
1141+
1142+
// Reset promotion state for next frame
1143+
this._framePromotedToFramebuffer = false;
10721144
}
10731145

10741146
//////////////////////////////////////////////
@@ -2203,7 +2275,10 @@ function rendererWebGPU(p5, fn) {
22032275
// Create non-multisampled texture for texture binding (always needed)
22042276
const colorTextureDescriptor = {
22052277
...baseDescriptor,
2206-
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC,
2278+
usage: GPUTextureUsage.RENDER_ATTACHMENT |
2279+
GPUTextureUsage.TEXTURE_BINDING |
2280+
GPUTextureUsage.COPY_SRC |
2281+
(framebuffer._useCanvasFormat ? GPUTextureUsage.COPY_DST : 0),
22072282
sampleCount: 1,
22082283
};
22092284
framebuffer.colorTexture = this.device.createTexture(colorTextureDescriptor);
@@ -2324,6 +2399,11 @@ function rendererWebGPU(p5, fn) {
23242399
} else if (framebuffer.format === constants.HALF_FLOAT) {
23252400
return framebuffer.channels === RGBA ? 'rgba16float' : 'rgba16float';
23262401
} else {
2402+
// Framebuffer with _useCanvasFormat should match canvas presentation format
2403+
if (framebuffer._useCanvasFormat) {
2404+
return this.presentationFormat;
2405+
}
2406+
// Other framebuffers use standard RGBA format
23272407
return framebuffer.channels === RGBA ? 'rgba8unorm' : 'rgba8unorm';
23282408
}
23292409
}
@@ -2413,22 +2493,28 @@ function rendererWebGPU(p5, fn) {
24132493
const mappedRange = stagingBuffer.getMappedRange(0, bufferSize);
24142494

24152495
// If alignment was needed, extract the actual pixel data
2496+
let result;
24162497
if (alignedBytesPerRow === unalignedBytesPerRow) {
2417-
const result = new Uint8Array(mappedRange.slice(0, width * height * bytesPerPixel));
2498+
result = new Uint8Array(mappedRange.slice(0, width * height * bytesPerPixel));
24182499
stagingBuffer.unmap();
2419-
return result;
24202500
} else {
24212501
// Need to extract pixel data from aligned buffer
2422-
const result = new Uint8Array(width * height * bytesPerPixel);
2502+
result = new Uint8Array(width * height * bytesPerPixel);
24232503
const mappedData = new Uint8Array(mappedRange);
24242504
for (let y = 0; y < height; y++) {
24252505
const srcOffset = y * alignedBytesPerRow;
24262506
const dstOffset = y * unalignedBytesPerRow;
24272507
result.set(mappedData.subarray(srcOffset, srcOffset + unalignedBytesPerRow), dstOffset);
24282508
}
24292509
stagingBuffer.unmap();
2430-
return result;
24312510
}
2511+
2512+
// Convert BGRA to RGBA if reading from canvas-format framebuffer on BGRA systems
2513+
if (framebuffer._useCanvasFormat && this.presentationFormat === 'bgra8unorm') {
2514+
this._convertBGRtoRGB(result);
2515+
}
2516+
2517+
return result;
24322518
}
24332519

24342520
async readFramebufferPixel(framebuffer, x, y) {
@@ -2556,25 +2642,34 @@ function rendererWebGPU(p5, fn) {
25562642
//////////////////////////////////////////////
25572643

25582644
_convertBGRtoRGB(pixelData) {
2559-
// Convert BGR to RGB by swapping red and blue channels
25602645
for (let i = 0; i < pixelData.length; i += 4) {
2561-
const temp = pixelData[i]; // Store red
2562-
pixelData[i] = pixelData[i + 2]; // Red = Blue
2563-
pixelData[i + 2] = temp; // Blue = Red
2564-
// Green (i + 1) and Alpha (i + 3) stay the same
2646+
const temp = pixelData[i];
2647+
pixelData[i] = pixelData[i + 2];
2648+
pixelData[i + 2] = temp;
25652649
}
25662650
return pixelData;
25672651
}
25682652

25692653
async loadPixels() {
2654+
this._promoteToFramebuffer();
25702655
await this.mainFramebuffer.loadPixels();
25712656
this.pixels = this.mainFramebuffer.pixels.slice();
25722657
}
25732658

25742659
async get(x, y, w, h) {
2660+
this._promoteToFramebuffer();
25752661
return this.mainFramebuffer.get(x, y, w, h);
25762662
}
25772663

2664+
filter(...args) {
2665+
// If no custom framebuffer is active, promote to mainFramebuffer
2666+
if (!this.activeFramebuffer()) {
2667+
this._promoteToFramebuffer();
2668+
}
2669+
2670+
return super.filter(...args);
2671+
}
2672+
25782673
getNoiseShaderSnippet() {
25792674
return noiseWGSL;
25802675
}

0 commit comments

Comments
 (0)