Skip to content

Commit 117ab93

Browse files
authored
Merge pull request #8008 from processing/webgpu-fbo
Framebuffer support on WebGPU renderer
2 parents b65080f + 724b41a commit 117ab93

File tree

29 files changed

+2337
-801
lines changed

29 files changed

+2337
-801
lines changed

.github/workflows/ci-test.yml

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,52 @@ on:
1111

1212
jobs:
1313
test:
14-
runs-on: ubuntu-latest
14+
strategy:
15+
matrix:
16+
include:
17+
- os: ubuntu-latest
18+
browser: chrome
19+
# - os: windows-latest
20+
# browser: chrome
21+
22+
runs-on: ${{ matrix.os }}
1523

1624
steps:
17-
- uses: actions/checkout@v1
25+
- uses: actions/checkout@v4
26+
1827
- name: Use Node.js 20.x
19-
uses: actions/setup-node@v1
28+
uses: actions/setup-node@v4
2029
with:
2130
node-version: 20.x
31+
32+
- name: Verify Chrome (Ubuntu)
33+
if: matrix.os == 'ubuntu-latest' && matrix.browser == 'chrome'
34+
run: |
35+
google-chrome --version
36+
37+
- name: Verify Chrome (Windows)
38+
if: matrix.os == 'windows-latest' && matrix.browser == 'chrome'
39+
run: |
40+
& "C:\Program Files\Google\Chrome\Application\chrome.exe" --version
41+
2242
- name: Get node modules
2343
run: npm ci
2444
env:
2545
CI: true
26-
- name: build and test
27-
run: npm test
46+
47+
#- name: Build and test (Ubuntu)
48+
# if: matrix.os == 'windows-latest'
49+
# run: npm test -- --project=unit-tests-webgpu
50+
# env:
51+
# CI: true
52+
53+
- name: Build and test (Ubuntu)
54+
if: matrix.os == 'ubuntu-latest'
55+
run: npm test -- --project=unit-tests
2856
env:
2957
CI: true
30-
- name: report test coverage
58+
59+
- name: Report test coverage
3160
run: bash <(curl -s https://codecov.io/bash) -f coverage/coverage-final.json
3261
env:
3362
CI: true

preview/index.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
p.setup = async function () {
3232
await p.createCanvas(400, 400, p.WEBGPU);
33+
fbo = p.createFramebuffer();
3334

3435
tex = p.createImage(100, 100);
3536
tex.loadPixels();
@@ -43,6 +44,10 @@
4344
}
4445
}
4546
tex.updatePixels();
47+
fbo.draw(() => {
48+
p.imageMode(p.CENTER);
49+
p.image(tex, 0, 0, p.width, p.height);
50+
});
4651

4752
sh = p.baseMaterialShader().modify({
4853
uniforms: {
@@ -87,7 +92,7 @@
8792
0, //p.width/3 * p.sin(t * 0.9 + i * Math.E + 0.2),
8893
p.width/3 * p.sin(t * 1.2 + i * Math.E + 0.3),
8994
)
90-
p.texture(tex)
95+
p.texture(fbo)
9196
p.sphere(30);
9297
p.pop();
9398
}

src/core/main.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class p5 {
5858
this._curElement = null;
5959
this._elements = [];
6060
this._glAttributes = null;
61+
this._webgpuAttributes = null;
6162
this._requestAnimId = 0;
6263
this._isGlobal = false;
6364
this._loop = true;
@@ -468,11 +469,11 @@ for (const k in constants) {
468469
* If `setup()` is declared `async` (e.g. `async function setup()`),
469470
* execution pauses at each `await` until its promise resolves.
470471
* For example, `font = await loadFont(...)` waits for the font asset
471-
* to load because `loadFont()` function returns a promise, and the await
472+
* to load because `loadFont()` function returns a promise, and the await
472473
* keyword means the program will wait for the promise to resolve.
473474
* This ensures that all assets are fully loaded before the sketch continues.
474475
475-
*
476+
*
476477
* loading assets.
477478
*
478479
* Note: `setup()` doesn’t have to be declared, but it’s common practice to do so.

src/core/p5.Renderer3D.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as constants from "../core/constants";
2+
import { Graphics } from "../core/p5.Graphics";
23
import { Renderer } from './p5.Renderer';
34
import GeometryBuilder from "../webgl/GeometryBuilder";
45
import { Matrix } from "../math/p5.Matrix";
@@ -350,6 +351,80 @@ export class Renderer3D extends Renderer {
350351
};
351352
}
352353

354+
//This is helper function to reset the context anytime the attributes
355+
//are changed with setAttributes()
356+
357+
async _resetContext(options, callback, ctor = Renderer3D) {
358+
const w = this.width;
359+
const h = this.height;
360+
const defaultId = this.canvas.id;
361+
const isPGraphics = this._pInst instanceof Graphics;
362+
363+
// Preserve existing position and styles before recreation
364+
const prevStyle = {
365+
position: this.canvas.style.position,
366+
top: this.canvas.style.top,
367+
left: this.canvas.style.left,
368+
};
369+
370+
if (isPGraphics) {
371+
// Handle PGraphics: remove and recreate the canvas
372+
const pg = this._pInst;
373+
pg.canvas.parentNode.removeChild(pg.canvas);
374+
pg.canvas = document.createElement("canvas");
375+
const node = pg._pInst._userNode || document.body;
376+
node.appendChild(pg.canvas);
377+
Element.call(pg, pg.canvas, pg._pInst);
378+
// Restore previous width and height
379+
pg.width = w;
380+
pg.height = h;
381+
} else {
382+
// Handle main canvas: remove and recreate it
383+
let c = this.canvas;
384+
if (c) {
385+
c.parentNode.removeChild(c);
386+
}
387+
c = document.createElement("canvas");
388+
c.id = defaultId;
389+
// Attach the new canvas to the correct parent node
390+
if (this._pInst._userNode) {
391+
this._pInst._userNode.appendChild(c);
392+
} else {
393+
document.body.appendChild(c);
394+
}
395+
this._pInst.canvas = c;
396+
this.canvas = c;
397+
398+
// Restore the saved position
399+
this.canvas.style.position = prevStyle.position;
400+
this.canvas.style.top = prevStyle.top;
401+
this.canvas.style.left = prevStyle.left;
402+
}
403+
404+
const renderer = new ctor(
405+
this._pInst,
406+
w,
407+
h,
408+
!isPGraphics,
409+
this._pInst.canvas
410+
);
411+
this._pInst._renderer = renderer;
412+
413+
renderer._applyDefaults();
414+
415+
if (renderer.contextReady) {
416+
await renderer.contextReady
417+
}
418+
419+
if (typeof callback === "function") {
420+
//setTimeout with 0 forces the task to the back of the queue, this ensures that
421+
//we finish switching out the renderer
422+
setTimeout(() => {
423+
callback.apply(window._renderer, options);
424+
}, 0);
425+
}
426+
}
427+
353428
remove() {
354429
this.wrappedElt.remove();
355430
this.wrappedElt = null;

src/image/pixels.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,7 @@ function pixels(p5, fn){
933933
*/
934934
fn.loadPixels = function(...args) {
935935
// p5._validateParameters('loadPixels', args);
936-
this._renderer.loadPixels();
936+
return this._renderer.loadPixels();
937937
};
938938

939939
/**

src/webgl/3d_primitives.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1869,7 +1869,7 @@ function primitives3D(p5, fn){
18691869
if (typeof args[4] === 'undefined') {
18701870
// Use the retained mode for drawing rectangle,
18711871
// if args for rounding rectangle is not provided by user.
1872-
const perPixelLighting = this._pInst._glAttributes.perPixelLighting;
1872+
const perPixelLighting = this._pInst._glAttributes?.perPixelLighting;
18731873
const detailX = args[4] || (perPixelLighting ? 1 : 24);
18741874
const detailY = args[5] || (perPixelLighting ? 1 : 16);
18751875
const gid = `rect|${detailX}|${detailY}`;

0 commit comments

Comments
 (0)