Skip to content

Conversation

@LeviPesin
Copy link
Contributor

@LeviPesin LeviPesin commented Jan 11, 2023

Related issue: #23905 (comment)

Description

A port of my ComputationRenderers from https://github.com/LeviPesin/THREE.js-PathTracing-Renderer/tree/webgpu/js/pathtracing/utils (originally made as a part of the unfinished port of erichlof's Three.js' pathtracing renderer).

API

See the example's code. Mainly just new functions WebGLRenderer.compute( ...computeNodes ), WebGLRenderer.deuploadBufferAttrribute( bufferAtrribute ), and WebGPURenderer.deuploadBufferAtrribute( bufferAtrribute, overwrite = false ) are introduced.

Original TODO

  • Remove explicit srcBuffer, move to ComputeNode and BufferAttribute, rework methods and arguments to constructor(renderer), createBuffer(...params), and compute(shaderNode, outBuffer)
  • Remove TypedBuffer by creating WebGLStorageBuffer in WebGLNodes and changing ArrayElementNode for it (also simplify BufferNode by inferring bufferType, also allow ComputeNode to use a buffer instead of count)
  • Remove ComputationRenderers by creating WebGLRenderer.compute() (in WebGLNodes)

API Modification

I don't really like the current API of ComputationRenderers -- I think @sunag's approach of WebGPURenderer.compute() and ComputeNodes is better...
ComputationRenderers mainly consist of setting up stuff needed for WebGL computations (this can be safely moved to a WebGLRenderer.compute() function and a thing like WebGLStorageBuffer to be able to use StorageNode), dealing with TypedBuffer.getBufferElement() and TypedBuffer.setBufferElement() (first can theoretically be moved to ArrayElementNode), and auto-transferring data from GPU back to CPU (this can also be moved to WebGLRenderer.compute() and WebGPURenderer.compute()).
Problems are in details -- for example, ComputeNode isn't really useful for WebGL computations -- they are not going for a specific number of iterations, they go for each pixel in the outBuffer. outBuffer is also the only buffer that we can write to in WebGL -- and even with it dealing with TypedBuffer.setBufferElement() is quite tricky...

API was changed.

Example (misc_nodes_gpgpu)

I made an example for testing WebGL and WebGPU computations, including some basic performance measuring.

Options

  • backend -- selects which backend will be used for doing computations.
  • example -- which example of a simple ShaderNode computation will be shown (Passthrough, AntiPassthrough, PassthroughColor -- outBuffer is 4-channeled instead of 1, ElementManipulation -- a srcBuffer is used, Multiplication, Random -- generates white noise based on PCG algorithm).
  • warmUps -- how many iterations of computations to perform; useful for performance measurements.
  • logBuffers and logPerformance -- whether to log data buffers (default false) and performance (default true).
  • refresh -- press to dispose renderer and clear caches.
  • clearConsole -- press to clear console.

Performance Problems

  • For some reason most examples are much slower with WebGPU. This is especially easy to see with Random example -- it takes about 100-150 ms to compute in WebGL and 200-300 ms in WebGPU on my machine.
  • For some reason, when changing backend from WebGL to WebGPU a short blink happens.

@LeviPesin
Copy link
Contributor Author

I think I've managed to create a prototype of WebGLBuffer, will commit it soon...

@LeviPesin
Copy link
Contributor Author

WebGLBuffer works when used as a srcBuffer but fails for some reason when used as an outBuffer... Not sure why.


}

dispose() {
Copy link
Contributor Author

@LeviPesin LeviPesin Jan 12, 2023

Choose a reason for hiding this comment

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

I think we should also add a WebGPUBuffer.dispose() (and maybe BufferNode.dispose()) functions.

Also, when this function should be called?


generate( builder, output ) {

this.construct( builder ); // this is required for some reason?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not really sure why...

@LeviPesin
Copy link
Contributor Author

I think I almost done fixing that error -- and also making a good API (nothing really user-facing except WebGLRenderer.compute()). Will commit it soon...

@LeviPesin
Copy link
Contributor Author

For some reason in the shader appear mistakenly generated element( storage( attribute ), index ), which shouldn't be generated being a part of stack.assign( ..., ... )... Because of this the outBuffer is "used" as an srcBuffer and Chrome thinks a feedback loop is forming.
Here is the resulting shader:

// Three.js r149dev - NodeMaterial System

// <node_builder>

// uniforms
uniform sampler2D nodeUniform1; uniform vec2 nodeUniform2; 

// attributes


// varyings


// vars
vec4 nodeVar0; 

// codes


// </node_builder>

uniform vec3 diffuse;
uniform float opacity;
#ifndef FLAT_SHADED
	varying vec3 vNormal;
#endif
#include <common>
#include <dithering_pars_fragment>
#include <color_pars_fragment>
#include <uv_pars_fragment>
#include <uv2_pars_fragment>
#include <map_pars_fragment>
#include <alphamap_pars_fragment>
#include <alphatest_pars_fragment>
#include <aomap_pars_fragment>
#include <lightmap_pars_fragment>
#include <envmap_common_pars_fragment>
#include <envmap_pars_fragment>
#include <fog_pars_fragment>
#include <specularmap_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
void main() {

	

	#include <clipping_planes_fragment>
	nodeVar0 = texture2D( nodeUniform1, vec2( ( ( 0.5 + float( ( ( ( uint( gl_FragCoord.y ) * uint( nodeUniform2.x ) ) + uint( gl_FragCoord.x ) ) % uint( 16.0 ) ) ) ) / 16.0 ), ( ( 0.5 + float( ( ( ( uint( gl_FragCoord.y ) * uint( nodeUniform2.x ) ) + uint( gl_FragCoord.x ) ) / uint( 16.0 ) ) ) ) / 16.0 ) ) );
	( vec4( vec3( ( ( ( ( uint( gl_FragCoord.y ) * uint( nodeUniform2.x ) ) + uint( gl_FragCoord.x ) ) * uint( 2.0 ) ) % uint( 256.0 ) ), ( ( ( ( uint( gl_FragCoord.y ) * uint( nodeUniform2.x ) ) + uint( gl_FragCoord.x ) ) * uint( 3.0 ) ) % uint( 256.0 ) ), ( ( ( ( uint( gl_FragCoord.y ) * uint( nodeUniform2.x ) ) + uint( gl_FragCoord.x ) ) * uint( 5.0 ) ) % uint( 256.0 ) ) ), 1.0 ) / vec4( vec3( 255.0 ), 1.0 ) );
	;
	
	vec4 diffuseColor = ( vec4( vec3( ( ( ( ( uint( gl_FragCoord.y ) * uint( nodeUniform2.x ) ) + uint( gl_FragCoord.x ) ) * uint( 2.0 ) ) % uint( 256.0 ) ), ( ( ( ( uint( gl_FragCoord.y ) * uint( nodeUniform2.x ) ) + uint( gl_FragCoord.x ) ) * uint( 3.0 ) ) % uint( 256.0 ) ), ( ( ( ( uint( gl_FragCoord.y ) * uint( nodeUniform2.x ) ) + uint( gl_FragCoord.x ) ) * uint( 5.0 ) ) % uint( 256.0 ) ) ), 1.0 ) / vec4( vec3( 255.0 ), 1.0 ) );
	#include <logdepthbuf_fragment>
	#include <map_fragment>
	#include <color_fragment>
	#include <alphamap_fragment>
	#include <alphatest_fragment>
diffuseColor.a = opacity;

	#include <specularmap_fragment>
	ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
	#ifdef USE_LIGHTMAP
		vec4 lightMapTexel = texture2D( lightMap, vUv2 );
		reflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;
	#else
		reflectedLight.indirectDiffuse += vec3( 1.0 );
	#endif
	#include <aomap_fragment>
	reflectedLight.indirectDiffuse *= diffuseColor.rgb;
	vec3 outgoingLight = reflectedLight.indirectDiffuse;
	#include <envmap_fragment>
	#include <output_fragment>
	#include <tonemapping_fragment>
	#include <encodings_fragment>
	#include <fog_fragment>
	#include <premultiplied_alpha_fragment>
	#include <dithering_fragment>
}


const result = [ remainder( mul( index, 2 ), 256 ), remainder( mul( index, 3 ), 256 ), remainder( mul( index, 5 ), 256 ) ];
stack.assign( element( storage( outAttribute ), index ), params.backend === 'WebGL' ? color( ...result ) : uvec4( ...result, uint( 255 ) ) );
return stack;
Copy link
Contributor Author

@LeviPesin LeviPesin Jan 14, 2023

Choose a reason for hiding this comment

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

These return stacks should not be needed ideally...

this.setBuildStage( 'construct' );
node.build( this );
this.setBuildStage( currentBuildStage );
return node.build( this );
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I feel like this is a little bit overcomplicated (but at least works)...

import WebGPU from 'three/addons/capabilities/WebGPU.js';
import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';

import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class

Unused import nodeFrame.

const TypedArray = params.backend === 'WebGL' ? Uint8Array : Uint32Array; // WebGPU can only work with uint32

const index = instanceIndex;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe make index an alias to instanceIndex in ShaderNodeBaseElements?

@LeviPesin
Copy link
Contributor Author

I think I fixed that problem -- now Multiplication and Random examples render (although something is completely wrong with Multiplication colors -- maybe a problem somewhere in encodings or builder.getInstanceIndex). Investigating other problems...

@mrdoob mrdoob modified the milestones: r151, r152 Mar 30, 2023
@mrdoob mrdoob modified the milestones: r152, r153 Apr 27, 2023
@LeviPesin LeviPesin marked this pull request as draft May 3, 2023 14:36
@LeviPesin LeviPesin marked this pull request as draft May 3, 2023 14:36
@Mugen87 Mugen87 modified the milestones: r153, r154 Jun 1, 2023
@mrdoob mrdoob modified the milestones: r154, r155 Jun 29, 2023
@mrdoob mrdoob modified the milestones: r155, r156 Jul 27, 2023
@sunag sunag mentioned this pull request Aug 28, 2023
@mrdoob mrdoob modified the milestones: r156, r157 Aug 31, 2023
@mrdoob mrdoob modified the milestones: r157, r158 Sep 28, 2023
@mrdoob mrdoob modified the milestones: r158, r159 Oct 27, 2023
@mrdoob mrdoob modified the milestones: r159, r160 Nov 30, 2023
@mrdoob mrdoob modified the milestones: r160, r161 Dec 22, 2023
@LeviPesin
Copy link
Contributor Author

Closing because #27367 provides the same support.

@LeviPesin LeviPesin closed this Jan 18, 2024
@LeviPesin LeviPesin deleted the computation-renderer-port branch January 18, 2024 14:19
@sunag sunag removed this from the r161 milestone Jan 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants