Skip to content

Commit

Permalink
WebGPURenderer: Add timestamp queries support in WebGPU (#27597)
Browse files Browse the repository at this point in the history
* add timestamp queries support

* add live example

* improve example

* honor async render and move to renderer await functions

* rename to trackTimestamp and cleanupg

* new async standard

* fix tabs

---------
  • Loading branch information
RenaudRohlinger authored Jan 21, 2024
1 parent 13a5874 commit e43ed97
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 14 deletions.
10 changes: 8 additions & 2 deletions examples/jsm/objects/QuadMesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ class QuadMesh {

}

render( renderer ) {
async renderAsync( renderer ) {

renderer.render( this._mesh, _camera );
await renderer.renderAsync( this._mesh, _camera );

}

Expand All @@ -55,6 +55,12 @@ class QuadMesh {

}

get render() {

return this.renderAsync;

}

}

export default QuadMesh;
2 changes: 2 additions & 0 deletions examples/jsm/renderers/common/Backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ class Backend {

// utils

resolveTimeStampAsync( renderContext, type ) { }

hasFeatureAsync( name ) { } // return Boolean

hasFeature( name ) { } // return Boolean
Expand Down
26 changes: 25 additions & 1 deletion examples/jsm/renderers/common/Info.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,20 @@ class Info {
};

this.compute = {
calls: 0
calls: 0,
computeCalls: 0
};

this.memory = {
geometries: 0,
textures: 0
};

this.timestamp = {
compute: 0,
render: 0
};

}

update( object, count, instanceCount ) {
Expand Down Expand Up @@ -54,13 +60,29 @@ class Info {

}

updateTimestamp( type, time ) {

this.timestamp[ type ] += time;

}

resetCompute() {

this.compute.computeCalls = 0;

this.timestamp.compute = 0;

}

reset() {

this.render.drawCalls = 0;
this.render.triangles = 0;
this.render.points = 0;
this.render.lines = 0;

this.timestamp.render = 0;

}

dispose() {
Expand All @@ -72,6 +94,8 @@ class Info {
this.render.calls = 0;
this.compute.calls = 0;

this.timestamp.compute = 0;
this.timestamp.render = 0;
this.memory.geometries = 0;
this.memory.textures = 0;

Expand Down
4 changes: 2 additions & 2 deletions examples/jsm/renderers/common/PostProcessing.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ class PostProcessing {

}

render() {
async render() {

quadMesh.material.fragmentNode = this.outputNode;

quadMesh.render( this.renderer );
await quadMesh.render( this.renderer );

}

Expand Down
24 changes: 22 additions & 2 deletions examples/jsm/renderers/common/Renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ class Renderer {

}

async render( scene, camera ) {
async renderAsync( scene, camera ) {

if ( this._initialized === false ) await this.init();

Expand Down Expand Up @@ -357,6 +357,9 @@ class Renderer {

sceneRef.onAfterRender( this, scene, camera, renderTarget );


await this.backend.resolveTimeStampAsync( renderContext, 'render' );

}

getMaxAnisotropy() {
Expand Down Expand Up @@ -698,7 +701,7 @@ class Renderer {

}

async compute( computeNodes ) {
async computeAsync( computeNodes ) {

if ( this._initialized === false ) await this.init();

Expand All @@ -710,10 +713,12 @@ class Renderer {

this.info.calls ++;
this.info.compute.calls ++;
this.info.compute.computeCalls ++;

nodeFrame.renderId = this.info.calls;

//
if ( this.info.autoReset === true ) this.info.resetCompute();

const backend = this.backend;
const pipelines = this._pipelines;
Expand Down Expand Up @@ -765,6 +770,8 @@ class Renderer {

backend.finishCompute( computeNodes );

await this.backend.resolveTimeStampAsync( computeNodes, 'compute' );

//

nodeFrame.renderId = previousRenderId;
Expand Down Expand Up @@ -1037,6 +1044,19 @@ class Renderer {

}


get compute() {

return this.computeAsync;

}

get render() {

return this.renderAsync;

}

}

export default Renderer;
101 changes: 99 additions & 2 deletions examples/jsm/renderers/webgpu/WebGPUBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class WebGPUBackend extends Backend {

this.parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits;

this.trackTimestamp = ( parameters.trackTimestamp === true );

this.adapter = null;
this.device = null;
this.context = null;
Expand Down Expand Up @@ -323,6 +325,8 @@ class WebGPUBackend extends Backend {

}

this.initTimeStampQuery( renderContext, descriptor );

descriptor.occlusionQuerySet = occlusionQuerySet;

const depthStencilAttachment = descriptor.depthStencilAttachment;
Expand Down Expand Up @@ -490,8 +494,11 @@ class WebGPUBackend extends Backend {

}

this.prepareTimeStampBuffer( renderContext, renderContextData.encoder );

this.device.queue.submit( [ renderContextData.encoder.finish() ] );


//

if ( renderContext.textures !== null ) {
Expand Down Expand Up @@ -725,8 +732,14 @@ class WebGPUBackend extends Backend {

const groupGPU = this.get( computeGroup );

groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( {} );
groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass();

const descriptor = {};

this.initTimeStampQuery( computeGroup, descriptor );

groupGPU.cmdEncoderGPU = this.device.createCommandEncoder();

groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass( descriptor );

}

Expand All @@ -753,6 +766,9 @@ class WebGPUBackend extends Backend {
const groupData = this.get( computeGroup );

groupData.passEncoderGPU.end();

this.prepareTimeStampBuffer( computeGroup, groupData.cmdEncoderGPU );

this.device.queue.submit( [ groupData.cmdEncoderGPU.finish() ] );

}
Expand Down Expand Up @@ -1014,6 +1030,87 @@ class WebGPUBackend extends Backend {

}


initTimeStampQuery( renderContext, descriptor ) {

if ( ! this.hasFeature( GPUFeatureName.TimestampQuery ) || ! this.trackTimestamp ) return;

const renderContextData = this.get( renderContext );

if ( ! renderContextData.timeStampQuerySet ) {

// Create a GPUQuerySet which holds 2 timestamp query results: one for the
// beginning and one for the end of compute pass execution.
const timeStampQuerySet = this.device.createQuerySet( { type: 'timestamp', count: 2 } );

const timestampWrites = {
querySet: timeStampQuerySet,
beginningOfPassWriteIndex: 0, // Write timestamp in index 0 when pass begins.
endOfPassWriteIndex: 1, // Write timestamp in index 1 when pass ends.
};
Object.assign( descriptor, {
timestampWrites,
} );
renderContextData.timeStampQuerySet = timeStampQuerySet;

}

}

// timestamp utils

prepareTimeStampBuffer( renderContext, encoder ) {

if ( ! this.hasFeature( GPUFeatureName.TimestampQuery ) || ! this.trackTimestamp ) return;

const renderContextData = this.get( renderContext );

const size = 2 * BigInt64Array.BYTES_PER_ELEMENT;
const resolveBuffer = this.device.createBuffer( {
size,
usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC,
} );

const resultBuffer = this.device.createBuffer( {
size,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
} );

encoder.resolveQuerySet( renderContextData.timeStampQuerySet, 0, 2, resolveBuffer, 0 );
encoder.copyBufferToBuffer( resolveBuffer, 0, resultBuffer, 0, size );

renderContextData.currentTimeStampQueryBuffer = resultBuffer;

}

async resolveTimeStampAsync( renderContext, type = 'render' ) {

if ( ! this.hasFeature( GPUFeatureName.TimestampQuery ) || ! this.trackTimestamp ) return;

const renderContextData = this.get( renderContext );

// handle timestamp query results

const { currentTimeStampQueryBuffer } = renderContextData;

if ( currentTimeStampQueryBuffer ) {

renderContextData.currentTimeStampQueryBuffer = null;

await currentTimeStampQueryBuffer.mapAsync( GPUMapMode.READ );

const times = new BigUint64Array( currentTimeStampQueryBuffer.getMappedRange() );

const duration = Number( times[ 1 ] - times[ 0 ] ) / 1000000;
// console.log( `Compute ${type} duration: ${Number( times[ 1 ] - times[ 0 ] ) / 1000000}ms` );
this.renderer.info.updateTimestamp( type, duration );

currentTimeStampQueryBuffer.unmap();

}

}

// node builder

createNodeBuilder( object, renderer, scene = null ) {
Expand Down
Loading

0 comments on commit e43ed97

Please sign in to comment.