Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebGPURenderer: StorageBufferNode Support reading external elements in the WebGL Backend #27661

Merged
merged 37 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
04c41e8
init pbo
RenaudRohlinger Feb 1, 2024
b4b82eb
remove flag
RenaudRohlinger Feb 1, 2024
3a0b66a
regenerate live example
RenaudRohlinger Feb 1, 2024
62238bc
single buffer and alternate for example
RenaudRohlinger Feb 1, 2024
64dee72
cleanup, no idea about circ dep
RenaudRohlinger Feb 1, 2024
9d32e79
test fix circular
RenaudRohlinger Feb 1, 2024
e089954
cleanup
RenaudRohlinger Feb 1, 2024
9738dd4
moved pbo management to GLSLNodeBuilder
RenaudRohlinger Feb 2, 2024
9cbafeb
fix screenshots
RenaudRohlinger Feb 2, 2024
8072cc5
Merge remote-tracking branch 'upstream/dev' into utsubo/feat/storage_pbo
RenaudRohlinger Feb 2, 2024
90bf43a
support more ranges
RenaudRohlinger Feb 2, 2024
0f5d614
format dynamically in arrayelementnode
RenaudRohlinger Feb 3, 2024
b1c595b
init vertex buffer allocation to prevent issues with some backends
RenaudRohlinger Feb 3, 2024
71a5311
support different type size and update example
RenaudRohlinger Feb 3, 2024
a6c5b64
fix files.json
RenaudRohlinger Feb 3, 2024
0b6b1a8
puppeteer need webgpu support
RenaudRohlinger Feb 3, 2024
fbb4d63
unbind post pbo
RenaudRohlinger Feb 3, 2024
15033d7
add TODO
RenaudRohlinger Feb 3, 2024
affd3bc
Merge branch 'dev' into pr/27661
sunag Feb 4, 2024
cbb95e2
cleanup
sunag Feb 4, 2024
80f9766
Move to StorageArrayElementNode
sunag Feb 4, 2024
daf8f38
cleanup
sunag Feb 4, 2024
05c53b2
rev
sunag Feb 4, 2024
cf55995
add increaseUsage
sunag Feb 4, 2024
17ac24c
Update StorageArrayElementNode.js
sunag Feb 4, 2024
356b712
fixes optmization and revisions
sunag Feb 4, 2024
1c89f4d
revision
sunag Feb 4, 2024
74c6ac4
improved example for E2E tests
RenaudRohlinger Feb 4, 2024
1a22b0b
WIP: Test (1)
sunag Feb 5, 2024
de9b203
handle compute with non-PBO -> PBO case
RenaudRohlinger Feb 5, 2024
b47480e
revert WIP
sunag Feb 5, 2024
c9a21ea
TSL: add `storageObject`
sunag Feb 5, 2024
00f6a5f
revision
sunag Feb 5, 2024
7687fcc
make sure attributeData gets pbo attached in GLSLNodeBuilder
RenaudRohlinger Feb 6, 2024
39691a5
no need transfer anymore
RenaudRohlinger Feb 6, 2024
8936a37
revert compute_texture
RenaudRohlinger Feb 6, 2024
474c0f7
remove copyBufferToSubBuffer
sunag Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,8 @@
"webgpu_postprocessing_anamorphic",
"webgpu_mirror",
"webgpu_multisampled_renderbuffers",
"webgpu_materials_texture_anisotropy"
"webgpu_materials_texture_anisotropy",
"webgpu_storage_buffer"
],
"webaudio": [
"webaudio_orientation",
Expand Down
12 changes: 10 additions & 2 deletions examples/jsm/nodes/core/AssignNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ class AssignNode extends TempNode {

}

setup( builder ) {

const properties = builder.getNodeProperties( this );

properties.targetNode = this.targetNode.context( { assign: true } );
properties.sourceNode = this.sourceNode;

}

getNodeType( builder, output ) {

return output !== 'void' ? this.targetNode.getNodeType( builder ) : 'void';
Expand All @@ -27,8 +36,7 @@ class AssignNode extends TempNode {

generate( builder, output ) {

const targetNode = this.targetNode;
const sourceNode = this.sourceNode;
const { targetNode, sourceNode } = builder.getNodeProperties( this );

const targetType = targetNode.getNodeType( builder );

Expand Down
6 changes: 0 additions & 6 deletions examples/jsm/nodes/utils/ArrayElementNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ class ArrayElementNode extends Node { // @TODO: If extending from TempNode it br
const nodeSnippet = this.node.build( builder );
const indexSnippet = this.indexNode.build( builder, 'uint' );

if ( this.node.isStorageBufferNode && ! builder.isAvailable( 'storageBuffer' ) ) {

return nodeSnippet;

}

return `${nodeSnippet}[ ${indexSnippet} ]`;

}
Expand Down
37 changes: 34 additions & 3 deletions examples/jsm/nodes/utils/StorageArrayElementNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,52 @@ class StorageArrayElementNode extends ArrayElementNode {

}

generate( builder ) {
setup( builder ) {

if ( builder.isAvailable( 'storageBuffer' ) === false ) {

if ( ! this.node.instanceIndex ) {

builder.setupPBO( this.node );

}

}

super.setup( builder );

}

generate( builder, output ) {

const type = this.getNodeType( builder );

let snippet;

if ( builder.isAvailable( 'storageBuffer' ) === false ) {

snippet = this.node.build( builder );
const { node } = this;

// TODO: How to properly detect if the node will be used as an assign target?
if ( false && ! node.instanceIndex && builder.context.assign !== true ) {

snippet = builder.generatePBO( this );

} else {

const nodeSnippet = node.build( builder );

snippet = nodeSnippet;

}

} else {

snippet = super.generate( builder );

}

return snippet;
return builder.format( snippet, type, output );

}

Expand Down
6 changes: 6 additions & 0 deletions examples/jsm/renderers/webgl/WebGLBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,12 @@ class WebGLBackend extends Backend {
// switch active buffers
for ( let i = 0; i < transformBuffers.length; i ++ ) {

if ( transformBuffers[ i ].pbo ) {

//this.textureUtils.copyBufferToTexture( transformBuffers[ i ].transformBuffer, transformBuffers[ i ].pbo );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RenaudRohlinger I have a performance issue, could you open the example webgpu_compute_particles and uncomment the PBO line here? Perhaps you have any suggestions as to what it could be?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

webgpu_compute_particles shouldn't need to use PBO so it's probably some sort of infinite loop. It's not related to the PBO line, if you comment transformBuffers[ i ].switchBuffers(); the buffers will not be swapped anymore as they should and all compute examples will have these performances issues @sunag


}

transformBuffers[ i ].switchBuffers();

}
Expand Down
124 changes: 122 additions & 2 deletions examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { MathNode, GLSLNodeParser, NodeBuilder } from '../../../nodes/Nodes.js';
import { MathNode, GLSLNodeParser, NodeBuilder, UniformNode, vectorComponents } from '../../../nodes/Nodes.js';

import UniformBuffer from '../../common/UniformBuffer.js';
import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js';

import { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js';

import { IntType } from 'three';

import { IntType, DataTexture, RGBAFormat, FloatType } from 'three';

const glslMethods = {
[ MathNode.ATAN2 ]: 'atan',
Expand Down Expand Up @@ -84,6 +85,123 @@ ${ flowData.code }

}

setupPBO( node ) {

const attribute = node.value;

if ( attribute.pbo === undefined ) {

const originalArray = attribute.array;
const numElements = attribute.count * attribute.itemSize;

const width = Math.pow( 2, Math.ceil( Math.log2( Math.sqrt( numElements / 4 ) ) ) );
let height = Math.ceil( ( numElements / 4 ) / width );
if ( width * height * 4 < numElements ) height ++; // Ensure enough space

const newSize = width * height * 4; // 4 floats per pixel due to RGBA format

const newArray = new Float32Array( newSize );

newArray.set( originalArray, 0 );

attribute.array = newArray;
attribute.count = newSize;

const pboTexture = new DataTexture( attribute.array, width, height, RGBAFormat, FloatType );
pboTexture.needsUpdate = true;
pboTexture.isPBOTexture = true;

const pbo = new UniformNode( pboTexture );
pbo.setPrecision( 'high' );

attribute.pboNode = pbo;
attribute.pbo = pbo.value;

this.getUniformFromNode( attribute.pboNode, 'texture', this.shaderStage, this.context.label );

}

}

generatePBO( storageArrayElementNode ) {

const { node, indexNode } = storageArrayElementNode;
const attribute = node.value;

const nodeUniform = this.getUniformFromNode( attribute.pboNode, 'texture', this.shaderStage, this.context.label );
const textureName = this.getPropertyName( nodeUniform );

indexNode.increaseUsage( this ); // force cache generate to be used as index in x,y
const indexSnippet = indexNode.build( this, 'uint' );

const elementNodeData = this.getDataFromNode( storageArrayElementNode );

let propertyName = elementNodeData.propertyName;

if ( propertyName === undefined ) {

// property element

const nodeVar = this.getVarFromNode( storageArrayElementNode );

propertyName = this.getPropertyName( nodeVar );

// property size

const bufferNodeData = this.getDataFromNode( node );

let propertySizeName = bufferNodeData.propertySizeName;

if ( propertySizeName === undefined ) {

propertySizeName = propertyName + 'Size';

this.getVarFromNode( node, propertySizeName, 'uint' );

this.addLineFlowCode( `${ propertySizeName } = uint( textureSize( ${ textureName }, 0 ).x )` );

bufferNodeData.propertySizeName = propertySizeName;

}

//

let channel;
let padding;

const itemSize = attribute.itemSize;

if ( itemSize === 1 ) {

padding = 4;
channel = `[ ${indexSnippet} % uint( ${ padding } ) ]`;

} else {

padding = itemSize > 2 ? 1 : itemSize;
channel = '.' + vectorComponents.join( '' ).slice( 0, itemSize );

}

const uvSnippet = `ivec2(
${indexSnippet} / uint( ${ padding } ) % ${ propertySizeName },
${indexSnippet} / ( uint( ${ padding } ) * ${ propertySizeName } )
)`;

const snippet = this.generateTextureLoad( null, textureName, uvSnippet, null, '0' );

//

this.addLineFlowCode( `${ propertyName } = ${ snippet + channel }` );

elementNodeData.propertyName = propertyName;

}

return propertyName;

}

generateTextureLoad( texture, textureProperty, uvIndexSnippet, depthSnippet, levelSnippet = '0' ) {

if ( depthSnippet ) {
Expand Down Expand Up @@ -599,9 +717,11 @@ void main() {
this.vertexShader = this._getGLSLVertexCode( shadersData.vertex );
this.fragmentShader = this._getGLSLFragmentCode( shadersData.fragment );

console.log( this.fragmentShader );
} else {

this.computeShader = this._getGLSLVertexCode( shadersData.compute );
console.log( this.computeShader );

}

Expand Down
2 changes: 2 additions & 0 deletions examples/jsm/renderers/webgl/utils/WebGLAttributeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class DualAttributeData {

this.buffers = [ attributeData.bufferGPU, dualBuffer ];
this.type = attributeData.type;
this.pbo = attributeData.pbo;
this.bytesPerElement = attributeData.BYTES_PER_ELEMENT;
this.version = attributeData.version;
this.isInteger = attributeData.isInteger;
Expand Down Expand Up @@ -129,6 +130,7 @@ class WebGLAttributeUtils {
type,
bytesPerElement: array.BYTES_PER_ELEMENT,
version: attribute.version,
pbo: attribute.pbo,
isInteger: type === gl.INT || type === gl.UNSIGNED_INT || attribute.gpuType === IntType,
id: _id ++
};
Expand Down
36 changes: 36 additions & 0 deletions examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ class WebGLTextureUtils {
if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2

if ( texture.anisotropy > 1 || currentAnisotropy !== texture.anisotropy ) {

const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, backend.getMaxAnisotropy() ) );
backend.get( texture ).currentAnisotropy = texture.anisotropy;
Expand Down Expand Up @@ -289,6 +290,41 @@ class WebGLTextureUtils {

}

copyBufferToTexture( buffer, texture ) {

const { gl, backend } = this;

const { textureGPU, glTextureType, glFormat, glType } = backend.get( texture );

const { width, height } = texture.source.data;

gl.bindBuffer( gl.PIXEL_UNPACK_BUFFER, buffer );

backend.state.bindTexture( glTextureType, textureGPU );

gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, false );
gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false );
gl.texSubImage2D( glTextureType, 0, 0, 0, width, height, glFormat, glType, 0 );

gl.bindBuffer( gl.PIXEL_UNPACK_BUFFER, null );

backend.state.unbindTexture();
// debug
// const framebuffer = gl.createFramebuffer();
// gl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer );
// gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, glTextureType, textureGPU, 0 );

// const readout = new Float32Array( width * height * 4 );

// const altFormat = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_FORMAT );
// const altType = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_TYPE );

// gl.readPixels( 0, 0, width, height, altFormat, altType, readout );
// gl.bindFramebuffer( gl.FRAMEBUFFER, null );
// console.log( readout );

}

updateTexture( texture, options ) {

const { gl } = this;
Expand Down
2 changes: 1 addition & 1 deletion examples/jsm/renderers/webgpu/WebGPURenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class WebGPURenderer extends Renderer {

let BackendClass;

if ( parameters.forceWebGL ) {
if ( true || parameters.forceWebGL ) {

BackendClass = WebGLBackend;

Expand Down
Binary file added examples/screenshots/webgpu_storage_buffer.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading