From 2b056f89686ce593596a8bb7dd79dd035d60b45a Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Tue, 10 Jan 2023 00:51:47 +1100 Subject: [PATCH 01/18] Initial port --- examples/jsm/gpgpu/TypedBuffer.js | 146 +++++++++ .../jsm/gpgpu/WebGLComputationRenderer.js | 68 ++++ examples/jsm/gpgpu/WebGLTypedBuffer.js | 134 ++++++++ .../jsm/gpgpu/WebGPUComputationRenderer.js | 42 +++ examples/jsm/gpgpu/WebGPUTypedBuffer.js | 31 ++ examples/misc_gpgpu.html | 302 ++++++++++++++++++ 6 files changed, 723 insertions(+) create mode 100644 examples/jsm/gpgpu/TypedBuffer.js create mode 100644 examples/jsm/gpgpu/WebGLComputationRenderer.js create mode 100644 examples/jsm/gpgpu/WebGLTypedBuffer.js create mode 100644 examples/jsm/gpgpu/WebGPUComputationRenderer.js create mode 100644 examples/jsm/gpgpu/WebGPUTypedBuffer.js create mode 100644 examples/misc_gpgpu.html diff --git a/examples/jsm/gpgpu/TypedBuffer.js b/examples/jsm/gpgpu/TypedBuffer.js new file mode 100644 index 00000000000000..0590be48a8efb7 --- /dev/null +++ b/examples/jsm/gpgpu/TypedBuffer.js @@ -0,0 +1,146 @@ +import { float, int, uint, vec2, ivec2, uvec2, vec3, ivec3, uvec3, vec4, ivec4, uvec4 } from 'three/nodes'; + +export default class TypedBuffer { + + constructor( typedArray, elementSize = 1 ) { + + if ( ( elementSize !== 1 ) && ( elementSize !== 2 ) && ( elementSize !== 3 ) && ( elementSize !== 4 ) ) { + + throw new Error( 'Element size can be only 1, 2, 3, or 4' ); + + } + + if ( ( typedArray instanceof Float64Array ) || ( typedArray instanceof BigInt64Array ) || ( typedArray instanceof BigUint64Array ) ) { + + throw new Error( 'Float64Array, BigInt64Array, and BigUint64Array are not supported, because float64 and int64 are not supported either in WebGL or WebGPU' ); + + } else if ( ! ArrayBuffer.isView( typedArray ) || ( typedArray instanceof DataView ) ) { + + throw new Error( 'First argument must be a typed array' ); + + } + + this.elementSize = elementSize; + + this._typedArray = typedArray; + this._buffer = null; + + } + + get typedArray() { + + return this._typedArray; + + } + + set typedArray( typedArray ) { + + this._typedArray.set( typedArray ); + + } + + get buffer() { + + return this._buffer; + + } + + set buffer( value ) { + + throw new Error( 'GPU buffer of a TypedBuffer cannot be changed' ); + + } + + get arrayLength() { + + return this._typedArray.length; + + } + + get length() { + + return this._typedArray.length / this.elementSize; + + } + + getBufferElement( i ) { + + console.warn( 'Abstract function.' ); + + } + + setBufferElement( i, value ) { + + console.warn( 'Abstract function.' ); + + } + + set needsUpdate( value ) { + + this._buffer.needsUpdate = value; + + } + + dispose() { + + this._buffer.dispose(); + + } + +} + +export function getFunction( typedArray, elementSize ) { + + let functionType; + + if ( ( typedArray instanceof Int8Array ) || ( typedArray instanceof Int16Array ) || ( typedArray instanceof Int32Array ) ) { + + functionType = 'int'; + + } else if ( ( typedArray instanceof Uint8Array ) || ( typedArray instanceof Uint8ClampedArray ) || ( typedArray instanceof Uint16Array ) || ( typedArray instanceof Uint32Array ) ) { + + functionType = 'uint'; + + } else if ( typedArray instanceof Float32Array ) { + + functionType = 'float'; + + } + + switch ( elementSize ) { + + case 1: + return ( functionType === 'uint' ) ? uint : ( functionType === 'int' ) ? int : float; + + case 2: + return ( functionType === 'uint' ) ? uvec2 : ( functionType === 'int' ) ? ivec2 : vec2; + + case 3: + return ( functionType === 'uint' ) ? uvec3 : ( functionType === 'int' ) ? ivec3 : vec3; + + case 4: + return ( functionType === 'uint' ) ? uvec4 : ( functionType === 'int' ) ? ivec4 : vec4; + + } + +} + +export function getFloatFunction( elementSize ) { + + switch ( elementSize ) { + + case 1: + return float; + + case 2: + return vec2; + + case 3: + return vec3; + + case 4: + return vec4; + + } + +} \ No newline at end of file diff --git a/examples/jsm/gpgpu/WebGLComputationRenderer.js b/examples/jsm/gpgpu/WebGLComputationRenderer.js new file mode 100644 index 00000000000000..7e6dab557e011d --- /dev/null +++ b/examples/jsm/gpgpu/WebGLComputationRenderer.js @@ -0,0 +1,68 @@ +import { Camera, Mesh, PlaneGeometry, Scene, WebGLRenderTarget } from 'three'; +import { int, viewportCoordinate, add, mul, MeshBasicNodeMaterial, ShaderNode, color } from 'three/nodes'; +import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js'; +import WebGLTypedBuffer from './WebGLTypedBuffer.js'; + +export default class WebGLComputationRenderer { + + constructor( shaderNode ) { + + this.shaderNode = shaderNode; + + this._material = new MeshBasicNodeMaterial(); + this._scene = new Scene().add( new Mesh( new PlaneGeometry( 2, 2 ), this._material ) ); + this._camera = new Camera(); + + } + + createBuffer( ...params ) { + + return new WebGLTypedBuffer( ...params ); + + } + + setBuffers( srcBuffer, outBuffer ) { + + this.srcBuffer = srcBuffer; + this.outBuffer = outBuffer; + + const outGPUBuffer = outBuffer.buffer; + outGPUBuffer.isRenderTargetTexture = true; + + this._renderTarget = new WebGLRenderTarget( outGPUBuffer.image.width, outGPUBuffer.image.height, { depthBuffer: false } ); + this._renderTarget.texture = outGPUBuffer; + + const index = add( mul( int( viewportCoordinate.y ), outGPUBuffer.image.width ), int( viewportCoordinate.x ) ); + const shaderParams = { index, element: srcBuffer.getBufferElement( index ), buffer: srcBuffer }; // Same arguments as in Array.forEach() + this._material.colorNode = new ShaderNode( ( inputs, builder ) => { + + return outBuffer.setBufferElement( index, this.shaderNode.call( shaderParams, builder ) ); + + } ); + this._material.needsUpdate = true; + + } + + async compute( renderer, populateTypedArray = true ) { + + nodeFrame.update(); + + const currentRenderTarget = renderer.getRenderTarget(); + const renderTarget = this._renderTarget; + renderer.setRenderTarget( renderTarget ); + renderer.render( this._scene, this._camera ); + if ( populateTypedArray ) { + + renderer.readRenderTargetPixels( renderTarget, 0, 0, renderTarget.width, renderTarget.height, this.outBuffer.typedArray ); + // The .render call populates the GPU buffer, the .readRenderTargetPixels call populates the typed array + + } else { + + this.outBuffer.typedArray = new this.outBuffer.typedArray.constructor( this.outBuffer.typedArray.length ); + + } + renderer.setRenderTarget( currentRenderTarget ); + + } + +} \ No newline at end of file diff --git a/examples/jsm/gpgpu/WebGLTypedBuffer.js b/examples/jsm/gpgpu/WebGLTypedBuffer.js new file mode 100644 index 00000000000000..7a36501e3fae31 --- /dev/null +++ b/examples/jsm/gpgpu/WebGLTypedBuffer.js @@ -0,0 +1,134 @@ +import { + DataTexture, + + UnsignedByteType, + ByteType, + ShortType, + UnsignedShortType, + IntType, + UnsignedIntType, + FloatType, + + RedFormat, + RGFormat, + RGBAFormat +} from 'three'; +import { texture, add, div, remainder, floor, vec2, mul, float, int } from 'three/nodes'; +import TypedBuffer, { getFunction, getFloatFunction } from './TypedBuffer.js'; + +function getTextureElement( dataTexture, i, width, height ) { + + const x = div( add( 0.5, remainder( i, width ) ), width ); + const y = div( add( 0.5, div( i, width ) ), height ); + + return texture( dataTexture, vec2( x, y ) ); + +} + +export function getTextureType( typedArray ) { + + let textureType; + + if ( typedArray instanceof Int8Array ) { + + textureType = ByteType; + + } else if ( ( typedArray instanceof Uint8Array ) || ( typedArray instanceof Uint8ClampedArray ) ) { + + textureType = UnsignedByteType; + + } else if ( typedArray instanceof Int16Array ) { + + textureType = ShortType; + + } else if ( typedArray instanceof Uint16Array ) { + + textureType = UnsignedShortType; + + } else if ( typedArray instanceof Int32Array ) { + + textureType = IntType; + + } else if ( typedArray instanceof Uint32Array ) { + + textureType = UnsignedIntType; + + } else if ( typedArray instanceof Float32Array ) { + + textureType = FloatType; + + } + + return textureType; + +} + +export function getTextureFormat( elementSize ) { // @TODO: possibly support impossible type-format combinations by using padding + + switch ( elementSize ) { + + case 1: + return RedFormat; + + case 2: + return RGFormat; + + case 3: + return null; + + case 4: + return RGBAFormat; + + } + +} + +export function calculateWidthHeight( length ) { + + let width; // @TODO: maybe just set width and height to Math.sqrt( length ) and pad the texture with zeroes? + for ( width = Math.floor( Math.sqrt( length ) ); width > 0; width ++ ) { + + if ( length % width === 0 ) break; + + } + const height = length / width; + + return { width, height }; + +} + +export default class WebGLTypedBuffer extends TypedBuffer { + + constructor( typedArray, elementSize = 1 ) { + + // @TODO: add support for UBOs + + super( typedArray, elementSize ); + + const { width, height } = calculateWidthHeight( this.length ); + this.width = width; + this.height = height; + + const buffer = this._buffer = new DataTexture( typedArray, width, height, getTextureFormat( elementSize ), getTextureType( typedArray ) ); + buffer.needsUpdate = true; + + this._function = getFunction( typedArray, elementSize ); + this._floatFunction = getFloatFunction( elementSize ); + + } + + getBufferElement( i ) { + + const gpuBuffer = this._buffer; + const textureElement = getTextureElement( gpuBuffer, int( i ), gpuBuffer.image.width, gpuBuffer.image.height ); + return this._function( mul( textureElement, 255 ) ); + + } + + setBufferElement( i, value ) { + + return div( this._floatFunction( value ), 255 ); + + } + +} \ No newline at end of file diff --git a/examples/jsm/gpgpu/WebGPUComputationRenderer.js b/examples/jsm/gpgpu/WebGPUComputationRenderer.js new file mode 100644 index 00000000000000..326499f28d5b1d --- /dev/null +++ b/examples/jsm/gpgpu/WebGPUComputationRenderer.js @@ -0,0 +1,42 @@ +import { instanceIndex, ComputeNode, ShaderNode } from 'three/nodes'; +import WebGPUTypedBuffer from './WebGPUTypedBuffer.js'; + +export default class WebGPUComputationRenderer { + + constructor( shaderNode ) { + + this.shaderNode = shaderNode; + + } + + createBuffer( ...params ) { + + return new WebGPUTypedBuffer( ...params ); + + } + + setBuffers( srcBuffer, outBuffer ) { + + this.srcBuffer = srcBuffer; + this.outBuffer = outBuffer; + + const index = instanceIndex; + const shaderParams = { index, element: srcBuffer.getBufferElement( index ), buffer: srcBuffer }; // Same arguments as in Array.forEach() + this._shader = new ShaderNode( ( inputs, builder ) => { + + return outBuffer.setBufferElement( index, this.shaderNode.call( shaderParams, builder ) ); + + } ); + + } + + async compute( renderer, populateTypedArray = true ) { + + await renderer.compute( new ComputeNode( this._shader, this.outBuffer.length ) ); + + const buffer = populateTypedArray ? await renderer.getArrayBuffer( this.outBuffer.buffer ) : this.outBuffer.buffer.length; + this.outBuffer.typedArray = new this.outBuffer.typedArray.constructor( buffer ); + + } + +} \ No newline at end of file diff --git a/examples/jsm/gpgpu/WebGPUTypedBuffer.js b/examples/jsm/gpgpu/WebGPUTypedBuffer.js new file mode 100644 index 00000000000000..47a17eaa4eea96 --- /dev/null +++ b/examples/jsm/gpgpu/WebGPUTypedBuffer.js @@ -0,0 +1,31 @@ +import { InstancedBufferAttribute } from 'three'; +import { storage, element, assign } from 'three/nodes'; +import TypedBuffer, { getFunction } from './TypedBuffer.js'; + +export default class WebGPUTypedBuffer extends TypedBuffer { + + constructor( typedArray, elementSize = 1 ) { + + super( typedArray, elementSize ); + + this._buffer = new InstancedBufferAttribute( typedArray, elementSize ); + this._nodeBuffer = storage( this._buffer, getFunction( typedArray, elementSize )().nodeType, this.length ); + + } + + getBufferElement( i ) { + + return element( this._nodeBuffer, i ); + + } + + setBufferElement( i, value ) { + + return assign( element( this._nodeBuffer, i ), value ); + + } + + set needsUpdate( value ) {} // Temporary + dispose() {} // Temporary + +} \ No newline at end of file diff --git a/examples/misc_gpgpu.html b/examples/misc_gpgpu.html new file mode 100644 index 00000000000000..1e5ad581ac723f --- /dev/null +++ b/examples/misc_gpgpu.html @@ -0,0 +1,302 @@ + +
+#A`miWS)<5rxdCi!iCoYv*{wZ5|D_pi+Lzct&w zxTyVS+H$21Zfhu~Xy0h=uNdpgZd+uh%GQ^EJ$}@G8MJ?mo<6kJ^b5^)?#oTn4V}IB ziDp*3xD9o2bW5tcgj8#mi4;40ktxW|)v9oCgkt(e5%uJRVa)vH%gp@S)$aAFKWRBi f*1MCk*O{UEP94uUu6*x**{xkf)2>?~q2pIqX From c86b396db9da3ff6bea96b01851d99b8967f8480 Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Thu, 12 Jan 2023 02:49:01 +1100 Subject: [PATCH 04/18] Fix screenshot --- examples/screenshots/misc_nodes_gpgpu.jpg | Bin 4631 -> 15683 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/screenshots/misc_nodes_gpgpu.jpg b/examples/screenshots/misc_nodes_gpgpu.jpg index 9718c02f7725f7b4fa3d1034d2b6ffdaadf92730..4196a15dcc95e67f602ac8a2d782811675426502 100644 GIT binary patch literal 15683 zcmeHudt6N0`~M~h$*5F>MkOKXxE;5&KXEcHhg?pjqCpacBve!zNfJgbCFd}R6OutC zmuk~Z6jM=AYHGUQG}CSN% VpQU4H~RIcyQlejiEyZ zYYf#GJa~xakfFnvANa4~n!|=Ozw{3qsII1_K1^e ;zzIa))h%6&f2=~*wA762BXIqnoTmFJZ0+aIX};xH-EvBU+k80 zmMveg{x=6lrwz_7?%O=Jd+ylj<+uO9LH~fjL!rmRPK2L~ICcKQ#Y@pKmt(KojJtLF z&-goc?>$aRPI;30^x5;wtn8fJ*LiQ=7Jv9yQd;)ub9s%pwywURv8lO5+S%3J(<>w7 ziUEjPzd7*v-|PXy2MlB|*I?`!Fwh_Vs%Z~WH=Z$A$9Ap87GGVHnMa3=S{!xb(W{}R zv(`!Uw(ct$ray6ZjhU1&sn4SSH-nD-D~o;@^ur#y92u@Q02WM58$l5YYeKFMJTeTK z{6GKv4+Q@gFF;pd)#Wtu*`n7+m1r}UTq%*Y8%si$NrR)KYFt-d{kg*FOziG@@ALOt zQ^L&x1iA~eowABU<1IE_C5HXh@_vO^W7}|4vXo7j#Vymrw{q3psdNt-5w|uvdh&4T zm=^0vhN5B9tVnG?YUt(-YD#UwLO?3=Y2@|PQjfFHgQXcx{pL&~;TG5{3$!WET9-zS zJf;!p+Y059v1A)RVGZ@$27AJUN9tU^Zr(dyVs9_+iJ_6R4);T5$v 2ECh!&g?b8lU7mYQmY2wyQtH54q(@6Ej;kQ!TW>otL`&F>8JCj2r3yllo z$lH+^VPZh)_ aAK&6n5;2~+7_R-0P&biAwCiblrWbMW%lJ$_gptI1+Z zI06Mnb_A1*0HvMU&A(jR5X~V+a;PD#M`^^6l(49Qt3aTI#}w^t8^Wm}V?iI^9Y;}# zJs*Lkbo0ZcZ<8y-wuOuVEk5Iq%-e1#b*MtoP-`pYWO8^RsTJRPZFt7>lEm5bVs%=! zy%_SK;&P|KsXOPpQL~ogqmVTN{-cikgMS7n4$#QpT+*(T8Xt6k;2*x}QvSBgMkk45 zu&e+v_(hr$ondjtH)Zbzm(@cTzUgRg;T<;4%1qyRcR!~s2X9|kE1HL8$8lgG0%0LS z0+QN1sL?7RDU7GyUsvE+WaM(?YC;As60ww5Mg`Z?NbM!8;*+qR_k~6ZVxkM5bVz8# zL`2T+;9*VLao`Qsux2SVvS9{4pZ$zRV%eXQ7c!F8QXeuFb(T?PhG-?bvx7lD(O{&z zrR4Qjtbx;eU__s!l0K~_Gg`IMh)pN6J5sVo=LgMD^5v1rpF>+{gw<(T&ItR2iPy&U zfcrAa?=+f_>sr&NR)j~g)e92MOY&xvFF>V}i>MA#$?QanWu8QCpNVe*cdM6B>^hgw z-mB7%E9l=ud3}fB&36>I5}1xgt}o@mj_uP4Z*TMLWHh&GD<3baq3ndjP7!RHLeuYP zdQ%VG(P-3 bd1jlM$!!HC;b3SbEQ69TeQyi*3R*0V-@4CU_Lf2q> XCupXTtL*_ST(0ll*UO4rF&j%m>lsNd)0AVd{!UoZ9e<`1 zjnsfF8u`x|HjNDEbAtHZ-dS>Y8QNvgySsnoJ~5JDqv)Jr$A1Ic;rqhH(#ZD0uYD^A zih~<)m QCu0@2S&)HYWdIdQ8H zZE{Sb5&Vq{c7HY+MGe9{?d4H>(DDoaft@nNTlgHUf9&wU_=e9>3c}@fXxv`Wd#lj; z291nLY2?b=F5M3}7Rgm3hX(E{E4O>Wg^}gHHQw*>uFn?Br0VRtZ7VJLT_*1Y?aQyr z!>K8*!WT5MC^|WO 24bixCMb&;9xt8w; zfMjS8BRk)S#CE@fg0`JPa$PF_Fm>1xHto%_mozfa&2>&MW7^Ha4SeFZ0JeumqG|Rh zEb4q`Ij`O1U26N1>vFu)!i^F6#)Wfoa=c`woAWx6jwMIArpfptjbvH|e#G*(B|42P z!e3IIXp%XZ2ir(%`E{%?F_1>E^8(eDIFi?4& y |`&hO<9wc8IL}AJkhVxPFw1{aa=>w zI4SqK C0)W4%@Op-t z%Tev-Xok(PjJc nzHyq5kzm|Xq#W0Uyavllz? zSsnS9exHll(@35@r6*S?<`emt95z{f5K1G{QQLqob26-6(MZ(JAm pg}v zVo(kGr+AAySi4Z^PaQO3%_l9Esm77O!ft#MDK LJZm kG@eEpFVINXj*PP*GewjgCg-wfB>%Ydsdo^U zv06x)RY8cq9!@O=a3Cl8Nn@z6Z{1FQH%3UTOoLou%Oa0M+=LC3{Vd=ejV%5KVy2?k zpdXvGO*I{sn0QMC75Lw=5gX0@hINS`GHp>kpSX+}FpHA71?;$F_1Ef6+VEu#)wg(F zA9AXU^0Dl!)V_}H(D5(*A{1Zsf(6VPdsRTUF09V@`^5aysedhs*lpo3s*FMK3O1H- zd#{o9>|^h19fPQ}n0aev+G{p%bQT;nve*{5DJXw@%)YfZhW_?PdWuH=5WBali|*C6 zkE%SsyY0f3;j4xvt%)pYN^RK=TeFvNpphl^)G{H!;UvZ?#`ttmEb=Uk+!5n3Qf`AQ z_pE5x6CQEz1tvUABcz?TN)Usgj2hf^*)(D$#tV`(Uh~3CCa_N7ol7ywByqd^KnBVf z#8QvIvj{;KY6 `eil>y*Fl4e)9<^D+Cdb1qWbs5VE*J`r$l^!*XW z#s0}LAyuAP(oKdJeMfyPNycL3O75TR$^U?VeIslGdghWk#Z+$A>#C`TTk~#c#bu7l zpRkdVRpG9Z=)EW0BN`6M3>tzt!zZ ) 4&CGJNuXP%?RB6*dtjvpI+0$g6m|nQaNLEqe7@w| z4zznLgyff(fKH1l3Mmg+9LlI~C)Uyc^{;;J1~SJqrnsHSFq2*H^=r#_%$QIZ1cP*6 zpy@P%T2OOtc^9Q-XLSE^V ;r F}}GnJ0OZ!E7-)~)g?ioIWtXkMClHM}ZgY|hV*j7B$JynDDfnx%8z V<)#P><~Rs}OjTz8zqnDu*Bd@1tHxH>&bj*oq_Qg5lS4KE-%@eMW; z{sreL#kPs$qf>koo%X5`8L}g|$N~!s@%*wERhIZj1O+m0tS1v+ 8RNOG-VWi{Lv8^tgtsc*|vv9)k+?R-5_8N+!p2X-n{d+H; zxr~XvNh6N 0(cf8yIU&W#@DB)0jvVKj+bWUkb7 z{H@{-LB9D|W%{lnLU{Tqc~EUyX1> +H7S`!oaaSSp;O^^IAh216*<}L?lIrG`;v1B|9Szr z#>J|V-PI>Ou}oX4g^RxRzl_}nXH%({^y@aW1kb|4L)L))ns%+=;!iQ+%2O$OWQD3z zKn>^1&k^A$uB_yeW6&;37e@L+8p%<=*#o(eF?OfXM6s1(_;=M!#G0_?KZFYy;amEI zE594d)A7}#k?;D%qOAravBFLL6WB^bH6l2l9V @CoJ=JZjdste?c=-p;#MeCN_+=M}3RWr|Hn zUDGSF7tqM>G-90pp5Fy$*II6AONuvX-+?~ewBX=peRqh54OXPRm5qc(VqljUrnsa~ z=CX4&n!6$~$vN UouS}DjX?KS=5^mc~7iu1)CuSbj6coDp7i^DzytukNo_h zE%Ik-LRlKHXWbV!*c1>Qa_9 z1O?6)8gODom5_u5<0d}Zznc_vSzpN*PVD{X#a$NQU487()!taC|7)SUO=i{DC&Leh z^1DM@EK1)7XdWkeZ#|TM9b2*ZO2etfyP9+3{NpF5#vhK8u-OT}#a*cWV>}*crJAU+ zO_grP8(EN)$m9GPw@#qVbH34L4nvz`Y|>9rKCdu_HH)?SS+l()(V$593LGp?KqD4n zRPJjvHX=2Lu )TNv5e@>kdomV?3ihFwR>xgkXGxb^= z-_CG*yMFod&h?jy{kh{NMyK #y1* z*f+!8O=dGu@WH<5*gwB6vUvqIp+TGs!dGgVJGXSfE~iP2OOB55*f;R#&we?<@#?kl zM{^u5I&|pC`Jsd~{H*EZSAVwe%%_p%He{;m?i?DK2gL?J8M}ttIjg+MA(VTd$Lx$r zE>IHTLS{PU0&KeX5Z-evwbKMk+Cmlp>(#OqV1)u^)D;K`8@yrLlgp`s@UEF(u#~~+ zkk!}qV*9cD?NCZBOMQv!7&$|gLU)?2a&2p-HI``!r-;LIHU^wX$xxR)c5@v>O>#*A zZQ7YqkPTFO5|&A|yn{45%<9i!JEIdZuT_U31g!Y163N?2Zp}N9gbh@_3G`SP^ea;( z62Lk6+c>*yJ%Pl;$a$4jL^NJ=cTR^wZx@YpbUE@XdhjS+)m2sl r!g_PyKC*yydGLdO(Ld?^WPlZ&n;`Z795&FU|9NN1U6qhk)BdTtNmd~d$eUUq!T zx}_72EEl3Kl)u#P)kZ;1)x_I>xX zJspBKAz|M3>?#jj%r&s@FeIO&zZi`LufED92Wq$%+(REnI#$`F!7-T4hW28n*@s$s z0Fw;SotAr$SnGwd-3->M`0Tah(iH9zYAjBkZpErcnkrXdZ{d>TY9%H-j>$elq>n&r zaK)HXNRbold#~Ep+oeb=t27jYg3&Z@k8azVd@S$1fEq9mOZFu1r1;dn5KP3bb7&;^ zO3L3_jocTKlSw%19_wvg*UB}pR-5*4>Mlur-hkD9)H#cimjTX4B#(76YkYO?CmeqF z#>{)Chr7b><*p^!9;0(h66euKv-!)Os*vt~bAp8{CoJ;{?^K3yz+=(sE2C!YD4aFs z@)6sMXMfWA^=YbQjg`pVc>a{8XF7XYH~CDy=IC8592Jnidfv3?6vKVj<#nwqd{?ck z+?aN;WufS~Tl^OYX&Py(AbYm5lyJNx?N&(R!UUwDh!~E_x76U*QzH-3NXSdb)n5RM zS^6>69TAZOdRs@}U66H%Q#f&E9E3j`Z>;(!2!F@1?8pF!EViH6 Ozlnz}? z*b}Z&K{)_8@X4Y~QCCAMrN*K*SP@Y?h 6) )XR^pT0V6A$dB_Mm4YC}L#W+|oRLk?z>58tWO z(_C?7uLkgO$ncj_yrHv_r3en^%HdE%+U;bIAvOuB8JE}ssdx}|jIy+npJ9wGhxP*J z6k@Iz1GC~k1&jKJc(Y`eVpY%ofw 6Z!&cZaTrf7#>C8Aw z?8ks1n5~a~JEi(=fF+jsyA&m9 vSi+Aaynn~-GA`wc%(h$ zz858a#u
1FS;BMcP>2ptT#Q&3jdWouK5ouA>Rd5)LS>FV= z*?Uv)P*=Ed38#^1W-4{k)#opGLfo)dAwg5_5KdxQmNIzhj*a;>FaBJwse>3Ge&;M2 z{oY&V2RylNvAJg69SfWN7haUDT*3Jy5r$f?tu7gMd0I<~;rRz2#Z7PbEw|g3vFFhK z%8t+I`f5Xo@Xvo!r?#SolwqFkQmfqEfqNgFC`` s5xwA17ehi{iO1`{V@qf9x9h6N;I@=*@T7&-qV z&)`IAcI0xgr7qBuh>O8qWyeA+xRjk 2s2AGM{ z@w&B6U%3rSa<+6xO)3Quvll@X+4n$C;+AUusW({OO~DtuAY$u784 )$ylbr$yQMJi}s97uaJnCtb*?GJJHpva)sL>rw90+g2kFhKn-sk)6YzJlJyC zBW22(ThHR>o9%eevF?W3CFjYum5*`KQgj0Ep7$Bf!yMA0i`za;gCsdlB{R^sP(w=% zF$6EG^@QxfmL#Id57}K(?VexlJ0E}K8iW|ifg$Lf;oEmWqrS({RO!f!gp-sb#G4nA zvV2s!;j^j(+7iw(ZQ3l&lXzynBOeCV im3LT cb!x3eVUlJA#T{dT-GMDLcriL1UlkVU*>Sjsc?=Y^U(Wg5lF zv& |t$vJmu1gz3BbzOFs&N)Q9a=Q z^8dy2Dilr-)gSET^SF`R-bqmQY<3nLs};!>pkgdaLi$pz9MZlM-49#0Y#8~Ltu0Ub zl*|tb_TH)qRmMGiRmKF@0bU~28bxX3R-^g^?$eMI-%k;2UF=m5S|?0{W$~CQq>)vM zFw9m3)UNJnHuh;Y6f4?w6 O+-(3S%mRB*98j3Y@-Z~Jr#EyUZ z#sgWS(QHnR^%L_)60WsA*}wBxEsx8sGxU<34NYt|PhXxBvR0g7@`P=bwP0UL9ZX&y zaPN<)Tbl1$02MLq`6?23WDDTz;T{MvgC!AUJN7+BlA;a}p{7oY&k;qqhLetSufhX> z%*5>*AT(=7)choR3w$6o#0)E}?H>ri<`LvCX;jazb31Q-#0+fA0{fy(qb<3Fw6Ke< zZ6}v5$*k#++$_O!gb{9Qn9$HAkiBL?1KUSDBY3?Q@LzAKpV$$m);rV4?cl+ER;+tD zWopdKc=oXPLF^W(EUS=MSZj8V2> bn{V8;HN5u~HhE4d<3(#)1@f z+fL aU^jxgQq^$)_y+Bi-<0oH oL<1{j!GH4c!ur}U#%htbU?3?NC$?FE4oql9c-L;bL&j$KZbN%A0 zyJ~f(c7`TzOuTt0`F_m$sy9T!N!Ud98a~B3UL1e3zG%@d__~uy|M2NwBoP$?XF8;% z?23IZ>!gnJ<71Rziq ylvz>uch*8dq#M!@1>nZ zS?heiGJANYDAz^w2O7v$DfN22o6>8*6T$)3!FVTXqo7Rlk~3zOW~nuB%Ba8+!A)7% zTuP5i0#V2_LbdmV@rhLjgz{zsl`;_(JEcJ;<-@%c>|Ik-AhwQNOO$QwER#hA7Xi&| z_2}LtxhcwoWDv@gkCYOT-Sg2g TW%$G{6JlTGDNTjQ6_%$UfOz3$+N2` z{NXZ4X-BT;VksQ>m7tQX3&;Ak=zvV8lbrKhReuNFfk(LQfD5yvR^Zvk5 ~zh;Py zxF? 7`tl8J%Tf(p40H>)F|7IB$s{c6w~GPWSa zpdA|ExRPMQ?`pEx&=}CVr%zmluwz;|RfOrJC4rtYys^+R>b m~ymVDNqXWyse!mrLxR5xuV%5iBI+&+SJrBT8eGG-XAwdeLNrY&5f{pVn; zu@F1s(%)o_YkgML^9V4F+tmdm?no% 15lMfv?<#9?GjOMF@UmCNT#;_tT~@?5&iUkX zzoyxu{2j$<^1$02lr`|ihD|x3N{5gVBa;`D0R^6o5Obql+bOnWrD^ZIIxI&+L>=R+ z?g6#mfHjx(_27QSMI>6y6rO0L >UPhb9gD|ZdtDwhtgNB;p(u6w)z9sly@QvAKThcp`E*&7leTgexndz)rN5>T zcWx}F3HJDUA^8NzY-nP`K_#0<4tyubW>>L^ix`U}vEDhpigDoGrv06cxp`ub2v}Q7 zTd9UDjxrk$FH&l0 S)nc^Moe_eHSEDVE%cDgqiG6Rp3}qAQcQPhW?eL7Uf(k*E zhNb9|t7zSiE-`&w61XSP7x0FXvXX;tZC ZJr}{Tlyeh=34y;bQf2OCZ5wK8Ejg%`Udo{BlnP8N;NQB0yDNXS{ zWN!W!WQ;uGUopr`gQFgh Jh4b-ZFW+TYm|_3zpO3< zE4F$1FK(loaKfC+3qs)?OTgKVY@HQ%eQ#$+$=B|wTYg0uG9-8HD{;btt6T9|FNICN zB`zJdXiQHc-fkc%d(Up3%O{3lx%jfP{zunUs$S4>qaPTnSNj&NPj3p9SEVHz25# zDg6dis S;W1*L$I5(o#>&as9j}_&- ;?FR0_U*f`9_)_FA z8(KX>zsVUWStQ{N9_9pQdUM+gH2h`SDt<&;MrR{A8-~dh%r)f_7i`bwh4@om8)+Zq zWmRrfBZ$VEovxydg{6_!FJ1j*nk}PwCM5y}w0yML30@)wYi;x^P*25tt*o+bB0DTy zAP#Ho!-|=Qh&LGB*-kRup5cp|A{|AHg0m=_z9vBPoA$Ar85McGpkafRVpU@myR!+) zP4o;H3uqdtY1aWk{HA{YDm|gTy!6L>*`JAP`5l%#Q1gKXjI)<_qqW~G;I7FIWG;M| zMPR~ypLNt6?>-|&N=8R^qYJU>l&VD%8sUXZ4w%%H)w$x0&-INHukCUToE6HA2uM7A z<8;AK@A=E{0_E2caSP^o-P=@F7i7=9$lv-eVtz9_>BO^}I}TOdo}PN&<@2rRliCj+ z=>PQE|34c}m%WH`yuCEJa=BT&ll%LEE%P(2? 7C4$q^tBYLHA$UP< zbqBc&c%#t#lM}oromm`YE76CLrk4Sa%#W=F-s(Lz6qlOH1YH`7(=!ZY@bqaKdjMA0 z5eLW0w^r2DWW`q&6~gJ@HhA_N0-i%PxbnoTBWX;a7jcwv)YflOQv0MB*R+bNF+WRJ zn8@G~1l)|MZxh|jt#0qrC8x~-?rW8?ULQctLrk1n4Ev>U1 PY9 z<8XfuT)<}Ys_+8d7OtHAC7&yKXsrQ7k|$9>wkE?F<3sCHR4!+yW0wyPgPaCNF(JO% zN-@40BgZg89Qm7G!8XnV@CqnFpOm$XP4L{-wMm6GnHd0zj*p%$#B@-=Wu}o)Iy^?6 zSpr#SK%ggO5zOv@IvE4t*;#!?E$-E0ZUPHZcEUP2`w)rUA^dKgNPscX_{=L25e1z6 z!qjhlb~9_~NuE`p_Wd?F=6_!&K)8ZIn8S403Yz-FsUZE%V9ZMSE-32TT0unlKbn;V zF5x7bSxtmiPh }wD)=Fa2|67q_;mOq9 zRFWjo^x4c$No++9jRZI(<{j2=$+J8g+6+TQs%3rU9&4;O7M}log%3AKnm3#Z8fhdq z{6 xG>z>_@|t}o(8$p%^KXBd7TO-;e)}U|_BsCD^qOr}6+Rj!U%5(R zM Nxm7uP#S<#Eas1F~*)z xY<^m;0Y_sEYED#+Cop->IZTW7UdUV0d$hqV}o&SOE0X;yR8yvyS;PmH5z`g2k$8 z-i*kkkzK9qZoOwTg11w%54+$6l%y_vGgV%SHCY~ja(o53qqhV)i|fL-S@nK}oN4>J zfE)soRXD(N7~c8uVN_Ka+BC->?6U+BUZMg@x^Ky2 6^<^_Z9rM<=4mhuN&7- md5`HnTW p|dqx}B~Gk9bF`F{bJ1-^&? delta 1293 zcmV+o1@ijCdY2@y*#dt9v-k<|3&URwz9T1zd|~0cJyTDUNYrelhWA0V)RqY@Y^H)6 zrMJ5VDIN)Aj&)WmlMox1IUW}g<(RzND|9f`944nuNvY1A)G9_&gPlKmDx_0`YW7mS zovc(P%Panu{10i5_MrWg^)>MGc$48})x^36gK?)pE#HH5>!yE`eU;QvNj4{lT$pYi z(q#-3ZM89|MU{noBjGMFr12iZ5|XPp&81qbZEBE|lzqv#bHP-Enr+@{P0CSB-W8z~ zqSEDFO8UyHZTGsA6|_s0wXXGiHTzP2%T^=9^XmTqviF4=eLqBdTfaBK(p;#!Z?k!q zGJ&c@x9cH_<7|IrZOyma4#^xsfbELm9xtiw)jZ0rr!^&N%0A-fv?Kn{H%Z5u7m{@q zUuW+|Jn}79pqzchqi=5f*79p_b?wqxYr*^)@t4962>ABT#M9g@rJwd*puQ)t)%1~b z9o%mts# X M8|^!H$*oMi~|E^9bBImydMQgr1`F^b!N!>`|N+iSV+9y q~6zSmeP9r)=E*dw6xb)CiCw< zY!BG>Ux$Oiuzt=qH#4r2s9R~$>wXcA;w>U6_89G95NbB^OCs_A023E+KIk`?nA}Pi zrx#UX6uA| Yo$;0KrH86rT$CgG|uA9p7s+>dRvgv+*s9t)%v=HnHd0 zx QYIEsH# ztlNWd(rU@QIhwN5mD&k3o}9`}G_`V+{{X>EJOSZd6H #A`miWS)<5rxdCi!iCoYv*{wZ5|D_pi+Lzct&w zxTyVS+H$21Zfhu~Xy0h=uNdpgZd+uh%GQ^EJ$}@G8MJ?mo<6kJ^b5^)?#oTn4V}IB ziDp*3xD9o2bW5tcgj8#mi4;40ktxW|)v9oCgkt(e5%uJRVa)vH%gp@S)$aAFKWRBi z*1MCk*Date: Fri, 13 Jan 2023 00:53:51 +1100 Subject: [PATCH 05/18] WebGLBuffer (TODO 2). Doesn't really work --- examples/jsm/gpgpu/ComputationRenderer.js | 17 +- examples/jsm/gpgpu/TypedBuffer.js | 136 --------------- .../jsm/gpgpu/WebGLComputationRenderer.js | 35 +--- examples/jsm/gpgpu/WebGLTypedBuffer.js | 136 --------------- .../jsm/gpgpu/WebGPUComputationRenderer.js | 23 +-- examples/jsm/gpgpu/WebGPUTypedBuffer.js | 30 ---- examples/jsm/nodes/accessors/BufferNode.js | 2 +- examples/jsm/nodes/accessors/TextureNode.js | 2 + examples/jsm/nodes/math/OperatorNode.js | 9 +- examples/jsm/nodes/shadernode/ShaderNode.js | 2 - .../shadernode/ShaderNodeBaseElements.js | 68 +++++++- examples/jsm/nodes/utils/ArrayElementNode.js | 9 +- examples/jsm/renderers/webgl/WebGLBuffer.js | 156 ++++++++++++++++++ .../renderers/webgl/nodes/WebGLNodeBuilder.js | 18 +- examples/misc_nodes_gpgpu.html | 80 ++++----- 15 files changed, 306 insertions(+), 417 deletions(-) delete mode 100644 examples/jsm/gpgpu/TypedBuffer.js delete mode 100644 examples/jsm/gpgpu/WebGLTypedBuffer.js delete mode 100644 examples/jsm/gpgpu/WebGPUTypedBuffer.js create mode 100644 examples/jsm/renderers/webgl/WebGLBuffer.js diff --git a/examples/jsm/gpgpu/ComputationRenderer.js b/examples/jsm/gpgpu/ComputationRenderer.js index 87a08f98558328..396601a374d4b1 100644 --- a/examples/jsm/gpgpu/ComputationRenderer.js +++ b/examples/jsm/gpgpu/ComputationRenderer.js @@ -4,15 +4,6 @@ export default class ComputationRenderer { this.renderer = renderer; - this._buffers = []; - - } - - createBuffer( /* ...params */ ) { - - console.warn( 'Abstract function.' ); - // this._buffers.push( buffer ); - } async compute( /* shaderNode, outBuffer, populateTypedArray = true */ ) { @@ -21,10 +12,4 @@ export default class ComputationRenderer { } - disposeBuffers() { - - this._buffers.forEach( buffer => buffer.dispose() ); - - } - -} \ No newline at end of file +} diff --git a/examples/jsm/gpgpu/TypedBuffer.js b/examples/jsm/gpgpu/TypedBuffer.js deleted file mode 100644 index 6c3c777acc3efe..00000000000000 --- a/examples/jsm/gpgpu/TypedBuffer.js +++ /dev/null @@ -1,136 +0,0 @@ -import { float, int, uint, vec2, ivec2, uvec2, vec3, ivec3, uvec3, vec4, ivec4, uvec4 } from 'three/nodes'; - -export default class TypedBuffer { - - constructor( bufferAttribute ) { - - if ( ( bufferAttribute.array instanceof Float64Array ) || ( bufferAttribute.array instanceof BigInt64Array ) || ( bufferAttribute.array instanceof BigUint64Array ) ) { - - throw new Error( 'Float64Array, BigInt64Array, and BigUint64Array are not supported, because float64 and int64 are not supported either in WebGL or WebGPU' ); - - } - - this._attribute = bufferAttribute; - this._buffer = null; - - } - - get typedArray() { - - return this._attribute.array; - - } - - set typedArray( typedArray ) { - - this._attribute.array.set( new this._attribute.array.constructor( typedArray ) ); - - } - - get buffer() { - - return this._buffer; - - } - - set buffer( value ) { - - throw new Error( 'GPU buffer of a TypedBuffer cannot be changed' ); - - } - - get arrayLength() { - - return this._attribute.array.length; - - } - - get length() { - - return this._attribute.count; - - } - - getBufferElement( /* i */ ) { - - console.warn( 'Abstract function.' ); - - } - - setBufferElement( /* i, value */ ) { - - console.warn( 'Abstract function.' ); - - } - - set needsUpdate( value ) { - - this._buffer.needsUpdate = value; - - } - - dispose() { - - this._buffer.dispose(); - - } - -} - -export function getFunction( bufferAttribute ) { - - const array = bufferAttribute.array; - - let functionType; - - if ( ( array instanceof Int8Array ) || ( array instanceof Int16Array ) || ( array instanceof Int32Array ) ) { - - functionType = 'int'; - - } else if ( ( array instanceof Uint8Array ) || ( array instanceof Uint8ClampedArray ) || ( array instanceof Uint16Array ) || ( array instanceof Uint32Array ) ) { - - functionType = 'uint'; - - } else if ( array instanceof Float32Array ) { - - functionType = 'float'; - - } - - switch ( bufferAttribute.itemSize ) { - - case 1: - return ( functionType === 'uint' ) ? uint : ( functionType === 'int' ) ? int : float; - - case 2: - return ( functionType === 'uint' ) ? uvec2 : ( functionType === 'int' ) ? ivec2 : vec2; - - case 3: - return ( functionType === 'uint' ) ? uvec3 : ( functionType === 'int' ) ? ivec3 : vec3; - - case 4: - return ( functionType === 'uint' ) ? uvec4 : ( functionType === 'int' ) ? ivec4 : vec4; - - } - -} - -export function getFloatFunction( bufferAttribute ) { - - switch ( bufferAttribute.itemSize ) { - - case 1: - return float; - - case 2: - return vec2; - - case 3: - return vec3; - - case 4: - return vec4; - - } - -} \ No newline at end of file diff --git a/examples/jsm/gpgpu/WebGLComputationRenderer.js b/examples/jsm/gpgpu/WebGLComputationRenderer.js index a6f7ff6b2a55d4..03105a90d7c3df 100644 --- a/examples/jsm/gpgpu/WebGLComputationRenderer.js +++ b/examples/jsm/gpgpu/WebGLComputationRenderer.js @@ -1,8 +1,7 @@ -import { Camera, Mesh, PlaneGeometry, Scene, WebGLRenderTarget } from 'three'; +import { Camera, Mesh, PlaneGeometry, Scene } from 'three'; import { int, viewportCoordinate, MeshBasicNodeMaterial, ShaderNode } from 'three/nodes'; import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js'; import ComputationRenderer from './ComputationRenderer.js'; -import WebGLTypedBuffer from './WebGLTypedBuffer.js'; export default class WebGLComputationRenderer extends ComputationRenderer { @@ -16,48 +15,32 @@ export default class WebGLComputationRenderer extends ComputationRenderer { } - createBuffer( ...params ) { - - const buffer = new WebGLTypedBuffer( ...params ); - this._buffers.push( buffer ); - return buffer; - - } - async compute( shaderNode, outBuffer, populateTypedArray = true ) { nodeFrame.update(); - const outGPUBuffer = outBuffer.buffer; - outGPUBuffer.isRenderTargetTexture = true; - const outTypedArray = outBuffer.typedArray; - - const renderTarget = new WebGLRenderTarget( outGPUBuffer.image.width, outGPUBuffer.image.height, { depthBuffer: false } ); - renderTarget.texture = outGPUBuffer; - - const index = int( viewportCoordinate.y ).mul( outGPUBuffer.image.width ).add( int( viewportCoordinate.x ) ); - this._material.colorNode = new ShaderNode( ( inputs, builder ) => { - - return outBuffer.setBufferElement( index, shaderNode.call( { index }, builder ) ); - - } ); + const index = int( viewportCoordinate.y ).mul( outBuffer.texture.image.width ).add( int( viewportCoordinate.x ) ); + this._material.colorNode = new ShaderNode( ( inputs, stack ) => shaderNode.call( { index }, stack ) ); this._material.needsUpdate = true; + const renderTarget = outBuffer.renderTarget; const currentRenderTarget = this.renderer.getRenderTarget(); this.renderer.setRenderTarget( renderTarget ); this.renderer.render( this._scene, this._camera ); if ( populateTypedArray ) { - this.renderer.readRenderTargetPixels( renderTarget, 0, 0, renderTarget.width, renderTarget.height, outTypedArray ); + this.renderer.readRenderTargetPixels( renderTarget, 0, 0, renderTarget.width, renderTarget.height, outBuffer.attribute.array ); // The .render call populates the GPU buffer, the .readRenderTargetPixels call populates the typed array } else { - outBuffer.typedArray = outTypedArray.length; // null array + outBuffer.attribute.array = new outBuffer.attribute.array.constructor( outBuffer.attribute.array.length ); } this.renderer.setRenderTarget( currentRenderTarget ); + // outBuffer.attribute.array.needsUpdate = true; + } -} \ No newline at end of file +} diff --git a/examples/jsm/gpgpu/WebGLTypedBuffer.js b/examples/jsm/gpgpu/WebGLTypedBuffer.js deleted file mode 100644 index 1ba95baf119569..00000000000000 --- a/examples/jsm/gpgpu/WebGLTypedBuffer.js +++ /dev/null @@ -1,136 +0,0 @@ -import { - DataTexture, - - UnsignedByteType, - ByteType, - ShortType, - UnsignedShortType, - IntType, - UnsignedIntType, - FloatType, - - RedFormat, - RGFormat, - RGBAFormat -} from 'three'; -import { texture, add, div, remainder, vec2, mul, int } from 'three/nodes'; -import TypedBuffer, { getFunction, getFloatFunction } from './TypedBuffer.js'; - -function getTextureElement( dataTexture, i, width, height ) { - - const x = div( add( 0.5, remainder( i, width ) ), width ); - const y = div( add( 0.5, div( i, width ) ), height ); - - return texture( dataTexture, vec2( x, y ) ); - -} - -function getTextureType( bufferAttribute ) { - - const array = bufferAttribute.array; - - let textureType; - - if ( array instanceof Int8Array ) { - - textureType = ByteType; - - } else if ( ( array instanceof Uint8Array ) || ( array instanceof Uint8ClampedArray ) ) { - - textureType = UnsignedByteType; - - } else if ( array instanceof Int16Array ) { - - textureType = ShortType; - - } else if ( array instanceof Uint16Array ) { - - textureType = UnsignedShortType; - - } else if ( array instanceof Int32Array ) { - - textureType = IntType; - - } else if ( array instanceof Uint32Array ) { - - textureType = UnsignedIntType; - - } else if ( array instanceof Float32Array ) { - - textureType = FloatType; - - } - - return textureType; - -} - -export function getTextureFormat( bufferAttribute ) { // @TODO: possibly support impossible type-format combinations by using padding - - switch ( bufferAttribute.itemSize ) { - - case 1: - return RedFormat; - - case 2: - return RGFormat; - - case 3: - return null; - - case 4: - return RGBAFormat; - - } - -} - -export function calculateWidthHeight( length ) { - - let width; // @TODO: maybe just set width and height to Math.sqrt( length ) and pad the texture with zeroes? - for ( width = Math.floor( Math.sqrt( length ) ); width > 0; width -- ) { - - if ( length % width === 0 ) break; - - } - const height = length / width; - - return { width, height }; - -} - -export default class WebGLTypedBuffer extends TypedBuffer { - - constructor( bufferAttribute ) { - - // @TODO: add support for UBOs - - super( bufferAttribute ); - - const { width, height } = calculateWidthHeight( this.length ); - this.width = width; - this.height = height; - - const buffer = this._buffer = new DataTexture( this.typedArray, width, height, getTextureFormat( bufferAttribute ), getTextureType( bufferAttribute ) ); - buffer.needsUpdate = true; - - this._function = getFunction( bufferAttribute ); - this._floatFunction = getFloatFunction( bufferAttribute ); - - } - - getBufferElement( i ) { - - const gpuBuffer = this._buffer; - const textureElement = getTextureElement( gpuBuffer, int( i ), gpuBuffer.image.width, gpuBuffer.image.height ); - return this._function( mul( textureElement, 255 ) ); - - } - - setBufferElement( i, value ) { - - return div( this._floatFunction( value ), 255 ); - - } - -} \ No newline at end of file diff --git a/examples/jsm/gpgpu/WebGPUComputationRenderer.js b/examples/jsm/gpgpu/WebGPUComputationRenderer.js index 69bf72c35aef9d..2acd3a8f403edc 100644 --- a/examples/jsm/gpgpu/WebGPUComputationRenderer.js +++ b/examples/jsm/gpgpu/WebGPUComputationRenderer.js @@ -1,30 +1,17 @@ import { instanceIndex, ComputeNode, ShaderNode } from 'three/nodes'; import ComputationRenderer from './ComputationRenderer.js'; -import WebGPUTypedBuffer from './WebGPUTypedBuffer.js'; export default class WebGPUComputationRenderer extends ComputationRenderer { - createBuffer( ...params ) { - - const buffer = new WebGPUTypedBuffer( ...params ); - this._buffers.push( buffer ); - return buffer; - - } - - async compute( shaderNode, outBuffer, populateTypedArray = true ) { + async compute( computeNode, outAttribute, populateTypedArray = true ) { const index = instanceIndex; - const shader = new ShaderNode( ( inputs, builder ) => { - - return outBuffer.setBufferElement( index, shaderNode.call( { index }, builder ) ); - - } ); + const shader = new ShaderNode( ( inputs, stack ) => computeNode.computeNode.call( { index }, stack ) ); - await this.renderer.compute( new ComputeNode( shader, outBuffer.length ) ); + await this.renderer.compute( new ComputeNode( shader, computeNode.count ) ); - outBuffer.typedArray = populateTypedArray ? await this.renderer.getArrayBuffer( outBuffer.buffer ) : outBuffer.length; + outAttribute.array = new outAttribute.array.constructor( populateTypedArray ? await this.renderer.getArrayBuffer( outAttribute ) : outAttribute.array.length ); } -} \ No newline at end of file +} diff --git a/examples/jsm/gpgpu/WebGPUTypedBuffer.js b/examples/jsm/gpgpu/WebGPUTypedBuffer.js deleted file mode 100644 index 58c316edd34534..00000000000000 --- a/examples/jsm/gpgpu/WebGPUTypedBuffer.js +++ /dev/null @@ -1,30 +0,0 @@ -import { storage, element, assign } from 'three/nodes'; -import TypedBuffer, { getFunction } from './TypedBuffer.js'; - -export default class WebGPUTypedBuffer extends TypedBuffer { - - constructor( bufferAttribute ) { - - super( bufferAttribute ); - - this._buffer = bufferAttribute; - this._nodeBuffer = storage( bufferAttribute, getFunction( bufferAttribute )().nodeType, this.length ); - - } - - getBufferElement( i ) { - - return element( this._nodeBuffer, i ); - - } - - setBufferElement( i, value ) { - - return assign( element( this._nodeBuffer, i ), value ); - - } - - set needsUpdate( value ) {} // Temporary - dispose() {} // Temporary - -} \ No newline at end of file diff --git a/examples/jsm/nodes/accessors/BufferNode.js b/examples/jsm/nodes/accessors/BufferNode.js index 0331475f332903..9f8e6e16d440c7 100644 --- a/examples/jsm/nodes/accessors/BufferNode.js +++ b/examples/jsm/nodes/accessors/BufferNode.js @@ -2,7 +2,7 @@ import UniformNode from '../core/UniformNode.js'; class BufferNode extends UniformNode { - constructor( value, bufferType, bufferCount = 0 ) { + constructor( value, bufferType = 'float', bufferCount = 0 ) { super( value, bufferType ); diff --git a/examples/jsm/nodes/accessors/TextureNode.js b/examples/jsm/nodes/accessors/TextureNode.js index 3646eb2534e32f..6378472277f478 100644 --- a/examples/jsm/nodes/accessors/TextureNode.js +++ b/examples/jsm/nodes/accessors/TextureNode.js @@ -69,6 +69,8 @@ class TextureNode extends UniformNode { generate( builder, output ) { + this.construct( builder ); // this is required for some reason? + const { uvNode, levelNode } = builder.getNodeProperties( this ); const texture = this.value; diff --git a/examples/jsm/nodes/math/OperatorNode.js b/examples/jsm/nodes/math/OperatorNode.js index 555ecd5479c6eb..5dca89d5e7f981 100644 --- a/examples/jsm/nodes/math/OperatorNode.js +++ b/examples/jsm/nodes/math/OperatorNode.js @@ -170,7 +170,14 @@ class OperatorNode extends TempNode { if ( op === '=' ) { - builder.addFlowCode( `${a} ${this.op} ${b}` ); + if ( aNode.isBufferNode === true || aNode.node.isBufferNode === true ) { + + const nodeData = builder.getDataFromNode( aNode.isBufferNode ? aNode : aNode.node, builder.getShaderStage() ); + if ( nodeData.uniformBuffer !== undefined ) return nodeData.uniformBuffer.setElement( bNode ).build( builder ); + + } + + builder.addFlowCode( `${a} = ${b}` ); return a; diff --git a/examples/jsm/nodes/shadernode/ShaderNode.js b/examples/jsm/nodes/shadernode/ShaderNode.js index 7230844e5b3473..943dceb2c2bc99 100644 --- a/examples/jsm/nodes/shadernode/ShaderNode.js +++ b/examples/jsm/nodes/shadernode/ShaderNode.js @@ -285,5 +285,3 @@ export const ConvertType = function ( type, cacheMap = null ) { }; }; - -export const getConstNodeType = ( value ) => value.nodeType || value.convertTo || ( typeof value === 'string' ? value : null ); diff --git a/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js b/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js index c6737ce6798b40..9c262951be6f31 100644 --- a/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js +++ b/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js @@ -52,7 +52,7 @@ import DiscardNode from '../utils/DiscardNode.js'; import MaxMipLevelNode from '../utils/MaxMipLevelNode.js'; // shader node utils -import { ShaderNode, nodeObject, nodeObjects, nodeArray, nodeProxy, nodeImmutable, ConvertType, getConstNodeType, cacheMaps } from './ShaderNode.js'; +import { ShaderNode, nodeObject, nodeObjects, nodeArray, nodeProxy, nodeImmutable, ConvertType, cacheMaps } from './ShaderNode.js'; // shader node base @@ -90,6 +90,68 @@ export const imat4 = new ConvertType( 'imat4' ); export const umat4 = new ConvertType( 'umat4' ); export const bmat4 = new ConvertType( 'bmat4' ); +// utils functions + +const getConstNodeType = ( value ) => ( value === undefined || value === null ) ? null : ( value.nodeType || value.convertTo || ( typeof value === 'string' ? value : null ) ); + +export function getBufferSrcFunction( bufferAttribute ) { + + const array = bufferAttribute.array; + + let functionType; + + if ( ( array instanceof Int8Array ) || ( array instanceof Int16Array ) || ( array instanceof Int32Array ) ) { + + functionType = 'int'; + + } else if ( ( array instanceof Uint8Array ) || ( array instanceof Uint8ClampedArray ) || ( array instanceof Uint16Array ) || ( array instanceof Uint32Array ) ) { + + functionType = 'uint'; + + } else if ( array instanceof Float32Array ) { + + functionType = 'float'; + + } + + switch ( bufferAttribute.itemSize ) { + + case 1: + return ( functionType === 'uint' ) ? uint : ( functionType === 'int' ) ? int : float; + + case 2: + return ( functionType === 'uint' ) ? uvec2 : ( functionType === 'int' ) ? ivec2 : vec2; + + case 3: + return ( functionType === 'uint' ) ? uvec3 : ( functionType === 'int' ) ? ivec3 : vec3; + + case 4: + return ( functionType === 'uint' ) ? uvec4 : ( functionType === 'int' ) ? ivec4 : vec4; + + } + +} + +export function getBufferOutFunction( bufferAttribute ) { + + switch ( bufferAttribute.itemSize ) { + + case 1: + return float; + + case 2: + return vec2; + + case 3: + return vec3; + + case 4: + return vec4; + + } + +} + // core // @TODO: ArrayUniformNode @@ -213,8 +275,8 @@ export const faceforward = nodeProxy( MathNode, MathNode.FACEFORWARD ); // accessors -export const buffer = ( value, nodeOrType, count ) => nodeObject( new BufferNode( value, getConstNodeType( nodeOrType ), count ) ); -export const storage = ( value, nodeOrType, count ) => nodeObject( new StorageBufferNode( value, getConstNodeType( nodeOrType ), count ) ); +export const buffer = ( value, nodeOrType, count ) => nodeObject( new BufferNode( value, getConstNodeType( nodeOrType ) || getBufferSrcFunction( value )().nodeType, count ) ); +export const storage = ( value, nodeOrType, count ) => nodeObject( new StorageBufferNode( value, getConstNodeType( nodeOrType ) || getBufferSrcFunction( value )().nodeType, count ) ); export const cameraProjectionMatrix = nodeImmutable( CameraNode, CameraNode.PROJECTION_MATRIX ); export const cameraViewMatrix = nodeImmutable( CameraNode, CameraNode.VIEW_MATRIX ); diff --git a/examples/jsm/nodes/utils/ArrayElementNode.js b/examples/jsm/nodes/utils/ArrayElementNode.js index e50ad7e907c6b5..044931a6c897fb 100644 --- a/examples/jsm/nodes/utils/ArrayElementNode.js +++ b/examples/jsm/nodes/utils/ArrayElementNode.js @@ -19,9 +19,16 @@ class ArrayElementNode extends TempNode { generate( builder ) { - const nodeSnippet = this.node.build( builder ); + const nodeSnippet = this.node.build( builder ); // We should build the node before accessing its nodeData const indexSnippet = this.indexNode.build( builder, 'uint' ); + if ( this.node.isBufferNode === true ) { + + const nodeData = builder.getDataFromNode( this.node, builder.getShaderStage() ); + if ( nodeData.uniformBuffer !== undefined ) return nodeData.uniformBuffer.getElement( this.indexNode ).build( builder ); + + } + return `${nodeSnippet}[ ${indexSnippet} ]`; } diff --git a/examples/jsm/renderers/webgl/WebGLBuffer.js b/examples/jsm/renderers/webgl/WebGLBuffer.js new file mode 100644 index 00000000000000..309a34cc8e54ad --- /dev/null +++ b/examples/jsm/renderers/webgl/WebGLBuffer.js @@ -0,0 +1,156 @@ +import { + WebGLRenderTarget, + DataTexture, + + UnsignedByteType, + ByteType, + ShortType, + UnsignedShortType, + IntType, + UnsignedIntType, + FloatType, + + RedFormat, + RGFormat, + RGBAFormat +} from 'three'; +import { texture, vec2, uint, add, getBufferSrcFunction as getSrcFunction, getBufferOutFunction as getOutFunction } from 'three/nodes'; + +function getTextureElement( dataTexture, i, width, height ) { + + const x = add( 0.5, i.remainder( width ) ).div( width ); + const y = add( 0.5, i.div ( width ) ).div( height ); + + return texture( dataTexture, vec2( x, y ) ); + +} + +function getTextureType( attribute ) { + + const array = attribute.array; + + let textureType; + + if ( array instanceof Int8Array ) { + + textureType = ByteType; + + } else if ( ( array instanceof Uint8Array ) || ( array instanceof Uint8ClampedArray ) ) { + + textureType = UnsignedByteType; + + } else if ( array instanceof Int16Array ) { + + textureType = ShortType; + + } else if ( array instanceof Uint16Array ) { + + textureType = UnsignedShortType; + + } else if ( array instanceof Int32Array ) { + + textureType = IntType; + + } else if ( array instanceof Uint32Array ) { + + textureType = UnsignedIntType; + + } else if ( array instanceof Float32Array ) { + + textureType = FloatType; + + } + + return textureType; + +} + +export function getTextureFormat( attribute ) { // @TODO: possibly support impossible type-format combinations by using padding + + switch ( attribute.itemSize ) { + + case 1: + return RedFormat; + + case 2: + return RGFormat; + + case 3: + return null; + + case 4: + return RGBAFormat; + + } + +} + +export function calculateWidthHeight( length ) { + + let width; // @TODO: maybe just set width and height to Math.sqrt( length ) and pad the texture with zeroes? + for ( width = Math.floor( Math.sqrt( length ) ); width > 0; width -- ) { + + if ( length % width === 0 ) break; + + } + const height = length / width; + + return { width, height }; + +} + +function createTexture( attribute ) { + + const { width, height } = calculateWidthHeight( attribute.count ); + const texture = new DataTexture( attribute.array, width, height, getTextureFormat( attribute ), getTextureType( attribute ) ); + texture.needsUpdate = true; + return texture; + +} + +function createRenderTarget( texture ) { + + texture.isRenderTargetTexture = true; + const renderTarget = new WebGLRenderTarget( texture.image.width, texture.image.height, { depthBuffer: false } ); + renderTarget.texture = texture; + return renderTarget; + +} + +export default class WebGLBuffer { + + constructor( attribute ) { + + this.attribute = attribute; + + this.texture = createTexture( attribute ); + this.renderTarget = null; + + this._srcFunction = getSrcFunction( attribute ); + this._outFunction = getOutFunction( attribute ); + + } + + getElement( indexNode ) { + + const texture = this.texture; + const textureElement = getTextureElement( texture, uint( indexNode ), texture.image.width, texture.image.height ); + return this._srcFunction( textureElement.mul( 255 ) ); + + } + + setElement( value ) { + + if ( this.renderTarget === null ) this.renderTarget = createRenderTarget( this.texture ); + + return this._outFunction( value ).div( 255 ); + + } + + dispose() { + + ( this.renderTarget !== null ) ? this.renderTarget.dispose() : this.texture.dispose(); + + } + +} diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js index c6ae9b39d59d99..6593499e048268 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js @@ -1,5 +1,6 @@ import { defaultShaderStages, NodeFrame, MathNode, GLSLNodeParser, NodeBuilder } from 'three/nodes'; import SlotNode from './SlotNode.js'; +import WebGLBuffer from '../WebGLBuffer.js'; import { PerspectiveCamera, ShaderChunk, ShaderLib, UniformsUtils, UniformsLib } from 'three'; const nodeFrame = new NodeFrame(); @@ -456,6 +457,21 @@ class WebGLNodeBuilder extends NodeBuilder { } + getUniformFromNode( node, shaderStage, type ) { + + const uniformNode = super.getUniformFromNode( node, shaderStage, type ); + const nodeData = this.getDataFromNode( node, shaderStage ); + + if ( ( nodeData.uniformBuffer === undefined ) && ( type === 'buffer' || type === 'storageBuffer' ) ) { + + nodeData.uniformBuffer = new WebGLBuffer( node.value ); + + } + + return uniformNode; + + } + getUniforms( shaderStage ) { const uniforms = this.uniforms[ shaderStage ]; @@ -472,7 +488,7 @@ class WebGLNodeBuilder extends NodeBuilder { snippet += `uniform samplerCube ${uniform.name}; `; - } else { + } else if ( uniform.type !== 'buffer' && uniform.type !== 'storageBuffer' ) { // Uniform for a buffer will be generated by TextureNode in a WebGLBuffer const vectorType = this.getVectorType( uniform.type ); diff --git a/examples/misc_nodes_gpgpu.html b/examples/misc_nodes_gpgpu.html index 1693d7a70efe54..39ead51cf5b295 100644 --- a/examples/misc_nodes_gpgpu.html +++ b/examples/misc_nodes_gpgpu.html @@ -27,7 +27,7 @@ import * as THREE from 'three'; - import { ShaderNode, color, remainder, add, sub, mul, uint, bitXor, shiftRight, vec4, uvec4 } from 'three/nodes'; + import { ShaderNode, color, remainder, add, sub, mul, uint, bitXor, shiftRight, vec4, uvec4, storage, element, compute } from 'three/nodes'; import WebGPU from 'three/addons/capabilities/WebGPU.js'; import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js'; @@ -35,7 +35,7 @@ import WebGLComputationRenderer from 'three/addons/gpgpu/WebGLComputationRenderer.js'; import WebGPUComputationRenderer from 'three/addons/gpgpu/WebGPUComputationRenderer.js'; - import { getTextureFormat, calculateWidthHeight } from 'three/addons/gpgpu/WebGLTypedBuffer.js'; + import WebGLBuffer, { getTextureFormat, calculateWidthHeight } from 'three/addons/renderers/webgl/WebGLBuffer.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; @@ -43,7 +43,6 @@ backend: 'WebGL', example: 'Multiplication', - reuploadOutput: true, warmUps: 2, logBuffers: false, logPerformance: true, @@ -52,7 +51,7 @@ }; - const hash = new ShaderNode( ( { num }, builder ) => { // Taken from https://www.shadertoy.com/view/XlGcRh, originally from pcg-random.org + const hash = new ShaderNode( ( { num }, stack ) => { // Taken from https://www.shadertoy.com/view/XlGcRh, originally from pcg-random.org const state = add( mul( uint( num ), 747796405 ), 2891336453 ); const word = mul( bitXor( shiftRight( state, add( shiftRight( state, 28 ), 4 ) ), state ), 277803737 ); @@ -85,7 +84,6 @@ gui.add( params, 'backend', [ 'WebGL', 'WebGPU' ] ).onChange( () => { setBackend().then( showExample ); } ); gui.add( params, 'example', [ 'Passthrough', 'AntiPassthrough', 'PassthroughColor', 'ElementManipulation', 'Multiplication', 'Random' ] ).onChange( showExample ); - gui.add( params, 'reuploadOutput' ).onChange( showExample ); gui.add( params, 'warmUps', 0, 5, 1 ); gui.add( params, 'logBuffers' ); gui.add( params, 'logPerformance' ); @@ -185,18 +183,18 @@ switch ( params.example ) { case 'Passthrough': - shaderNode = new ShaderNode( ( { index }, builder ) => index ); outAttribute = new THREE.BufferAttribute( new TypedArray( 256 ), 1 ); + shaderNode = new ShaderNode( ( { index }, stack ) => stack.assign( element( storage( outAttribute ), index ), index ) ); break; case 'AntiPassthrough': - shaderNode = new ShaderNode( ( { index }, builder ) => sub( 255, index ) ); outAttribute = new THREE.BufferAttribute( new TypedArray( 256 ), 1 ); + shaderNode = new ShaderNode( ( { index }, stack ) => stack.assign( element( storage( outAttribute ), index ), sub( 255, index ) ) ); break; case 'PassthroughColor': - shaderNode = new ShaderNode( ( { index }, builder ) => params.backend === 'WebGL' ? index : uvec4( index, index, index, uint( 255 ) ) ); outAttribute = new THREE.BufferAttribute( new TypedArray( 256 * 4 ), 4 ); + shaderNode = new ShaderNode( ( { index }, stack ) => stack.assign( element( storage( outAttribute ), index ), params.backend === 'WebGL' ? index : uvec4( index, index, index, uint( 255 ) ) ) ); break; case 'ElementManipulation': @@ -205,77 +203,67 @@ srcAttribute.array[ 7 ] = 192 - 7; srcAttribute.array[ 8 ] = 64 - 8; srcAttribute.array[ 15 ] = 255 - 15; - const srcBuffer = computationRenderer.createBuffer( srcAttribute ); - shaderNode = new ShaderNode( ( { index }, builder ) => add( index, srcBuffer.getBufferElement( index ) ) ); + const srcBuffer = storage( srcAttribute ); outAttribute = new THREE.BufferAttribute( new TypedArray( 256 ), 1 ); + shaderNode = new ShaderNode( ( { index }, stack ) => stack.assign( element( storage( outAttribute ), index ), add( index, element( srcBuffer, index ) ) ) ); break; case 'Multiplication': - shaderNode = new ShaderNode( ( { index }, builder ) => { + outAttribute = new THREE.BufferAttribute( new TypedArray( 256 * 4 ), 4 ); + shaderNode = new ShaderNode( ( { index }, stack ) => { const result = [ remainder( mul( index, 2 ), 256 ), remainder( mul( index, 3 ), 256 ), remainder( mul( index, 5 ), 256 ) ]; - return params.backend === 'WebGL' ? color( ...result ) : uvec4( ...result, uint( 255 ) ); + stack.assign( element( storage( outAttribute ), index ), params.backend === 'WebGL' ? color( ...result ) : uvec4( ...result, uint( 255 ) ) ); } ); - outAttribute = new THREE.BufferAttribute( new TypedArray( 256 * 4 ), 4 ); break; case 'Random': - shaderNode = new ShaderNode( ( { index }, builder ) => { + outAttribute = new THREE.BufferAttribute( new TypedArray( 1024 ** 2 * 4 ), 4 ); + shaderNode = new ShaderNode( ( { index }, stack ) => { - const r = hash.call( { num: add( mul( index, 4 ), 0 ) }, builder ); // @TODO: Tweak parameters to some that produce a more random image - const g = hash.call( { num: add( mul( index, 4 ), 1 ) }, builder ); - const b = hash.call( { num: add( mul( index, 4 ), 2 ) }, builder ); - const a = hash.call( { num: add( mul( index, 4 ), 3 ) }, builder ); // Works only in WebGPU + const r = hash.call( { num: add( mul( index, 4 ), 0 ) }, stack ); // @TODO: Tweak parameters to some that produce a more random image + const g = hash.call( { num: add( mul( index, 4 ), 1 ) }, stack ); + const b = hash.call( { num: add( mul( index, 4 ), 2 ) }, stack ); + const a = hash.call( { num: add( mul( index, 4 ), 3 ) }, stack ); // Works only in WebGPU - return mul( vec4( r, g, b, a ), vec4( 256, 256, 256, 256 ) ); + stack.assign( element( storage( outAttribute ), index ), mul( vec4( r, g, b, a ), vec4( 256, 256, 256, 256 ) ) ); } ); - outAttribute = new THREE.BufferAttribute( new TypedArray( 1024 ** 2 * 4 ), 4 ); break; } - const outBuffer = computationRenderer.createBuffer( outAttribute ); - await computationRenderer.compute( shaderNode, outBuffer, params.reuploadOutput ); - const { typedArray, buffer } = outBuffer; - - if ( params.logBuffers && iteration === 0 ) console.log( params.backend, params.example, typedArray ); - if ( params.backend === 'WebGL' ) { - if ( params.reuploadOutput === true ) { - - material.map = new THREE.DataTexture( typedArray, buffer.image.width, buffer.image.height, buffer.format, buffer.type ); - material.map.needsUpdate = true; + await computationRenderer.compute( shaderNode, new WebGLBuffer( outAttribute ), true ); - } else { + } else { - material.map = buffer; + await computationRenderer.compute( compute( shaderNode, outAttribute.count ), outAttribute, true ); - } + } - } else { + const typedArray = outAttribute.array; - if ( params.reuploadOutput === true ) { + if ( params.logBuffers && iteration === 0 ) console.log( params.backend, params.example, typedArray ); - const uint8TypedArray = uint32ToUint8( typedArray ); // WebGPU can only work with uint8 textures - const type = THREE.UnsignedByteType; - const format = getTextureFormat( outAttribute ); - const { width, height } = calculateWidthHeight( outAttribute.count ); - material.map = new THREE.DataTexture( uint8TypedArray, width, height, format, type ); - material.map.needsUpdate = true; + if ( params.backend === 'WebGL' ) { - } else { + material.map = new THREE.DataTexture( typedArray, buffer.image.width, buffer.image.height, buffer.format, buffer.type ); + material.map.needsUpdate = true; - // @TODO: Investigate how params.reuploadOutput === false can work with WebGPU renderer (can WebGPU storage buffer be used to populate a texture?) + } else { - } + const uint8TypedArray = uint32ToUint8( typedArray ); // WebGPU doesn't currently support sampling uint32 textures + const type = THREE.UnsignedByteType; + const format = getTextureFormat( outAttribute ); + const { width, height } = calculateWidthHeight( outAttribute.count ); + material.map = new THREE.DataTexture( uint8TypedArray, width, height, format, type ); + material.map.needsUpdate = true; } await renderer.render( scene, camera ); - computationRenderer.disposeBuffers(); - if ( params.logPerformance ) console.timeEnd( params.backend + ' ' + params.example + ' Iteration-' + iteration ); if ( iteration < params.warmUps ) await showExample(); From 0a69697909cc9ba8977f911f70c10c7c88d8e807 Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Sat, 14 Jan 2023 15:50:28 +1100 Subject: [PATCH 06/18] Some nodes refactor --- examples/jsm/nodes/core/AssignNode.js | 75 +++++++++++++++++++ examples/jsm/nodes/core/StackNode.js | 6 +- examples/jsm/nodes/core/TempNode.js | 2 +- examples/jsm/nodes/core/VarNode.js | 5 +- examples/jsm/nodes/math/OperatorNode.js | 29 +------ .../shadernode/ShaderNodeBaseElements.js | 4 +- 6 files changed, 89 insertions(+), 32 deletions(-) create mode 100644 examples/jsm/nodes/core/AssignNode.js diff --git a/examples/jsm/nodes/core/AssignNode.js b/examples/jsm/nodes/core/AssignNode.js new file mode 100644 index 00000000000000..626c5e7624c4a7 --- /dev/null +++ b/examples/jsm/nodes/core/AssignNode.js @@ -0,0 +1,75 @@ +import TempNode from './TempNode.js'; + +class AssignNode extends TempNode { + + constructor( aNode, bNode ) { + + super(); + + this.aNode = aNode; + this.bNode = bNode; + + } + + hasDependencies( builder ) { + + return false; + + } + + getNodeType( builder, output ) { + + const aNode = this.aNode; + const bNode = this.bNode; + + const typeA = aNode.getNodeType( builder ); + const typeB = bNode.getNodeType( builder ); + + if ( typeA === 'void' || typeB === 'void' ) { + + return 'void'; + + } else { + + return typeA; + + } + + } + + generate( builder, output ) { + + const aNode = this.aNode; + const bNode = this.bNode; + + const type = this.getNodeType( builder, output ); + + const a = aNode.build( builder, type ); + const b = bNode.build( builder, type ); + + const outputLength = builder.getTypeLength( output ); + + if ( output !== 'void' ) { + + if ( aNode.isBufferNode === true || aNode.node.isBufferNode === true ) { + + const nodeData = builder.getDataFromNode( aNode.isBufferNode ? aNode : aNode.node, builder.getShaderStage() ); + if ( nodeData.uniformBuffer !== undefined ) return nodeData.uniformBuffer.setElement( bNode ).build( builder ); + + } + + builder.addFlowCode( `${a} = ${b}` ); + + return a; + + } else if ( type !== 'void' ) { + + return builder.format( `${a} = ${b}`, type, output ); + + } + + } + +} + +export default AssignNode; diff --git a/examples/jsm/nodes/core/StackNode.js b/examples/jsm/nodes/core/StackNode.js index a2e6246958dd78..6afd9733ff85d8 100644 --- a/examples/jsm/nodes/core/StackNode.js +++ b/examples/jsm/nodes/core/StackNode.js @@ -1,5 +1,5 @@ import Node from './Node.js'; -import OperatorNode from '../math/OperatorNode.js'; +import AssignNode from './AssignNode.js'; import BypassNode from '../core/BypassNode.js'; import ExpressionNode from '../core/ExpressionNode.js'; @@ -32,7 +32,7 @@ class StackNode extends Node { assign( targetNode, sourceValue ) { - return this.add( new OperatorNode( '=', targetNode, sourceValue ) ); + return this.add( new AssignNode( targetNode, sourceValue ) ); } @@ -44,7 +44,7 @@ class StackNode extends Node { } - return this.outputNode ? this.outputNode.build( builder, ...params ) : super.build( builder, ...params ); + return this.outputNode && ( this.outputNode !== this ) ? this.outputNode.build( builder, ...params ) : super.build( builder, ...params ); } diff --git a/examples/jsm/nodes/core/TempNode.js b/examples/jsm/nodes/core/TempNode.js index 2d77db896bf785..198ce4b54bb7b8 100644 --- a/examples/jsm/nodes/core/TempNode.js +++ b/examples/jsm/nodes/core/TempNode.js @@ -29,7 +29,7 @@ class TempNode extends Node { return builder.format( nodeData.propertyName, type, output ); - } else if ( builder.context.tempWrite !== false && type !== 'void ' && output !== 'void' && this.hasDependencies( builder ) ) { + } else if ( builder.context.tempWrite !== false && type !== 'void' && output !== 'void' && this.hasDependencies( builder ) ) { const snippet = super.build( builder, type ); diff --git a/examples/jsm/nodes/core/VarNode.js b/examples/jsm/nodes/core/VarNode.js index 3fe42ddbaf9b2c..408e4a5f6664ea 100644 --- a/examples/jsm/nodes/core/VarNode.js +++ b/examples/jsm/nodes/core/VarNode.js @@ -1,5 +1,6 @@ import Node from './Node.js'; import OperatorNode from '../math/OperatorNode.js'; +import AssignNode from './AssignNode.js'; class VarNode extends Node { @@ -22,7 +23,9 @@ class VarNode extends Node { assign( ...params ) { - return this.op( '=', ...params ); + this.node = new AssignNode( this.node, ...params ); + + return this; } diff --git a/examples/jsm/nodes/math/OperatorNode.js b/examples/jsm/nodes/math/OperatorNode.js index 5dca89d5e7f981..21a36cc57bcf86 100644 --- a/examples/jsm/nodes/math/OperatorNode.js +++ b/examples/jsm/nodes/math/OperatorNode.js @@ -27,12 +27,6 @@ class OperatorNode extends TempNode { } - hasDependencies( builder ) { - - return this.op !== '=' ? super.hasDependencies( builder ) : false; - - } - getNodeType( builder, output ) { const op = this.op; @@ -47,7 +41,7 @@ class OperatorNode extends TempNode { return 'void'; - } else if ( op === '=' || op === '%' ) { + } else if ( op === '%' ) { return typeA; @@ -114,11 +108,7 @@ class OperatorNode extends TempNode { typeA = aNode.getNodeType( builder ); typeB = bNode.getNodeType( builder ); - if ( op === '=' ) { - - typeB = typeA; - - } else if ( op === '<' || op === '>' || op === '<=' || op === '>=' ) { + if ( op === '<' || op === '>' || op === '<=' || op === '>=' ) { if ( builder.isVector( typeA ) ) { @@ -168,20 +158,7 @@ class OperatorNode extends TempNode { if ( output !== 'void' ) { - if ( op === '=' ) { - - if ( aNode.isBufferNode === true || aNode.node.isBufferNode === true ) { - - const nodeData = builder.getDataFromNode( aNode.isBufferNode ? aNode : aNode.node, builder.getShaderStage() ); - if ( nodeData.uniformBuffer !== undefined ) return nodeData.uniformBuffer.setElement( bNode ).build( builder ); - - } - - builder.addFlowCode( `${a} = ${b}` ); - - return a; - - } else if ( op === '<' && outputLength > 1 ) { + if ( op === '<' && outputLength > 1 ) { return builder.format( `${ builder.getMethod( 'lessThan' ) }( ${a}, ${b} )`, type, output ); diff --git a/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js b/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js index 9c262951be6f31..8bf4e058ef0a7e 100644 --- a/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js +++ b/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js @@ -1,5 +1,6 @@ // core //import ArrayUniformNode from '../core/ArrayUniformNode.js'; +import AssignNode from '../core/AssignNode.js'; import AttributeNode from '../core/AttributeNode.js'; import BypassNode from '../core/BypassNode.js'; import CacheNode from '../core/CacheNode.js'; @@ -204,13 +205,14 @@ export const INFINITY = float( 1e6 ); export const cond = nodeProxy( CondNode ); +export const assign = nodeProxy( AssignNode ); + export const add = nodeProxy( OperatorNode, '+' ); export const sub = nodeProxy( OperatorNode, '-' ); export const mul = nodeProxy( OperatorNode, '*' ); export const div = nodeProxy( OperatorNode, '/' ); export const remainder = nodeProxy( OperatorNode, '%' ); export const equal = nodeProxy( OperatorNode, '==' ); -export const assign = nodeProxy( OperatorNode, '=' ); export const lessThan = nodeProxy( OperatorNode, '<' ); export const greaterThan = nodeProxy( OperatorNode, '>' ); export const lessThanEqual = nodeProxy( OperatorNode, '<=' ); From cbf104b60c10146ec89599883ca146606584adda Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Sun, 15 Jan 2023 03:09:41 +1100 Subject: [PATCH 07/18] Fix (still not working) (TODO 3) --- examples/jsm/gpgpu/ComputationRenderer.js | 15 --- .../jsm/gpgpu/WebGLComputationRenderer.js | 46 -------- .../jsm/gpgpu/WebGPUComputationRenderer.js | 17 --- examples/jsm/nodes/core/AssignNode.js | 23 ++-- examples/jsm/nodes/core/StackNode.js | 4 +- examples/jsm/nodes/display/ViewportNode.js | 6 +- examples/jsm/nodes/gpgpu/ComputeNode.js | 4 +- examples/jsm/nodes/shadernode/ShaderNode.js | 6 ++ .../shadernode/ShaderNodeBaseElements.js | 2 +- examples/jsm/nodes/utils/ArrayElementNode.js | 12 ++- examples/jsm/renderers/webgl/WebGLBuffer.js | 2 +- .../renderers/webgl/nodes/WebGLNodeBuilder.js | 20 +++- .../jsm/renderers/webgl/nodes/WebGLNodes.js | 41 ++++++- .../jsm/renderers/webgpu/WebGPURenderer.js | 100 +++++++++--------- examples/misc_nodes_gpgpu.html | 60 +++++------ examples/webgpu_audio_processing.html | 6 +- 16 files changed, 172 insertions(+), 192 deletions(-) delete mode 100644 examples/jsm/gpgpu/ComputationRenderer.js delete mode 100644 examples/jsm/gpgpu/WebGLComputationRenderer.js delete mode 100644 examples/jsm/gpgpu/WebGPUComputationRenderer.js diff --git a/examples/jsm/gpgpu/ComputationRenderer.js b/examples/jsm/gpgpu/ComputationRenderer.js deleted file mode 100644 index 396601a374d4b1..00000000000000 --- a/examples/jsm/gpgpu/ComputationRenderer.js +++ /dev/null @@ -1,15 +0,0 @@ -export default class ComputationRenderer { - - constructor( renderer ) { - - this.renderer = renderer; - - } - - async compute( /* shaderNode, outBuffer, populateTypedArray = true */ ) { - - console.warn( 'Abstract function.' ); - - } - -} diff --git a/examples/jsm/gpgpu/WebGLComputationRenderer.js b/examples/jsm/gpgpu/WebGLComputationRenderer.js deleted file mode 100644 index 03105a90d7c3df..00000000000000 --- a/examples/jsm/gpgpu/WebGLComputationRenderer.js +++ /dev/null @@ -1,46 +0,0 @@ -import { Camera, Mesh, PlaneGeometry, Scene } from 'three'; -import { int, viewportCoordinate, MeshBasicNodeMaterial, ShaderNode } from 'three/nodes'; -import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js'; -import ComputationRenderer from './ComputationRenderer.js'; - -export default class WebGLComputationRenderer extends ComputationRenderer { - - constructor( renderer ) { - - super( renderer ); - - this._material = new MeshBasicNodeMaterial(); - this._scene = new Scene().add( new Mesh( new PlaneGeometry( 2, 2 ), this._material ) ); - this._camera = new Camera(); - - } - - async compute( shaderNode, outBuffer, populateTypedArray = true ) { - - nodeFrame.update(); - - const index = int( viewportCoordinate.y ).mul( outBuffer.texture.image.width ).add( int( viewportCoordinate.x ) ); - this._material.colorNode = new ShaderNode( ( inputs, stack ) => shaderNode.call( { index }, stack ) ); - this._material.needsUpdate = true; - - const renderTarget = outBuffer.renderTarget; - const currentRenderTarget = this.renderer.getRenderTarget(); - this.renderer.setRenderTarget( renderTarget ); - this.renderer.render( this._scene, this._camera ); - if ( populateTypedArray ) { - - this.renderer.readRenderTargetPixels( renderTarget, 0, 0, renderTarget.width, renderTarget.height, outBuffer.attribute.array ); - // The .render call populates the GPU buffer, the .readRenderTargetPixels call populates the typed array - - } else { - - outBuffer.attribute.array = new outBuffer.attribute.array.constructor( outBuffer.attribute.array.length ); - - } - this.renderer.setRenderTarget( currentRenderTarget ); - - // outBuffer.attribute.array.needsUpdate = true; - - } - -} diff --git a/examples/jsm/gpgpu/WebGPUComputationRenderer.js b/examples/jsm/gpgpu/WebGPUComputationRenderer.js deleted file mode 100644 index 2acd3a8f403edc..00000000000000 --- a/examples/jsm/gpgpu/WebGPUComputationRenderer.js +++ /dev/null @@ -1,17 +0,0 @@ -import { instanceIndex, ComputeNode, ShaderNode } from 'three/nodes'; -import ComputationRenderer from './ComputationRenderer.js'; - -export default class WebGPUComputationRenderer extends ComputationRenderer { - - async compute( computeNode, outAttribute, populateTypedArray = true ) { - - const index = instanceIndex; - const shader = new ShaderNode( ( inputs, stack ) => computeNode.computeNode.call( { index }, stack ) ); - - await this.renderer.compute( new ComputeNode( shader, computeNode.count ) ); - - outAttribute.array = new outAttribute.array.constructor( populateTypedArray ? await this.renderer.getArrayBuffer( outAttribute ) : outAttribute.array.length ); - - } - -} diff --git a/examples/jsm/nodes/core/AssignNode.js b/examples/jsm/nodes/core/AssignNode.js index 626c5e7624c4a7..faeb7131a313e3 100644 --- a/examples/jsm/nodes/core/AssignNode.js +++ b/examples/jsm/nodes/core/AssignNode.js @@ -40,24 +40,27 @@ class AssignNode extends TempNode { generate( builder, output ) { const aNode = this.aNode; - const bNode = this.bNode; - const type = this.getNodeType( builder, output ); - const a = aNode.build( builder, type ); - const b = bNode.build( builder, type ); - const outputLength = builder.getTypeLength( output ); + if ( aNode.isBufferNode === true || aNode.node.isBufferNode === true ) { - if ( output !== 'void' ) { - - if ( aNode.isBufferNode === true || aNode.node.isBufferNode === true ) { + const nodeData = builder.getDataFromNode( aNode.isBufferNode ? aNode : aNode.node, builder.getShaderStage() ); + const buffer = nodeData.uniformBuffer; + if ( buffer !== undefined ) { - const nodeData = builder.getDataFromNode( aNode.isBufferNode ? aNode : aNode.node, builder.getShaderStage() ); - if ( nodeData.uniformBuffer !== undefined ) return nodeData.uniformBuffer.setElement( bNode ).build( builder ); + builder.outComputeBuffer = buffer; + return buffer.setElement( this.bNode ).build( builder ); } + } + + const bNode = this.bNode; + const b = bNode.build( builder, type ); + + if ( output !== 'void' ) { + builder.addFlowCode( `${a} = ${b}` ); return a; diff --git a/examples/jsm/nodes/core/StackNode.js b/examples/jsm/nodes/core/StackNode.js index 6afd9733ff85d8..2f4598f5d7dc09 100644 --- a/examples/jsm/nodes/core/StackNode.js +++ b/examples/jsm/nodes/core/StackNode.js @@ -44,7 +44,9 @@ class StackNode extends Node { } - return this.outputNode && ( this.outputNode !== this ) ? this.outputNode.build( builder, ...params ) : super.build( builder, ...params ); + const outputNode = this.outputNode && ( this.outputNode.uuid !== this.uuid ) ? this.outputNode : this.nodes[ this.nodes.length - 1 ].callNode; + + return outputNode ? outputNode.build( builder, ...params ) : super.build( builder, ...params ); } diff --git a/examples/jsm/nodes/display/ViewportNode.js b/examples/jsm/nodes/display/ViewportNode.js index bd6edd16cda610..4a2734a5b4f616 100644 --- a/examples/jsm/nodes/display/ViewportNode.js +++ b/examples/jsm/nodes/display/ViewportNode.js @@ -19,7 +19,7 @@ class ViewportNode extends Node { getNodeType() { - return this.scope === ViewportNode.COORDINATE ? 'vec4' : 'vec2'; + return this.scope === ViewportNode.COORDINATE ? 'uvec4' : ( this.scope === ViewportNode.COORDINATE ? 'uvec2' : 'vec2' ); } @@ -41,7 +41,7 @@ class ViewportNode extends Node { update( { renderer } ) { - renderer.getSize( resolution ); + renderer.getSize( resolution || ( resolution = new Vector2() ) ); } @@ -55,7 +55,7 @@ class ViewportNode extends Node { if ( scope === ViewportNode.RESOLUTION ) { - output = uniform( resolution || ( resolution = new Vector2() ) ); + output = uniform( resolution || ( resolution = new Vector2() ), 'uvec2' ); } else { diff --git a/examples/jsm/nodes/gpgpu/ComputeNode.js b/examples/jsm/nodes/gpgpu/ComputeNode.js index 767a1e6c59c514..3da24e151f50a9 100644 --- a/examples/jsm/nodes/gpgpu/ComputeNode.js +++ b/examples/jsm/nodes/gpgpu/ComputeNode.js @@ -3,7 +3,7 @@ import { NodeUpdateType } from '../core/constants.js'; class ComputeNode extends Node { - constructor( computeNode, count, workgroupSize = [ 64 ] ) { + constructor( computeNode, count = 0, populateOutArray = true, workgroupSize = [ 64 ] ) { super( 'void' ); @@ -15,6 +15,8 @@ class ComputeNode extends Node { this.workgroupSize = workgroupSize; this.dispatchCount = 0; + this.populateOutArray = true; + this.updateType = NodeUpdateType.OBJECT; this.updateDispatchCount(); diff --git a/examples/jsm/nodes/shadernode/ShaderNode.js b/examples/jsm/nodes/shadernode/ShaderNode.js index 943dceb2c2bc99..a87581d1f089bd 100644 --- a/examples/jsm/nodes/shadernode/ShaderNode.js +++ b/examples/jsm/nodes/shadernode/ShaderNode.js @@ -35,6 +35,12 @@ const shaderNodeHandler = { return nodeObject( new SplitNode( node, prop ) ); + } else if ( prop === 'width' || prop === 'height' ) { + + // accessing property + + return nodeObject( new SplitNode( node, prop === 'width' ? 'x' : 'y' ) ); + } else if ( /^\d+$/.test( prop ) === true ) { // accessing array diff --git a/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js b/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js index 8bf4e058ef0a7e..9d9de12191d49b 100644 --- a/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js +++ b/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js @@ -359,7 +359,7 @@ export const pointUV = nodeImmutable( PointUVNode ); // gpgpu -export const compute = ( node, count, workgroupSize ) => nodeObject( new ComputeNode( nodeObject( node ), count, workgroupSize ) ); +export const compute = ( node, count, populateOutArray, workgroupSize ) => nodeObject( new ComputeNode( nodeObject( node ), count, populateOutArray, workgroupSize ) ); // display diff --git a/examples/jsm/nodes/utils/ArrayElementNode.js b/examples/jsm/nodes/utils/ArrayElementNode.js index 044931a6c897fb..5af16fb395a865 100644 --- a/examples/jsm/nodes/utils/ArrayElementNode.js +++ b/examples/jsm/nodes/utils/ArrayElementNode.js @@ -1,4 +1,4 @@ -import TempNode from '../core/Node.js'; +import TempNode from '../core/TempNode.js'; class ArrayElementNode extends TempNode { @@ -20,15 +20,21 @@ class ArrayElementNode extends TempNode { generate( builder ) { const nodeSnippet = this.node.build( builder ); // We should build the node before accessing its nodeData - const indexSnippet = this.indexNode.build( builder, 'uint' ); if ( this.node.isBufferNode === true ) { const nodeData = builder.getDataFromNode( this.node, builder.getShaderStage() ); - if ( nodeData.uniformBuffer !== undefined ) return nodeData.uniformBuffer.getElement( this.indexNode ).build( builder ); + const buffer = nodeData.uniformBuffer; + if ( buffer !== undefined ) { + + return buffer.getElement( this.indexNode ).build( builder ); + + } } + const indexSnippet = this.indexNode.build( builder, 'uint' ); + return `${nodeSnippet}[ ${indexSnippet} ]`; } diff --git a/examples/jsm/renderers/webgl/WebGLBuffer.js b/examples/jsm/renderers/webgl/WebGLBuffer.js index 309a34cc8e54ad..47c73ff70ddaa3 100644 --- a/examples/jsm/renderers/webgl/WebGLBuffer.js +++ b/examples/jsm/renderers/webgl/WebGLBuffer.js @@ -25,7 +25,7 @@ function getTextureElement( dataTexture, i, width, height ) { } -function getTextureType( attribute ) { +export function getTextureType( attribute ) { const array = attribute.array; diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js index 6593499e048268..cbc0f4dca9c157 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js @@ -1,7 +1,7 @@ -import { defaultShaderStages, NodeFrame, MathNode, GLSLNodeParser, NodeBuilder } from 'three/nodes'; +import { PerspectiveCamera, ShaderChunk, ShaderLib, UniformsUtils, UniformsLib } from 'three'; +import { defaultShaderStages, NodeFrame, MathNode, GLSLNodeParser, NodeBuilder, uint, viewportCoordinate, viewportResolution } from 'three/nodes'; import SlotNode from './SlotNode.js'; import WebGLBuffer from '../WebGLBuffer.js'; -import { PerspectiveCamera, ShaderChunk, ShaderLib, UniformsUtils, UniformsLib } from 'three'; const nodeFrame = new NodeFrame(); nodeFrame.camera = new PerspectiveCamera(); @@ -32,13 +32,15 @@ function getShaderStageProperty( shaderStage ) { class WebGLNodeBuilder extends NodeBuilder { - constructor( object, renderer, shader ) { + constructor( object, renderer, shader = {} ) { super( object, renderer, new GLSLNodeParser() ); this.shader = shader; this.slots = { vertex: [], fragment: [] }; + this.outComputeBuffer = null; + this._parseShaderLib(); this._parseInclude( 'fragment', 'lights_physical_fragment', 'clearcoat_normal_fragment_begin', 'transmission_fragment' ); this._parseObject(); @@ -526,6 +528,18 @@ class WebGLNodeBuilder extends NodeBuilder { } + getInstanceIndex() { + + const node = uint( viewportCoordinate.y ).mul( viewportResolution.width ).add( uint( viewportCoordinate.x ) ); // @COMMENT: Not sure why this uints are needed -- I fixed ViewportNode so that it produces uvec viewportCoordinate and viewportResolution... + + const currentBuildStage = this.getBuildStage(); + this.setBuildStage( 'construct' ); + node.build( this ); + this.setBuildStage( currentBuildStage ); + return node.build( this ); // @COMMENT: I feel like this is a little bit overcomplicated... + + } + getVaryings( shaderStage ) { let snippet = ''; diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodes.js b/examples/jsm/renderers/webgl/nodes/WebGLNodes.js index 2825b7335103ab..b291fe729b5c97 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodes.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodes.js @@ -1,7 +1,6 @@ +import { Material, WebGLRenderer, Mesh, PlaneGeometry, Scene, Camera } from 'three'; +import { NodeFrame, MeshBasicNodeMaterial } from 'three/nodes'; import { WebGLNodeBuilder } from './WebGLNodeBuilder.js'; -import { NodeFrame } from 'three/nodes'; - -import { Material } from 'three'; const builders = new WeakMap(); export const nodeFrame = new NodeFrame(); @@ -47,3 +46,39 @@ Material.prototype.onBeforeRender = function ( renderer, scene, camera, geometry } }; + +WebGLRenderer.prototype.compute = async function ( ...computeNodes ) { + + const material = new MeshBasicNodeMaterial(); + const geometry = new PlaneGeometry( 2, 2 ); + const object = new Mesh( geometry, material ); + const scene = new Scene().add( object ); + const camera = new Camera(); + + for ( const computeNode of computeNodes ) { + + material.colorNode = computeNode.computeNode; + material.needsUpdate = true; + + this.compile( scene, camera ); // Compile material and populate outComputeBuffer + + const outBuffer = builders.get( material ).outComputeBuffer; + + const renderTarget = outBuffer.renderTarget; + const currentRenderTarget = this.getRenderTarget(); + this.setRenderTarget( renderTarget ); + // nodeFrame.update(); + this.render( scene, camera ); + if ( computeNode.populateOutArray === true ) { // Note that if this is false then the results will be available only for further computeNodes, and will not be available after the completion of WebGLRenderer.compute() + + this.readRenderTargetPixels( renderTarget, 0, 0, renderTarget.width, renderTarget.height, outBuffer.attribute.array ); + + } + this.setRenderTarget( currentRenderTarget ); + + } + + material.dispose(); + geometry.dispose(); + +}; diff --git a/examples/jsm/renderers/webgpu/WebGPURenderer.js b/examples/jsm/renderers/webgpu/WebGPURenderer.js index ff9b6e24968abf..845a27d73082fe 100644 --- a/examples/jsm/renderers/webgpu/WebGPURenderer.js +++ b/examples/jsm/renderers/webgpu/WebGPURenderer.js @@ -375,6 +375,56 @@ class WebGPURenderer { } + async compute( ...computeNodes ) { + + if ( this._initialized === false ) await this.init(); + + const device = this._device; + const computePipelines = this._computePipelines; + + const cmdEncoder = device.createCommandEncoder( {} ); + const passEncoder = cmdEncoder.beginComputePass(); + + for ( const computeNode of computeNodes ) { + + // onInit + + if ( computePipelines.has( computeNode ) === false ) { + + computeNode.onInit( { renderer: this } ); + + } + + // pipeline + + const pipeline = computePipelines.get( computeNode ); + passEncoder.setPipeline( pipeline ); + + // node + + //this._nodes.update( computeNode ); + + // bind group + + const bindGroup = this._bindings.get( computeNode ).group; + this._bindings.update( computeNode ); + passEncoder.setBindGroup( 0, bindGroup ); + + passEncoder.dispatchWorkgroups( computeNode.dispatchCount ); + + } + + passEncoder.end(); + device.queue.submit( [ cmdEncoder.finish() ] ); + + } + + async populateArray( attribute ) { + + attribute.array = new attribute.array.constructor( await this._attributes.getArrayBuffer( attribute ) ); + + } + setAnimationLoop( callback ) { if ( this._initialized === false ) this.init(); @@ -387,12 +437,6 @@ class WebGPURenderer { } - async getArrayBuffer( attribute ) { - - return await this._attributes.getArrayBuffer( attribute ); - - } - getContext() { return this._context; @@ -621,50 +665,6 @@ class WebGPURenderer { } - async compute( ...computeNodes ) { - - if ( this._initialized === false ) await this.init(); - - const device = this._device; - const computePipelines = this._computePipelines; - - const cmdEncoder = device.createCommandEncoder( {} ); - const passEncoder = cmdEncoder.beginComputePass(); - - for ( const computeNode of computeNodes ) { - - // onInit - - if ( computePipelines.has( computeNode ) === false ) { - - computeNode.onInit( { renderer: this } ); - - } - - // pipeline - - const pipeline = computePipelines.get( computeNode ); - passEncoder.setPipeline( pipeline ); - - // node - - //this._nodes.update( computeNode ); - - // bind group - - const bindGroup = this._bindings.get( computeNode ).group; - this._bindings.update( computeNode ); - passEncoder.setBindGroup( 0, bindGroup ); - - passEncoder.dispatchWorkgroups( computeNode.dispatchCount ); - - } - - passEncoder.end(); - device.queue.submit( [ cmdEncoder.finish() ] ); - - } - getRenderTarget() { return this._renderTarget; diff --git a/examples/misc_nodes_gpgpu.html b/examples/misc_nodes_gpgpu.html index 39ead51cf5b295..60d5cd2a70ceb7 100644 --- a/examples/misc_nodes_gpgpu.html +++ b/examples/misc_nodes_gpgpu.html @@ -27,15 +27,14 @@ import * as THREE from 'three'; - import { ShaderNode, color, remainder, add, sub, mul, uint, bitXor, shiftRight, vec4, uvec4, storage, element, compute } from 'three/nodes'; + import { ShaderNode, color, remainder, add, sub, mul, uint, bitXor, shiftRight, vec4, uvec4, storage, element, compute, instanceIndex } from 'three/nodes'; import WebGPU from 'three/addons/capabilities/WebGPU.js'; import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js'; - import WebGLComputationRenderer from 'three/addons/gpgpu/WebGLComputationRenderer.js'; - import WebGPUComputationRenderer from 'three/addons/gpgpu/WebGPUComputationRenderer.js'; + import { nodeFrame } from 'three/addons/renderers/webgl/nodes/WebGLNodes.js'; - import WebGLBuffer, { getTextureFormat, calculateWidthHeight } from 'three/addons/renderers/webgl/WebGLBuffer.js'; + import { getTextureType, getTextureFormat, calculateWidthHeight } from 'three/addons/renderers/webgl/WebGLBuffer.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; @@ -141,13 +140,11 @@ if ( params.backend === 'WebGL' ) { renderer = new THREE.WebGLRenderer(); - computationRenderer = new WebGLComputationRenderer( renderer ); } else { renderer = new WebGPURenderer(); await renderer.init(); - computationRenderer = new WebGPUComputationRenderer( renderer ); } @@ -180,21 +177,23 @@ const TypedArray = params.backend === 'WebGL' ? Uint8Array : Uint32Array; // WebGPU can only work with uint32 + const index = instanceIndex; // @COMMENT: Maybe make this an alias to instanceIndex in ShaderNodeBaseElements? + switch ( params.example ) { case 'Passthrough': outAttribute = new THREE.BufferAttribute( new TypedArray( 256 ), 1 ); - shaderNode = new ShaderNode( ( { index }, stack ) => stack.assign( element( storage( outAttribute ), index ), index ) ); + shaderNode = new ShaderNode( ( {}, stack ) => stack.assign( element( storage( outAttribute ), index ), index ) ); break; case 'AntiPassthrough': outAttribute = new THREE.BufferAttribute( new TypedArray( 256 ), 1 ); - shaderNode = new ShaderNode( ( { index }, stack ) => stack.assign( element( storage( outAttribute ), index ), sub( 255, index ) ) ); + shaderNode = new ShaderNode( ( {}, stack ) => stack.assign( element( storage( outAttribute ), index ), sub( 255, index ) ) ); break; case 'PassthroughColor': outAttribute = new THREE.BufferAttribute( new TypedArray( 256 * 4 ), 4 ); - shaderNode = new ShaderNode( ( { index }, stack ) => stack.assign( element( storage( outAttribute ), index ), params.backend === 'WebGL' ? index : uvec4( index, index, index, uint( 255 ) ) ) ); + shaderNode = new ShaderNode( ( {}, stack ) => stack.assign( element( storage( outAttribute ), index ), params.backend === 'WebGL' ? index : uvec4( index, index, index, uint( 255 ) ) ) ); break; case 'ElementManipulation': @@ -205,20 +204,23 @@ srcAttribute.array[ 15 ] = 255 - 15; const srcBuffer = storage( srcAttribute ); outAttribute = new THREE.BufferAttribute( new TypedArray( 256 ), 1 ); - shaderNode = new ShaderNode( ( { index }, stack ) => stack.assign( element( storage( outAttribute ), index ), add( index, element( srcBuffer, index ) ) ) ); + shaderNode = new ShaderNode( ( {}, stack ) => stack.assign( element( storage( outAttribute ), index ), add( index, element( srcBuffer, index ) ) ) ); break; case 'Multiplication': outAttribute = new THREE.BufferAttribute( new TypedArray( 256 * 4 ), 4 ); - shaderNode = new ShaderNode( ( { index }, stack ) => { + shaderNode = new ShaderNode( ( {}, stack ) => { + 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; // @COMMENT: This `return stack`s should not be needed... + } ); break; case 'Random': outAttribute = new THREE.BufferAttribute( new TypedArray( 1024 ** 2 * 4 ), 4 ); - shaderNode = new ShaderNode( ( { index }, stack ) => { + shaderNode = new ShaderNode( ( {}, stack ) => { const r = hash.call( { num: add( mul( index, 4 ), 0 ) }, stack ); // @TODO: Tweak parameters to some that produce a more random image const g = hash.call( { num: add( mul( index, 4 ), 1 ) }, stack ); @@ -227,40 +229,28 @@ stack.assign( element( storage( outAttribute ), index ), mul( vec4( r, g, b, a ), vec4( 256, 256, 256, 256 ) ) ); + return stack; + } ); break; } - if ( params.backend === 'WebGL' ) { - - await computationRenderer.compute( shaderNode, new WebGLBuffer( outAttribute ), true ); - - } else { - - await computationRenderer.compute( compute( shaderNode, outAttribute.count ), outAttribute, true ); - - } + await renderer.compute( compute( shaderNode, outAttribute.count ) ); + if ( params.backend === 'WebGPU' ) await renderer.populateArray( outAttribute ); - const typedArray = outAttribute.array; + let typedArray = outAttribute.array; if ( params.logBuffers && iteration === 0 ) console.log( params.backend, params.example, typedArray ); - if ( params.backend === 'WebGL' ) { - - material.map = new THREE.DataTexture( typedArray, buffer.image.width, buffer.image.height, buffer.format, buffer.type ); - material.map.needsUpdate = true; - - } else { + if ( params.backend === 'WebGPU' ) typedArray = uint32ToUint8( typedArray ); // WebGPU doesn't currently support sampling uint32 textures - const uint8TypedArray = uint32ToUint8( typedArray ); // WebGPU doesn't currently support sampling uint32 textures - const type = THREE.UnsignedByteType; - const format = getTextureFormat( outAttribute ); - const { width, height } = calculateWidthHeight( outAttribute.count ); - material.map = new THREE.DataTexture( uint8TypedArray, width, height, format, type ); - material.map.needsUpdate = true; + const type = getTextureType( outAttribute ); + const format = getTextureFormat( outAttribute ); + const { width, height } = calculateWidthHeight( outAttribute.count ); - } + material.map = new THREE.DataTexture( typedArray, width, height, format, type ); + material.map.needsUpdate = true; await renderer.render( scene, camera ); diff --git a/examples/webgpu_audio_processing.html b/examples/webgpu_audio_processing.html index ffc160c945f9a7..f70163d42019f9 100644 --- a/examples/webgpu_audio_processing.html +++ b/examples/webgpu_audio_processing.html @@ -59,9 +59,9 @@ // compute audio - renderer.compute( computeNode ); - - const waveArray = new Float32Array( await renderer.getArrayBuffer( waveGPUBuffer ) ); + await renderer.compute( computeNode ); + await renderer.populateArray( waveGPUBuffer ); + const waveArray = waveGPUBuffer.array; // play result From bd312e7071a1e165a31a98063e8b897afc44a27f Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Sun, 15 Jan 2023 03:14:25 +1100 Subject: [PATCH 08/18] Remove GH comments from code --- examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js | 4 ++-- examples/misc_nodes_gpgpu.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js index cbc0f4dca9c157..b1590b933538ba 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js @@ -530,13 +530,13 @@ class WebGLNodeBuilder extends NodeBuilder { getInstanceIndex() { - const node = uint( viewportCoordinate.y ).mul( viewportResolution.width ).add( uint( viewportCoordinate.x ) ); // @COMMENT: Not sure why this uints are needed -- I fixed ViewportNode so that it produces uvec viewportCoordinate and viewportResolution... + const node = uint( viewportCoordinate.y ).mul( viewportResolution.width ).add( uint( viewportCoordinate.x ) ); const currentBuildStage = this.getBuildStage(); this.setBuildStage( 'construct' ); node.build( this ); this.setBuildStage( currentBuildStage ); - return node.build( this ); // @COMMENT: I feel like this is a little bit overcomplicated... + return node.build( this ); } diff --git a/examples/misc_nodes_gpgpu.html b/examples/misc_nodes_gpgpu.html index 60d5cd2a70ceb7..83f5766064d2eb 100644 --- a/examples/misc_nodes_gpgpu.html +++ b/examples/misc_nodes_gpgpu.html @@ -213,7 +213,7 @@ 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; // @COMMENT: This `return stack`s should not be needed... + return stack; } ); break; From 19d896d85a44390b144ccd9dee7dbe4562c056f1 Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Sun, 15 Jan 2023 22:31:43 +1100 Subject: [PATCH 09/18] Some cleanup --- examples/jsm/nodes/gpgpu/ComputeNode.js | 4 +-- .../shadernode/ShaderNodeBaseElements.js | 8 ++--- .../renderers/webgl/nodes/WebGLNodeBuilder.js | 11 +++--- .../jsm/renderers/webgl/nodes/WebGLNodes.js | 34 ++++++++++++++----- .../jsm/renderers/webgpu/WebGPURenderer.js | 14 ++++++-- examples/misc_nodes_gpgpu.html | 6 ++-- examples/webgpu_audio_processing.html | 2 +- 7 files changed, 53 insertions(+), 26 deletions(-) diff --git a/examples/jsm/nodes/gpgpu/ComputeNode.js b/examples/jsm/nodes/gpgpu/ComputeNode.js index 3da24e151f50a9..ed48631d4f5fee 100644 --- a/examples/jsm/nodes/gpgpu/ComputeNode.js +++ b/examples/jsm/nodes/gpgpu/ComputeNode.js @@ -3,7 +3,7 @@ import { NodeUpdateType } from '../core/constants.js'; class ComputeNode extends Node { - constructor( computeNode, count = 0, populateOutArray = true, workgroupSize = [ 64 ] ) { + constructor( computeNode, count = 0, workgroupSize = [ 64 ] ) { super( 'void' ); @@ -15,8 +15,6 @@ class ComputeNode extends Node { this.workgroupSize = workgroupSize; this.dispatchCount = 0; - this.populateOutArray = true; - this.updateType = NodeUpdateType.OBJECT; this.updateDispatchCount(); diff --git a/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js b/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js index 9d9de12191d49b..9dcb507f3a7c91 100644 --- a/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js +++ b/examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js @@ -168,12 +168,12 @@ export const func = ( code, includes ) => { }; -export const uniform = ( nodeOrType ) => { +export const uniform = ( arg1, arg2 ) => { - const nodeType = getConstNodeType( nodeOrType ); + const nodeType = getConstNodeType( arg2 || arg1 ); // @TODO: get ConstNode from .traverse() in the future - const value = nodeOrType.isNode === true ? ( nodeOrType.node && nodeOrType.node.value ) || nodeOrType.value : nodeOrType; + const value = arg1.isNode === true ? ( arg1.node && arg1.node.value ) || arg1.value : arg1; return nodeObject( new UniformNode( value, nodeType ) ); @@ -359,7 +359,7 @@ export const pointUV = nodeImmutable( PointUVNode ); // gpgpu -export const compute = ( node, count, populateOutArray, workgroupSize ) => nodeObject( new ComputeNode( nodeObject( node ), count, populateOutArray, workgroupSize ) ); +export const compute = ( node, count, workgroupSize ) => nodeObject( new ComputeNode( nodeObject( node ), count, workgroupSize ) ); // display diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js index b1590b933538ba..cbf6094204fc36 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js @@ -1,5 +1,5 @@ import { PerspectiveCamera, ShaderChunk, ShaderLib, UniformsUtils, UniformsLib } from 'three'; -import { defaultShaderStages, NodeFrame, MathNode, GLSLNodeParser, NodeBuilder, uint, viewportCoordinate, viewportResolution } from 'three/nodes'; +import { defaultShaderStages, NodeFrame, MathNode, GLSLNodeParser, NodeBuilder, viewportCoordinate, viewportResolution } from 'three/nodes'; import SlotNode from './SlotNode.js'; import WebGLBuffer from '../WebGLBuffer.js'; @@ -466,7 +466,10 @@ class WebGLNodeBuilder extends NodeBuilder { if ( ( nodeData.uniformBuffer === undefined ) && ( type === 'buffer' || type === 'storageBuffer' ) ) { - nodeData.uniformBuffer = new WebGLBuffer( node.value ); + if ( this.renderer.bindings === undefined ) this.renderer.bindings = new Map(); + const buffer = this.renderer.bindings.get( node.value ) || new WebGLBuffer( node.value ); + this.renderer.bindings.set( node.value, buffer ); + nodeData.uniformBuffer = buffer; } @@ -530,7 +533,7 @@ class WebGLNodeBuilder extends NodeBuilder { getInstanceIndex() { - const node = uint( viewportCoordinate.y ).mul( viewportResolution.width ).add( uint( viewportCoordinate.x ) ); + const node = ( viewportCoordinate.y ).mul( viewportResolution.width ).add( viewportCoordinate.x ); const currentBuildStage = this.getBuildStage(); this.setBuildStage( 'construct' ); @@ -609,7 +612,7 @@ class WebGLNodeBuilder extends NodeBuilder { getFragCoord() { - return 'gl_FragCoord'; + return 'uvec4( gl_FragCoord )'; } diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodes.js b/examples/jsm/renderers/webgl/nodes/WebGLNodes.js index b291fe729b5c97..e1f10efbc3035d 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodes.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodes.js @@ -55,6 +55,8 @@ WebGLRenderer.prototype.compute = async function ( ...computeNodes ) { const scene = new Scene().add( object ); const camera = new Camera(); + const currentRenderTarget = this.getRenderTarget(); + for ( const computeNode of computeNodes ) { material.colorNode = computeNode.computeNode; @@ -64,21 +66,35 @@ WebGLRenderer.prototype.compute = async function ( ...computeNodes ) { const outBuffer = builders.get( material ).outComputeBuffer; - const renderTarget = outBuffer.renderTarget; - const currentRenderTarget = this.getRenderTarget(); - this.setRenderTarget( renderTarget ); + this.setRenderTarget( outBuffer.renderTarget ); // nodeFrame.update(); this.render( scene, camera ); - if ( computeNode.populateOutArray === true ) { // Note that if this is false then the results will be available only for further computeNodes, and will not be available after the completion of WebGLRenderer.compute() - - this.readRenderTargetPixels( renderTarget, 0, 0, renderTarget.width, renderTarget.height, outBuffer.attribute.array ); - - } - this.setRenderTarget( currentRenderTarget ); } + this.setRenderTarget( currentRenderTarget ); + material.dispose(); geometry.dispose(); }; + +WebGLRenderer.prototype.deuploadBufferAttribute = async function ( attribute ) { + + const { renderTarget } = this.bindings.get( attribute ); + this.readRenderTargetPixels( renderTarget, 0, 0, renderTarget.width, renderTarget.height, attribute.array ); + +}; + +const dispose = WebGLRenderer.prototype.dispose; +WebGLRenderer.prototype.dispose = function() { + + dispose.apply( this ); + if ( this.bindings !== undefined ) { + + this.bindings.forEach( buffer => buffer.dispose() ); + this.bindings.clear(); + + } + +}; diff --git a/examples/jsm/renderers/webgpu/WebGPURenderer.js b/examples/jsm/renderers/webgpu/WebGPURenderer.js index 845a27d73082fe..e8f3d4cd4922b7 100644 --- a/examples/jsm/renderers/webgpu/WebGPURenderer.js +++ b/examples/jsm/renderers/webgpu/WebGPURenderer.js @@ -419,9 +419,19 @@ class WebGPURenderer { } - async populateArray( attribute ) { + async deuploadBufferAttribute( attribute, overwrite = true ) { - attribute.array = new attribute.array.constructor( await this._attributes.getArrayBuffer( attribute ) ); + const array = new attribute.array.constructor( await this._attributes.getArrayBuffer( attribute ) ); + + if ( overwrite === true ) { + + attribute.array = array; + + } else { + + attribute.array.set( array ); + + } } diff --git a/examples/misc_nodes_gpgpu.html b/examples/misc_nodes_gpgpu.html index 83f5766064d2eb..f73912d43147e9 100644 --- a/examples/misc_nodes_gpgpu.html +++ b/examples/misc_nodes_gpgpu.html @@ -60,7 +60,7 @@ } ); - let camera, scene, material, renderer, computationRenderer, iterations; + let camera, scene, material, renderer, iterations; init(); @@ -177,7 +177,7 @@ const TypedArray = params.backend === 'WebGL' ? Uint8Array : Uint32Array; // WebGPU can only work with uint32 - const index = instanceIndex; // @COMMENT: Maybe make this an alias to instanceIndex in ShaderNodeBaseElements? + const index = instanceIndex; switch ( params.example ) { @@ -237,7 +237,7 @@ } await renderer.compute( compute( shaderNode, outAttribute.count ) ); - if ( params.backend === 'WebGPU' ) await renderer.populateArray( outAttribute ); + await renderer.deuploadBufferAttribute( outAttribute ); let typedArray = outAttribute.array; diff --git a/examples/webgpu_audio_processing.html b/examples/webgpu_audio_processing.html index f70163d42019f9..c7522a0f498ba0 100644 --- a/examples/webgpu_audio_processing.html +++ b/examples/webgpu_audio_processing.html @@ -60,7 +60,7 @@ // compute audio await renderer.compute( computeNode ); - await renderer.populateArray( waveGPUBuffer ); + await renderer.deuploadBufferAttribute( waveGPUBuffer ); const waveArray = waveGPUBuffer.array; // play result From 587199672865ea753830db70f92f7a499ef6d908 Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Tue, 17 Jan 2023 00:30:49 +1100 Subject: [PATCH 10/18] Fix framebuffer loop problem --- examples/jsm/nodes/core/AssignNode.js | 11 ++++++----- examples/jsm/nodes/core/BypassNode.js | 6 +++--- examples/jsm/nodes/core/ExpressionNode.js | 10 +++++----- examples/jsm/nodes/core/StackNode.js | 15 ++++++++++----- examples/jsm/nodes/core/UniformNode.js | 17 ++++++++++++----- examples/jsm/nodes/utils/ArrayElementNode.js | 5 ++--- .../jsm/renderers/webgl/nodes/WebGLNodes.js | 4 +++- 7 files changed, 41 insertions(+), 27 deletions(-) diff --git a/examples/jsm/nodes/core/AssignNode.js b/examples/jsm/nodes/core/AssignNode.js index faeb7131a313e3..34cd65365703ca 100644 --- a/examples/jsm/nodes/core/AssignNode.js +++ b/examples/jsm/nodes/core/AssignNode.js @@ -40,23 +40,24 @@ class AssignNode extends TempNode { generate( builder, output ) { const aNode = this.aNode; - const type = this.getNodeType( builder, output ); - const a = aNode.build( builder, type ); + const bNode = this.bNode; - if ( aNode.isBufferNode === true || aNode.node.isBufferNode === true ) { + if ( aNode.isBufferNode === true || aNode.node.isBufferNode === true ) { // TODO: Maybe this can be moved to .construct()? const nodeData = builder.getDataFromNode( aNode.isBufferNode ? aNode : aNode.node, builder.getShaderStage() ); const buffer = nodeData.uniformBuffer; if ( buffer !== undefined ) { builder.outComputeBuffer = buffer; - return buffer.setElement( this.bNode ).build( builder ); + return buffer.setElement( bNode ).build( builder ); } } - const bNode = this.bNode; + const type = this.getNodeType( builder, output ); + + const a = aNode.build( builder, type ); const b = bNode.build( builder, type ); if ( output !== 'void' ) { diff --git a/examples/jsm/nodes/core/BypassNode.js b/examples/jsm/nodes/core/BypassNode.js index a5e5ab944bb790..e9f4f551261308 100644 --- a/examples/jsm/nodes/core/BypassNode.js +++ b/examples/jsm/nodes/core/BypassNode.js @@ -2,14 +2,14 @@ import Node from './Node.js'; class BypassNode extends Node { - constructor( returnNode, callNode ) { + constructor( callNode, outputNode = null ) { super(); this.isBypassNode = true; - this.outputNode = returnNode; this.callNode = callNode; + this.outputNode = outputNode; } @@ -29,7 +29,7 @@ class BypassNode extends Node { } - return this.outputNode.build( builder, output ); + return this.outputNode ? this.outputNode.build( builder, output ) : ''; } diff --git a/examples/jsm/nodes/core/ExpressionNode.js b/examples/jsm/nodes/core/ExpressionNode.js index 52bb63f84b71aa..bb2f507e8a1421 100644 --- a/examples/jsm/nodes/core/ExpressionNode.js +++ b/examples/jsm/nodes/core/ExpressionNode.js @@ -2,26 +2,26 @@ import Node from './Node.js'; class ExpressionNode extends Node { - constructor( snipped = '', nodeType = 'void' ) { + constructor( snippet = '', nodeType = 'void' ) { super( nodeType ); - this.snipped = snipped; + this.snippet = snippet; } generate( builder, output ) { const type = this.getNodeType( builder ); - const snipped = this.snipped; + const snippet = this.snippet; if ( type === 'void' ) { - builder.addFlowCode( snipped ); + builder.addFlowCode( snippet ); } else { - return builder.format( `( ${ snipped } )`, type, output ); + return builder.format( `( ${ snippet } )`, type, output ); } diff --git a/examples/jsm/nodes/core/StackNode.js b/examples/jsm/nodes/core/StackNode.js index 2f4598f5d7dc09..5ada9fff51fd29 100644 --- a/examples/jsm/nodes/core/StackNode.js +++ b/examples/jsm/nodes/core/StackNode.js @@ -1,7 +1,6 @@ import Node from './Node.js'; import AssignNode from './AssignNode.js'; import BypassNode from '../core/BypassNode.js'; -import ExpressionNode from '../core/ExpressionNode.js'; class StackNode extends Node { @@ -24,7 +23,7 @@ class StackNode extends Node { add( node ) { - this.nodes.push( new BypassNode( new ExpressionNode(), node ) ); + this.nodes.push( new BypassNode( node ) ); return this; @@ -38,13 +37,19 @@ class StackNode extends Node { build( builder, ...params ) { - for ( const node of this.nodes ) { + let outputNode = this.outputNode; + + if ( ( ! outputNode || ( this.outputNode.uuid === this.uuid ) ) && this.nodes.length ) { // Make last node the output node - node.build( builder ); + outputNode = this.nodes[ this.nodes.length - 1 ].callNode; } - const outputNode = this.outputNode && ( this.outputNode.uuid !== this.uuid ) ? this.outputNode : this.nodes[ this.nodes.length - 1 ].callNode; + for ( const node of this.nodes ) { + + if ( node.callNode !== outputNode ) node.build( builder ); + + } return outputNode ? outputNode.build( builder, ...params ) : super.build( builder, ...params ); diff --git a/examples/jsm/nodes/core/UniformNode.js b/examples/jsm/nodes/core/UniformNode.js index 7bb713172ba2f5..d1a996fda1c9eb 100644 --- a/examples/jsm/nodes/core/UniformNode.js +++ b/examples/jsm/nodes/core/UniformNode.js @@ -16,9 +16,7 @@ class UniformNode extends InputNode { } - generate( builder, output ) { - - const type = this.getNodeType( builder ); + construct( builder ) { const hash = this.getUniformHash( builder ); @@ -34,8 +32,17 @@ class UniformNode extends InputNode { const sharedNodeType = sharedNode.getInputType( builder ); - const nodeUniform = builder.getUniformFromNode( sharedNode, builder.shaderStage, sharedNodeType ); - const propertyName = builder.getPropertyName( nodeUniform ); + this._nodeUniform = builder.getUniformFromNode( sharedNode, builder.shaderStage, sharedNodeType ); + + return null; + + } + + generate( builder, output ) { + + const type = this.getNodeType( builder ); + + const propertyName = builder.getPropertyName( this._nodeUniform ); return builder.format( propertyName, type, output ); diff --git a/examples/jsm/nodes/utils/ArrayElementNode.js b/examples/jsm/nodes/utils/ArrayElementNode.js index 5af16fb395a865..371bb8f6662398 100644 --- a/examples/jsm/nodes/utils/ArrayElementNode.js +++ b/examples/jsm/nodes/utils/ArrayElementNode.js @@ -19,9 +19,7 @@ class ArrayElementNode extends TempNode { generate( builder ) { - const nodeSnippet = this.node.build( builder ); // We should build the node before accessing its nodeData - - if ( this.node.isBufferNode === true ) { + if ( this.node.isBufferNode === true ) { // TODO: Maybe this can be moved to .construct()? const nodeData = builder.getDataFromNode( this.node, builder.getShaderStage() ); const buffer = nodeData.uniformBuffer; @@ -33,6 +31,7 @@ class ArrayElementNode extends TempNode { } + const nodeSnippet = this.node.build( builder ); const indexSnippet = this.indexNode.build( builder, 'uint' ); return `${nodeSnippet}[ ${indexSnippet} ]`; diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodes.js b/examples/jsm/renderers/webgl/nodes/WebGLNodes.js index e1f10efbc3035d..9841ba3eaca404 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodes.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodes.js @@ -9,7 +9,9 @@ Material.prototype.onBuild = function ( object, parameters, renderer ) { if ( object.material.isNodeMaterial === true ) { - builders.set( this, new WebGLNodeBuilder( object, renderer, parameters ).build() ); + const builder = new WebGLNodeBuilder( object, renderer, parameters ); + builder.build(); + builders.set( this, builder ); } From b489298b91cc6108916a3dece8254ff616d03dc2 Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Tue, 17 Jan 2023 00:44:38 +1100 Subject: [PATCH 11/18] Fix why other examples weren't rendering --- examples/jsm/nodes/core/AssignNode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/jsm/nodes/core/AssignNode.js b/examples/jsm/nodes/core/AssignNode.js index 34cd65365703ca..5a06c0e6e2d39c 100644 --- a/examples/jsm/nodes/core/AssignNode.js +++ b/examples/jsm/nodes/core/AssignNode.js @@ -49,7 +49,7 @@ class AssignNode extends TempNode { if ( buffer !== undefined ) { builder.outComputeBuffer = buffer; - return buffer.setElement( bNode ).build( builder ); + return buffer.setElement( bNode ).build( builder, output ); } From 96be9611ba25bd1191b8686ceca1dd645e6d1d2d Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Tue, 17 Jan 2023 01:08:32 +1100 Subject: [PATCH 12/18] Fix WebGLNodeBuilder.getInstanceIndex() --- examples/jsm/nodes/utils/SplitNode.js | 7 ++++--- examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js | 4 ++-- examples/jsm/renderers/webgl/nodes/WebGLNodes.js | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/jsm/nodes/utils/SplitNode.js b/examples/jsm/nodes/utils/SplitNode.js index 6051c8a912ce84..12e555ddd2f335 100644 --- a/examples/jsm/nodes/utils/SplitNode.js +++ b/examples/jsm/nodes/utils/SplitNode.js @@ -30,14 +30,15 @@ class SplitNode extends Node { getNodeType( builder ) { - return builder.getTypeFromLength( this.components.length ); + return builder.getTypeFromLength( this.components.length, builder.getComponentType( this.node.getNodeType( builder ) ) ); } generate( builder, output ) { const node = this.node; - const nodeTypeLength = builder.getTypeLength( node.getNodeType( builder ) ); + const nodeType = node.getNodeType( builder ); + const nodeTypeLength = builder.getTypeLength( nodeType ); let snippet = null; @@ -51,7 +52,7 @@ class SplitNode extends Node { // needed expand the input node - type = builder.getTypeFromLength( this.getVectorLength() ); + type = builder.getTypeFromLength( this.getVectorLength(), builder.getComponentType( nodeType ) ); } diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js index cbf6094204fc36..499130dda5dce5 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js @@ -1,5 +1,5 @@ import { PerspectiveCamera, ShaderChunk, ShaderLib, UniformsUtils, UniformsLib } from 'three'; -import { defaultShaderStages, NodeFrame, MathNode, GLSLNodeParser, NodeBuilder, viewportCoordinate, viewportResolution } from 'three/nodes'; +import { defaultShaderStages, NodeFrame, MathNode, GLSLNodeParser, NodeBuilder, viewportCoordinate } from 'three/nodes'; import SlotNode from './SlotNode.js'; import WebGLBuffer from '../WebGLBuffer.js'; @@ -533,7 +533,7 @@ class WebGLNodeBuilder extends NodeBuilder { getInstanceIndex() { - const node = ( viewportCoordinate.y ).mul( viewportResolution.width ).add( viewportCoordinate.x ); + const node = ( viewportCoordinate.y ).mul( this.outComputeBuffer.texture.image.width ).add( viewportCoordinate.x ); const currentBuildStage = this.getBuildStage(); this.setBuildStage( 'construct' ); diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodes.js b/examples/jsm/renderers/webgl/nodes/WebGLNodes.js index 9841ba3eaca404..c7a3f5a42345e5 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodes.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodes.js @@ -69,7 +69,7 @@ WebGLRenderer.prototype.compute = async function ( ...computeNodes ) { const outBuffer = builders.get( material ).outComputeBuffer; this.setRenderTarget( outBuffer.renderTarget ); - // nodeFrame.update(); + nodeFrame.update(); this.render( scene, camera ); } From 7d9e0408b1f659fe384cc8aaf42ac9a826ed0d2e Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Tue, 17 Jan 2023 01:24:14 +1100 Subject: [PATCH 13/18] Fix other Nodes examples that broke --- examples/jsm/nodes/accessors/TextureNode.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/jsm/nodes/accessors/TextureNode.js b/examples/jsm/nodes/accessors/TextureNode.js index 6378472277f478..0b491cf17b824f 100644 --- a/examples/jsm/nodes/accessors/TextureNode.js +++ b/examples/jsm/nodes/accessors/TextureNode.js @@ -36,6 +36,8 @@ class TextureNode extends UniformNode { construct( builder ) { + super.construct( builder ); + const properties = builder.getNodeProperties( this ); // From ddf51c7d3e3a9223d2c048ea7aac2742720d4934 Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Tue, 17 Jan 2023 01:43:47 +1100 Subject: [PATCH 14/18] Fix --- examples/jsm/nodes/accessors/Object3DNode.js | 2 ++ examples/jsm/nodes/core/AssignNode.js | 2 +- examples/misc_nodes_gpgpu.html | 11 ++++++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/examples/jsm/nodes/accessors/Object3DNode.js b/examples/jsm/nodes/accessors/Object3DNode.js index 21a7acde40c889..5e0ac4766d01ab 100644 --- a/examples/jsm/nodes/accessors/Object3DNode.js +++ b/examples/jsm/nodes/accessors/Object3DNode.js @@ -99,6 +99,8 @@ class Object3DNode extends Node { } + this._uniformNode.construct( builder ); + return this._uniformNode.build( builder ); } diff --git a/examples/jsm/nodes/core/AssignNode.js b/examples/jsm/nodes/core/AssignNode.js index 5a06c0e6e2d39c..dabb0b13962602 100644 --- a/examples/jsm/nodes/core/AssignNode.js +++ b/examples/jsm/nodes/core/AssignNode.js @@ -42,7 +42,7 @@ class AssignNode extends TempNode { const aNode = this.aNode; const bNode = this.bNode; - if ( aNode.isBufferNode === true || aNode.node.isBufferNode === true ) { // TODO: Maybe this can be moved to .construct()? + if ( aNode.isBufferNode === true || ( aNode.node && aNode.node.isBufferNode === true ) ) { // TODO: Maybe this can be moved to .construct()? const nodeData = builder.getDataFromNode( aNode.isBufferNode ? aNode : aNode.node, builder.getShaderStage() ); const buffer = nodeData.uniformBuffer; diff --git a/examples/misc_nodes_gpgpu.html b/examples/misc_nodes_gpgpu.html index f73912d43147e9..343ab458b43819 100644 --- a/examples/misc_nodes_gpgpu.html +++ b/examples/misc_nodes_gpgpu.html @@ -243,12 +243,17 @@ if ( params.logBuffers && iteration === 0 ) console.log( params.backend, params.example, typedArray ); - if ( params.backend === 'WebGPU' ) typedArray = uint32ToUint8( typedArray ); // WebGPU doesn't currently support sampling uint32 textures - - const type = getTextureType( outAttribute ); + let type = getTextureType( outAttribute ); const format = getTextureFormat( outAttribute ); const { width, height } = calculateWidthHeight( outAttribute.count ); + if ( params.backend === 'WebGPU' ) { // WebGPU doesn't currently support sampling uint32 textures + + typedArray = uint32ToUint8( typedArray ); + type = THREE.UnsignedByteType; + + } + material.map = new THREE.DataTexture( typedArray, width, height, format, type ); material.map.needsUpdate = true; From 4ca19cf4608827b6e6f8b20de8b84a7c0f42ecc2 Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Tue, 17 Jan 2023 02:34:15 +1100 Subject: [PATCH 15/18] Fix --- examples/jsm/nodes/accessors/Object3DNode.js | 2 ++ examples/jsm/nodes/core/UniformNode.js | 2 ++ examples/misc_nodes_gpgpu.html | 8 ++++---- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/jsm/nodes/accessors/Object3DNode.js b/examples/jsm/nodes/accessors/Object3DNode.js index 5e0ac4766d01ab..c7ecce881f1e41 100644 --- a/examples/jsm/nodes/accessors/Object3DNode.js +++ b/examples/jsm/nodes/accessors/Object3DNode.js @@ -81,6 +81,8 @@ class Object3DNode extends Node { } + construct() {} // Clear .construct() so that if it is called (e.g. for CameraNode) it does not call this._uniformNode.construct() before its nodeType is set + generate( builder ) { const scope = this.scope; diff --git a/examples/jsm/nodes/core/UniformNode.js b/examples/jsm/nodes/core/UniformNode.js index d1a996fda1c9eb..e01cf07c0ee552 100644 --- a/examples/jsm/nodes/core/UniformNode.js +++ b/examples/jsm/nodes/core/UniformNode.js @@ -40,6 +40,8 @@ class UniformNode extends InputNode { generate( builder, output ) { + if ( this._nodeUniform === undefined ) this.construct( builder ); // just-in-case -- this line shouldn't be required + const type = this.getNodeType( builder ); const propertyName = builder.getPropertyName( this._nodeUniform ); diff --git a/examples/misc_nodes_gpgpu.html b/examples/misc_nodes_gpgpu.html index 343ab458b43819..4ffe7b63950446 100644 --- a/examples/misc_nodes_gpgpu.html +++ b/examples/misc_nodes_gpgpu.html @@ -164,8 +164,6 @@ const iteration = iterations[ params.example ]; iterations[ params.example ] ++; - if ( params.logPerformance ) console.time( params.backend + ' ' + params.example + ' Iteration-' + iteration ); - if ( material.map !== null ) { material.map.dispose(); @@ -236,9 +234,13 @@ } + if ( params.logPerformance ) console.time( params.backend + ' ' + params.example + ' Iteration-' + iteration ); + await renderer.compute( compute( shaderNode, outAttribute.count ) ); await renderer.deuploadBufferAttribute( outAttribute ); + if ( params.logPerformance ) console.timeEnd( params.backend + ' ' + params.example + ' Iteration-' + iteration ); + let typedArray = outAttribute.array; if ( params.logBuffers && iteration === 0 ) console.log( params.backend, params.example, typedArray ); @@ -259,8 +261,6 @@ await renderer.render( scene, camera ); - if ( params.logPerformance ) console.timeEnd( params.backend + ' ' + params.example + ' Iteration-' + iteration ); - if ( iteration < params.warmUps ) await showExample(); } From a73ba79f51685e4561c7c37ff6780425fd8486c9 Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Tue, 17 Jan 2023 18:01:07 +1100 Subject: [PATCH 16/18] Little bit more accurate measurements --- examples/misc_nodes_gpgpu.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/misc_nodes_gpgpu.html b/examples/misc_nodes_gpgpu.html index 4ffe7b63950446..1722474b636eda 100644 --- a/examples/misc_nodes_gpgpu.html +++ b/examples/misc_nodes_gpgpu.html @@ -234,12 +234,12 @@ } - if ( params.logPerformance ) console.time( params.backend + ' ' + params.example + ' Iteration-' + iteration ); - + if ( params.logPerformance ) console.time( params.backend + ' ' + params.example + ' Iteration-' + iteration + ' Computation' ); await renderer.compute( compute( shaderNode, outAttribute.count ) ); + if ( params.logPerformance ) console.timeEnd( params.backend + ' ' + params.example + ' Iteration-' + iteration + ' Computation' ); + if ( params.logPerformance ) console.time( params.backend + ' ' + params.example + ' Iteration-' + iteration + ' Transfer' ); await renderer.deuploadBufferAttribute( outAttribute ); - - if ( params.logPerformance ) console.timeEnd( params.backend + ' ' + params.example + ' Iteration-' + iteration ); + if ( params.logPerformance ) console.timeEnd( params.backend + ' ' + params.example + ' Iteration-' + iteration + ' Transfer' ); let typedArray = outAttribute.array; From 39fed20022e2ecc0a5a307ab764f02946102c102 Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Mon, 6 Feb 2023 23:07:28 +1100 Subject: [PATCH 17/18] Fix ViewportNode --- examples/jsm/nodes/display/ViewportNode.js | 6 +++--- examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/jsm/nodes/display/ViewportNode.js b/examples/jsm/nodes/display/ViewportNode.js index 4a2734a5b4f616..4e874931daf885 100644 --- a/examples/jsm/nodes/display/ViewportNode.js +++ b/examples/jsm/nodes/display/ViewportNode.js @@ -80,15 +80,15 @@ class ViewportNode extends Node { } - generate( builder ) { + generate( builder, output ) { if ( this.scope === ViewportNode.COORDINATE ) { - return builder.getFragCoord(); + return builder.format( builder.getFragCoord(), this.getNodeType(), output ); } - return super.generate( builder ); + return super.generate( builder, output ); } diff --git a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js index 1f8d0087c6d8e0..99ef23ca83c39f 100644 --- a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js @@ -698,6 +698,8 @@ class WebGPUNodeBuilder extends NodeBuilder { _getNodeUniform( uniformNode, type ) { + type = this.changeComponentType( type, 'float' ); + if ( type === 'float' ) return new FloatNodeUniform( uniformNode ); if ( type === 'vec2' ) return new Vector2NodeUniform( uniformNode ); if ( type === 'vec3' ) return new Vector3NodeUniform( uniformNode ); From e2db2e513a8c44b83b412334d7bfcaf86b2285ed Mon Sep 17 00:00:00 2001 From: Levi Pesin <35454228+LeviPesin@users.noreply.github.com> Date: Thu, 9 Feb 2023 22:17:29 +1100 Subject: [PATCH 18/18] Fix bypass --- examples/jsm/nodes/materials/NodeMaterial.js | 6 +++--- examples/webgpu_compute.html | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/jsm/nodes/materials/NodeMaterial.js b/examples/jsm/nodes/materials/NodeMaterial.js index 819b8235c40301..9a56a04254c938 100644 --- a/examples/jsm/nodes/materials/NodeMaterial.js +++ b/examples/jsm/nodes/materials/NodeMaterial.js @@ -79,19 +79,19 @@ class NodeMaterial extends ShaderMaterial { if ( this.positionNode !== null ) { - vertex = bypass( vertex, assign( positionLocal, this.positionNode ) ); + vertex = bypass( assign( positionLocal, this.positionNode ), vertex ); } if ( ( object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true ) && builder.isAvailable( 'instance' ) === true ) { - vertex = bypass( vertex, instance( object ) ); + vertex = bypass( instance( object ), vertex ); } if ( object.isSkinnedMesh === true ) { - vertex = bypass( vertex, skinning( object ) ); + vertex = bypass( skinning( object ), vertex ); } diff --git a/examples/webgpu_compute.html b/examples/webgpu_compute.html index e7a88748052469..a0c4eb50732b5b 100644 --- a/examples/webgpu_compute.html +++ b/examples/webgpu_compute.html @@ -31,7 +31,7 @@ import { ShaderNode, compute, uniform, element, storage, attribute, mul, sin, cos, - add, cond, abs, max, min, float, vec2, vec3, color, instanceIndex + add, cond, abs, clamp, float, vec2, color, instanceIndex } from 'three/nodes'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; @@ -93,12 +93,12 @@ stack.assign( velocity.x, abs( position.x ).greaterThanEqual( limit.x ).cond( velocity.x.negate(), velocity.x ) ); stack.assign( velocity.y, abs( position.y ).greaterThanEqual( limit.y ).cond( velocity.y.negate(), velocity.y ) ); - stack.assign( position, max( limit.negate(), min( limit, position ) ) ); + stack.assign( position, clamp( position, limit.negate(), limit ) ); const pointerSize = 0.1; const distanceFromPointer = pointer.sub( position ).length(); - stack.assign( particle, cond( distanceFromPointer.lessThanEqual( pointerSize ), vec3(), position ) ); + stack.assign( particle, cond( distanceFromPointer.lessThanEqual( pointerSize ), vec2(), position ) ); } );