Skip to content

Commit

Permalink
Get Async WebGL2 Reads Working
Browse files Browse the repository at this point in the history
Thanks to the advice here!
mrdoob/three.js#22779 (comment)
  • Loading branch information
zalo committed Nov 9, 2021
1 parent c4ac0c5 commit 9696c2e
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 12 deletions.
65 changes: 61 additions & 4 deletions src/MultiTargetGPUComputationRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,14 @@ class MultiTargetGPUComputationRenderer {

};

this.addPass = function (variable, dependencies, computeFragmentShader) {
this.addPass = function (variable, dependencies, computeFragmentShader, asyncReadParams = {}) {

let pass = {
variable: variable,
material: this.createShaderMaterial(computeFragmentShader),
dependencies: dependencies
};
Object.assign(pass, pass, asyncReadParams); // Read in async read params

this.passes.push(pass);

Expand Down Expand Up @@ -265,7 +266,7 @@ class MultiTargetGPUComputationRenderer {

};

this.compute = function () {
this.compute = function (triggerAsyncRead) {

for (let i = 0, il = this.passes.length; i < il; i++) {

Expand All @@ -289,7 +290,7 @@ class MultiTargetGPUComputationRenderer {
}

// Performs the computation for this variable
this.doRenderTarget(pass.material, pass.variable.renderTargets[nextTextureIndex]);
this.doRenderTarget(pass.material, pass.variable.renderTargets[nextTextureIndex], triggerAsyncRead, pass);

pass.variable.currentTextureIndex = nextTextureIndex;

Expand Down Expand Up @@ -435,19 +436,75 @@ class MultiTargetGPUComputationRenderer {

};

this.doRenderTarget = function (material, output) {
this.doRenderTarget = function (material, output, triggerReadback, pass) {

const currentRenderTarget = renderer.getRenderTarget();

mesh.material = material;
renderer.setRenderTarget(output);
renderer.render(scene, camera);

// If this is the time to trigger an async read, do it!
if (triggerReadback && pass.buffer) {
readPixelsAsync(renderer.getContext(), pass.x, pass.y,
pass.w, pass.h, pass.format, pass.type, pass.buffer).then(
pass.callback, (err) => { console.error(err); });
}

mesh.material = passThruShader;

renderer.setRenderTarget(currentRenderTarget);

};

// async readback

function clientWaitAsync(gl, sync, flags, interval_ms) {
return new Promise((resolve, reject) => {
function test() {
const res = gl.clientWaitSync(sync, flags, 0);
if (res == gl.WAIT_FAILED) {
reject();
return;
}
if (res == gl.TIMEOUT_EXPIRED) {
setTimeout(test, interval_ms);
return;
}
resolve();
}
test();
});
}

async function getBufferSubDataAsync(
gl, target, buffer, srcByteOffset, dstBuffer,
/* optional */ dstOffset, /* optional */ length) {
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
gl.flush();

await clientWaitAsync(gl, sync, 0, 10);
gl.deleteSync(sync);

gl.bindBuffer(target, buffer);
gl.getBufferSubData(target, srcByteOffset, dstBuffer, dstOffset, length);
gl.bindBuffer(target, null);
}

async function readPixelsAsync(gl, x, y, w, h, format, type, dest) {
const buf = gl.createBuffer();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
gl.bufferData(gl.PIXEL_PACK_BUFFER, dest.byteLength, gl.STREAM_READ);
gl.readPixels(x, y, w, h, format, type, 0);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);

await getBufferSubDataAsync(gl, gl.PIXEL_PACK_BUFFER, buf, 0, dest);

gl.deleteBuffer(buf);
return dest;
}


// Shaders

function getPassThroughVertexShader() {
Expand Down
19 changes: 15 additions & 4 deletions src/SoftbodyGPU.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,17 @@ export class SoftBodyGPU {
pos.xz += F.xz * min(1.0, dt * friction);
}
pc_fragColor = vec4(pos, 0.0 );
}`);
}`,
{ // Async Texture Readback Parameters
x: 0,
y: 0,
w: this.texDim,
h: this.texDim,
format: 6408,//gl.RGBA = 6408
type: 5126,//gl.FLOAT = 5126, gl.UINT8 = 5121
buffer: this.tetPositionsArray,
callback: this.endFrame.bind(this)
});
this.collisionPass.material.uniforms['dt' ] = { value: this.physicsParams.dt };
this.collisionPass.material.uniforms['friction'] = { value: this.physicsParams.friction };
this.collisionPass.material.uniforms['grabId' ] = { value: -1 };
Expand Down Expand Up @@ -450,7 +460,7 @@ export class SoftBodyGPU {

// ----------------- begin solver -----------------------------------------------------

simulate(dt, physicsParams) {
simulate(dt, physicsParams, triggerAsyncRead = false) {
physicsParams.dt = dt;

// First, upload the new shader uniforms to the GPU
Expand Down Expand Up @@ -480,7 +490,7 @@ export class SoftBodyGPU {
}

// Run a substep!
this.gpuCompute.compute();
this.gpuCompute.compute(triggerAsyncRead);
}

// ----------------- end solver -----------------------------------------------------
Expand All @@ -498,7 +508,8 @@ export class SoftBodyGPU {

updateEdgeMesh() {
// Read tetrahedron positions back from the GPU
this.readToCPU(this.pos, this.tetPositionsArray);
// This is now called automatically after the last position update step
//this.readToCPU(this.pos, this.tetPositionsArray);

let positionIndex = 0;
const positions = this.edgeMesh.geometry.attributes.position.array;
Expand Down
9 changes: 5 additions & 4 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,15 @@ export default class Main {
let dt = (this.physicsParams.timeScale * this.physicsParams.timeStep) / this.physicsParams.numSubsteps;
for (let step = 0; step < this.physicsParams.numSubsteps; step++) {
for (let i = 0; i < this.physicsScene.softBodies.length; i++) {
this.physicsScene.softBodies[i].simulate(dt, this.physicsParams);
this.physicsScene.softBodies[i].simulate(dt, this.physicsParams, step == this.physicsParams.numSubsteps - 1);
}
}

// Update their visual representations
for (let i = 0; i < this.physicsScene.softBodies.length; i++) {
this.physicsScene.softBodies[i].endFrame();
}
// This now happens automatically and asynchronously on the last simulation step
//for (let i = 0; i < this.physicsScene.softBodies.length; i++) {
// this.physicsScene.softBodies[i].endFrame();
//}

// Render the scene and update the framerate counter
this.world.controls.update();
Expand Down

0 comments on commit 9696c2e

Please sign in to comment.